M Cargo.lock +122 -180
@@ 4,9 4,9 @@ version = 3
[[package]]
name = "aho-corasick"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
@@ 18,6 18,12 @@ source = "registry+https://github.com/ru
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
+name = "anymap"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
+
+[[package]]
name = "argh"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 76,9 82,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33
[[package]]
name = "bumpalo"
-version = "3.12.1"
+version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "cast"
@@ 100,9 106,9 @@ checksum = "baf1de4339761588bc0619e3cbc0
[[package]]
name = "ciborium"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
+checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
@@ 111,15 117,15 @@ dependencies = [
[[package]]
name = "ciborium-io"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
+checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
+checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half",
@@ 127,9 133,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "3.2.23"
+version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"bitflags",
"clap_lex",
@@ 215,9 221,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.14"
+version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
@@ 228,9 234,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.15"
+version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
@@ 262,7 268,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56
dependencies = [
"errno-dragonfly",
"libc",
- "windows-sys 0.48.0",
+ "windows-sys",
]
[[package]]
@@ 286,9 292,9 @@ dependencies = [
[[package]]
name = "fm"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671381339b1671872f5725caff3a4bd05be68a0d8adf2d8a7ff6c16395bbe35a"
+checksum = "21bcf4db620a804cf7e9d84fbcb5d4ac83a8c43396203b2507d62ea31814dfd4"
dependencies = [
"regex",
]
@@ 303,6 309,7 @@ checksum = "3f9eec918d3f24069decb9af1554
name = "garnet"
version = "0.1.0"
dependencies = [
+ "anymap",
"argh",
"codespan-reporting",
"criterion",
@@ 346,18 353,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.2.6"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "hermit-abi"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "humantime"
@@ 389,13 387,13 @@ dependencies = [
[[package]]
name = "io-lifetimes"
-version = "1.0.10"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
- "hermit-abi 0.3.1",
+ "hermit-abi 0.3.2",
"libc",
- "windows-sys 0.48.0",
+ "windows-sys",
]
[[package]]
@@ 409,15 407,15 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.6"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
[[package]]
name = "js-sys"
-version = "0.3.61"
+version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
@@ 446,24 444,21 @@ checksum = "e2abad23fbc42b3700f2f279844d
[[package]]
name = "libc"
-version = "0.2.142"
+version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "linux-raw-sys"
-version = "0.3.4"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "log"
-version = "0.4.17"
+version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "logos"
@@ 496,9 491,9 @@ checksum = "2dffe52ecf27772e601905b7522c
[[package]]
name = "memoffset"
-version = "0.8.0"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
@@ 514,19 509,19 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.15.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
- "hermit-abi 0.2.6",
+ "hermit-abi 0.3.2",
"libc",
]
[[package]]
name = "once_cell"
-version = "1.17.1"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oorandom"
@@ 536,15 531,15 @@ checksum = "0ab1bc2a289d34bd04a330323ac9
[[package]]
name = "os_str_bytes"
-version = "6.5.0"
+version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
+checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
[[package]]
name = "plotters"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
+checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
@@ 555,15 550,15 @@ dependencies = [
[[package]]
name = "plotters-backend"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
+checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
-version = "0.3.3"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
+checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
@@ 580,9 575,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
dependencies = [
"unicode-ident",
]
@@ 595,9 590,9 @@ checksum = "a1d01941d82fa2ab50be1e79e671
[[package]]
name = "quote"
-version = "1.0.26"
+version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
@@ 635,13 630,25 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.8.1"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.7.1",
+ "regex-automata",
+ "regex-syntax 0.7.4",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.7.4",
]
[[package]]
@@ 652,29 659,29 @@ checksum = "f162c6dd7b008981e4d40210aca2
[[package]]
name = "regex-syntax"
-version = "0.7.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
+checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "rustix"
-version = "0.37.14"
+version = "0.37.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
+checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
- "windows-sys 0.48.0",
+ "windows-sys",
]
[[package]]
name = "ryu"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
[[package]]
name = "same-file"
@@ 693,29 700,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f7
[[package]]
name = "serde"
-version = "1.0.160"
+version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.160"
+version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.15",
+ "syn 2.0.25",
]
[[package]]
name = "serde_json"
-version = "1.0.96"
+version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
dependencies = [
"itoa",
"ryu",
@@ 735,9 742,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.15"
+version = "2.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
dependencies = [
"proc-macro2",
"quote",
@@ 746,15 753,16 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.5.0"
+version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
dependencies = [
+ "autocfg",
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
- "windows-sys 0.45.0",
+ "windows-sys",
]
[[package]]
@@ 793,9 801,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
[[package]]
name = "unicode-width"
@@ 824,9 832,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.84"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ 834,24 842,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.84"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.25",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.84"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ 859,28 867,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.84"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.25",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.84"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "web-sys"
-version = "0.3.61"
+version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ 919,132 927,66 @@ checksum = "712e227841d057c1ee1cd2fb22fa
[[package]]
name = "windows-sys"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
-dependencies = [
- "windows-targets 0.42.2",
-]
-
-[[package]]
-name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "windows-targets 0.48.0",
+ "windows-targets",
]
[[package]]
name = "windows-targets"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
-dependencies = [
- "windows_aarch64_gnullvm 0.42.2",
- "windows_aarch64_msvc 0.42.2",
- "windows_i686_gnu 0.42.2",
- "windows_i686_msvc 0.42.2",
- "windows_x86_64_gnu 0.42.2",
- "windows_x86_64_gnullvm 0.42.2",
- "windows_x86_64_msvc 0.42.2",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.0"
+version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
- "windows_aarch64_gnullvm 0.48.0",
- "windows_aarch64_msvc 0.48.0",
- "windows_i686_gnu 0.48.0",
- "windows_i686_msvc 0.48.0",
- "windows_x86_64_gnu 0.48.0",
- "windows_x86_64_gnullvm 0.48.0",
- "windows_x86_64_msvc 0.48.0",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
-
-[[package]]
-name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
-
-[[package]]
-name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
-
-[[package]]
-name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
-
-[[package]]
-name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
-
-[[package]]
-name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
M Cargo.toml +2 -0
@@ 4,6 4,7 @@ version = "0.1.0"
authors = ["Simon Heath <icefox@dreamquest.io>"]
edition = "2018"
license = "LGPL-3.0-only"
+default-run = "garnetc"
[[test]]
@@ 26,6 27,7 @@ once_cell = "1"
logos = "0.12"
log = "0.4"
pretty_env_logger = "0.4"
+anymap = "0.12"
[dev-dependencies]
M benches/basic.rs +58 -19
@@ 5,7 5,7 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
-use garnet;
+use garnet::*;
/// Each iteration creates about 32 sLOC
fn gen_dumb_test_code(count: usize) -> String {
@@ 66,31 66,19 @@ end
buf
}
-fn criterion_benchmark(c: &mut Criterion) {
+fn bench_compile(c: &mut Criterion) {
let code = gen_dumb_test_code(103);
let lines = code.lines().count();
- let name = format!("compile {}ish lines", lines);
+ let name = format!("compile {}ish lines to Rust", lines);
c.bench_function(&name, |b| {
- b.iter(|| {
- garnet::compile(
- "criterion.gt",
- black_box(&code),
- garnet::backend::Backend::Rust,
- )
- })
+ b.iter(|| compile("criterion.gt", black_box(&code), backend::Backend::Rust))
});
let code = gen_dumb_test_code(103 * 8);
let lines = code.lines().count();
- let name = format!("compile {}ish lines", lines);
+ let name = format!("compile {}ish lines to Rust", lines);
c.bench_function(&name, |b| {
- b.iter(|| {
- garnet::compile(
- "criterion.gt",
- black_box(&code),
- garnet::backend::Backend::Rust,
- )
- })
+ b.iter(|| compile("criterion.gt", black_box(&code), backend::Backend::Rust))
});
let code = gen_dumb_test_code(103 * 16);
@@ 107,5 95,56 @@ fn criterion_benchmark(c: &mut Criterion
});
}
-criterion_group!(benches, criterion_benchmark);
+fn bench_stages(c: &mut Criterion) {
+ let code = gen_dumb_test_code(103 * 8);
+ let lines = code.lines().count();
+ let name = format!("Parse {} lines", lines);
+ c.bench_function(&name, |b| {
+ b.iter(|| {
+ let mut parser = parser::Parser::new("criterion.gt", black_box(&code));
+ parser.parse()
+ })
+ });
+
+ let mut parser = parser::Parser::new("criterion.gt", black_box(&code));
+ let ast = parser.parse();
+
+ c.bench_function("lower and run passes", |b| {
+ b.iter(|| {
+ let hir = hir::lower(black_box(&ast));
+ passes::run_passes(hir)
+ })
+ });
+
+ let hir = hir::lower(black_box(&ast));
+ let hir = passes::run_passes(hir);
+ let (hir, mut symtbl) = symtbl::resolve_symbols(hir);
+
+ c.bench_function("typecheck and borrowcheck", |b| {
+ b.iter(|| {
+ let tck = &mut typeck::typecheck(black_box(&hir), &mut symtbl).unwrap();
+ borrowck::borrowck(&hir, tck).unwrap();
+ })
+ });
+
+ let tck = &mut typeck::typecheck(black_box(&hir), &mut symtbl).unwrap();
+ borrowck::borrowck(&hir, tck).unwrap();
+
+ c.bench_function("typechecked passes", |b| {
+ b.iter(|| {
+ passes::run_typechecked_passes(black_box(hir.clone()), black_box(tck));
+ })
+ });
+
+ let hir = passes::run_typechecked_passes(hir, tck);
+ c.bench_function("codegen to Rust", |b| {
+ b.iter(|| backend::output(backend::Backend::Rust, black_box(&hir), black_box(tck)))
+ });
+
+ c.bench_function("codegen to null", |b| {
+ b.iter(|| backend::output(backend::Backend::Null, black_box(&hir), black_box(tck)))
+ });
+}
+
+criterion_group!(benches, bench_compile, bench_stages);
criterion_main!(benches);
R fml/Cargo.lock => +0 -320
@@ 1,320 0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "aho-corasick"
-version = "0.7.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "argh"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7e7e4aa7e40747e023c0761dafcb42333a9517575bbf1241747f68dd3177a62"
-dependencies = [
- "argh_derive",
- "argh_shared",
-]
-
-[[package]]
-name = "argh_derive"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69f2bd7ff6ed6414f4e5521bd509bae46454bbd513801767ced3f21a751ab4bc"
-dependencies = [
- "argh_shared",
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "argh_shared"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47253b98986dafc7a3e1cf3259194f1f47ac61abb57a57f46ec09e48d004ecda"
-
-[[package]]
-name = "beef"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
-
-[[package]]
-name = "codespan-reporting"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
-dependencies = [
- "termcolor",
- "unicode-width",
-]
-
-[[package]]
-name = "fm"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671381339b1671872f5725caff3a4bd05be68a0d8adf2d8a7ff6c16395bbe35a"
-dependencies = [
- "regex",
-]
-
-[[package]]
-name = "fml"
-version = "0.1.0"
-dependencies = [
- "argh",
- "codespan-reporting",
- "fnv",
- "lang_tester",
- "logos",
- "once_cell",
-]
-
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
-[[package]]
-name = "getopts"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
-dependencies = [
- "unicode-width",
-]
-
-[[package]]
-name = "heck"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
-dependencies = [
- "unicode-segmentation",
-]
-
-[[package]]
-name = "hermit-abi"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "lang_tester"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d63df63b7380a561f2c38b0a67ebe3a24b87ff2928391d0b5f8f3cfd39fe112c"
-dependencies = [
- "fm",
- "getopts",
- "libc",
- "num_cpus",
- "termcolor",
- "threadpool",
- "wait-timeout",
- "walkdir",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.139"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
-
-[[package]]
-name = "logos"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1"
-dependencies = [
- "logos-derive",
-]
-
-[[package]]
-name = "logos-derive"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c"
-dependencies = [
- "beef",
- "fnv",
- "proc-macro2",
- "quote",
- "regex-syntax",
- "syn",
-]
-
-[[package]]
-name = "memchr"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
-
-[[package]]
-name = "num_cpus"
-version = "1.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "regex"
-version = "1.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.6.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
-
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "threadpool"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
-dependencies = [
- "num_cpus",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
-
-[[package]]
-name = "unicode-width"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
-
-[[package]]
-name = "wait-timeout"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "walkdir"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
-dependencies = [
- "same-file",
- "winapi",
- "winapi-util",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
R fml/Cargo.toml => +0 -25
@@ 1,25 0,0 @@
-[package]
-name = "fml"
-version = "0.1.0"
-authors = ["Simon Heath <icefox@dreamquest.io>"]
-edition = "2018"
-
-
-[[bin]]
-name = "fmlc"
-path = "src/main.rs"
-
-[[test]]
-name = "endtoend"
-path = "tests/main.rs"
-harness = false
-
-
-[dependencies]
-argh = "0.1"
-codespan-reporting = "0.11"
-fnv = "1"
-logos = "0.12"
-once_cell = "1"
-lang_tester = "0.7"
-
R fml/src/ast.rs => +0 -289
@@ 1,289 0,0 @@
-//! Abstract syntax tree.
-//!
-//! Should be a *pretty exact* representation of the source code,
-//! including things like parentheses and comments. That way we can
-//! eventually use the same structure for a code formatter and not
-//! have it nuke anything.
-//!
-//! Though code formatters have different constraints and priorities, if they have line wrapping
-//! and stuff at least. So, it might not be a particularly great code formatter.
-
-use std::sync::Mutex;
-
-use fnv::FnvHashMap;
-
-use crate::*;
-
-/// Literal value
-#[derive(Debug, Clone, PartialEq)]
-pub enum Literal {
- /// An integer of some kind
- Integer(i32),
- /// A boolean
- Bool(bool),
- /// This is kinda weird 'cause we can't parse it,
- /// but we can lower our enums to it.
- /// First string is the enum name, second is the value.
- EnumLit(String, String),
-}
-
-/// A function type signature
-#[derive(Debug, Clone, PartialEq)]
-pub struct Signature<T = Type> {
- /// Parameters
- pub params: Vec<(String, T)>,
- /// Return type
- pub rettype: T,
-}
-
-impl Signature {
- /// Turn the function signature into a Lambda
- pub fn as_type(&self) -> Type {
- let paramtypes = self.params.iter().map(|(_nm, ty)| ty.clone()).collect();
- Type::Func(paramtypes, Box::new(self.rettype.clone()))
- }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct AstId(usize);
-
-impl AstId {
- /// Creates a new globally unique AstId
- fn new() -> AstId {
- let mut val = AST_ID.lock().unwrap();
- let new_val = *val + 1;
- let new_id = AstId(*val);
- *val = new_val;
- new_id
- }
-}
-
-/// An AST node wrapper that contains information
-/// common to all AST nodes.
-#[derive(Debug, Clone, PartialEq)]
-pub struct ExprNode {
- pub node: Box<Expr>,
- pub id: AstId,
-}
-
-use once_cell::sync::Lazy;
-static AST_ID: Lazy<Mutex<usize>> = once_cell::sync::Lazy::new(|| Mutex::new(0));
-
-impl ExprNode {
- pub fn new(expr: Expr) -> Self {
- let new_id = AstId::new();
- Self {
- node: Box::new(expr),
- id: new_id,
- }
- }
-}
-
-/// Any expression.
-/// So, basically anything not a top-level decl.
-#[derive(Debug, Clone, PartialEq)]
-pub enum Expr {
- Lit {
- val: Literal,
- },
- Var {
- name: String,
- },
- Let {
- varname: String,
- typename: Option<Type>,
- init: ExprNode,
- },
- Lambda {
- signature: Signature,
- body: Vec<ExprNode>,
- },
- Funcall {
- func: ExprNode,
- params: Vec<ExprNode>,
- },
- TupleCtor {
- body: Vec<ExprNode>,
- },
- StructCtor {
- body: FnvHashMap<String, ExprNode>,
- },
- ArrayCtor {
- body: Vec<ExprNode>,
- },
- // Create a new sum type.
- // Like EnumLit, this is generated for you by the
- // compiler.
- SumCtor {
- name: String,
- variant: String,
- body: ExprNode,
- },
- // Wrap a new type.
- // Like EnumLit, this is generated by the compiler.
- TypeCtor {
- name: String,
- type_params: Vec<Type>,
- body: ExprNode,
- },
- // Opposite of TypeCtor
- TypeUnwrap {
- e: ExprNode,
- },
- StructRef {
- e: ExprNode,
- name: String,
- },
- ArrayRef {
- e: ExprNode,
- idx: ExprNode,
- },
-}
-
-impl Expr {
- /// Shortcut function for making literal bools
- /// Shortcut function for making literal integers
- pub const fn int(i: i32) -> Expr {
- Expr::Lit {
- val: Literal::Integer(i),
- }
- }
-}
-
-/// A top-level declaration in the source file.
-#[derive(Debug, Clone, PartialEq)]
-pub enum Decl {
- Function {
- name: String,
- signature: Signature,
- body: Vec<ExprNode>,
- },
- TypeDef {
- name: String,
- params: Vec<String>,
- ty: Type,
- },
- ConstDef {
- name: String,
- // ty: Type,
- init: ExprNode,
- },
-}
-
-/// A compilable chunk of AST.
-///
-/// Currently, basically a compilation unit.
-#[derive(Debug, Clone, Default, PartialEq)]
-pub struct Ast {
- pub decls: Vec<Decl>,
-}
-
-/// To implement enum values we just juggle enum decl's
-/// to create a new const def too.
-impl Ast {
- fn lower_typedef(accm: &mut Vec<Decl>, name: &str, ty: &Type, params: &[String]) {
- use Decl::*;
- match ty {
- // For `type Foo = enum Foo, Bar, Baz end`
- // synthesize
- // const Foo = {
- // .Foo = <magic unprintable thing>
- // .Bar = <magic unprintable thing>
- // .Baz = <magic unprintable thing>
- // }
- Type::Enum(ts) => {
- let struct_body: FnvHashMap<_, _> = ts
- .iter()
- .map(|s| {
- let e = ExprNode::new(Expr::Lit {
- val: Literal::EnumLit(name.to_owned(), s.clone()),
- });
- (s.clone(), e)
- })
- .collect();
- let init_val = ExprNode::new(Expr::StructCtor { body: struct_body });
- let new_constdef = ConstDef {
- name: name.to_owned(),
- init: init_val,
- };
- accm.push(new_constdef);
- }
- // For `type Foo = sum X {}, Y Thing end`
- // synthesize
- // const Foo = {
- // .X = fn({}) Foo = <magic unprintable thing> end
- // .Y = fn(Thing) Foo = <magic unprintable thing> end
- // }
- //
- // Maybe also something like this???
- // type X = {}
- // type Y = Thing
- // TODO: What do we do with the generics...
- Type::Sum(body, _generics) => {
- let struct_body: FnvHashMap<_, _> = body
- .iter()
- .map(|(variant_name, variant_type)| {
- let signature = ast::Signature {
- params: vec![("x".into(), variant_type.clone())],
- rettype: Type::Named(name.to_owned(), vec![]),
- };
- // Just return the value passed to it wrapped
- // in a constructor of some kind...?
- let body = vec![ExprNode::new(Expr::SumCtor {
- name: name.into(),
- variant: variant_name.into(),
- body: ExprNode::new(Expr::Var { name: "x".into() }),
- })];
- let e = ExprNode::new(Expr::Lambda { signature, body });
- //println!("{} is {:#?}", variant_name, e);
- (variant_name.clone(), e)
- })
- .collect();
- let init_val = ExprNode::new(Expr::StructCtor { body: struct_body });
- let new_constdef = ConstDef {
- name: name.to_owned(),
- init: init_val,
- };
- accm.push(new_constdef);
- }
- // For other types, we create a constructor function to build them.
- other => {
- let type_params: Vec<_> =
- params.iter().map(|s| Type::Generic(s.to_owned())).collect();
- let signature = ast::Signature {
- params: vec![("x".into(), other.clone())],
- rettype: Type::Named(name.to_owned(), type_params.clone()),
- };
- // The generated function just returns the value passed to it wrapped
- // in a type constructor
- let body = vec![ExprNode::new(Expr::TypeCtor {
- name: name.into(),
- type_params,
- body: ExprNode::new(Expr::Var { name: "x".into() }),
- })];
- //println!("{} is {:#?}", variant_name, e);
- let new_fundecl = Function {
- name: name.to_owned(),
- signature,
- body,
- };
- accm.push(new_fundecl);
- }
- }
- }
- pub fn lower(self) -> Self {
- let mut out = vec![];
- for decl in self.decls.into_iter() {
- use Decl::*;
- match &decl {
- Function { .. } => out.push(decl),
- ConstDef { .. } => out.push(decl),
- TypeDef { name, ty, params } => {
- out.push(decl.clone());
- Self::lower_typedef(&mut out, name, ty, params);
- }
- }
- }
- Ast { decls: out }
- }
-}
R fml/src/lib.rs => +0 -188
@@ 1,188 0,0 @@
-//! Garnet compiler guts.
-
-use fnv::FnvHashMap;
-
-pub mod ast;
-pub mod parser;
-pub mod typeck;
-
-/*
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum PrimType {
- I32,
- Bool,
-}
-*/
-
-/// A concrete type that has been fully inferred
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum Type {
- //Primitive(PrimType),
- /// A C-like enum.
- /// For now we pretend there's no underlying values attached to
- /// the name.
- ///
- /// ... I seem to have ended up with anonymous enums somehow.
- /// Not sure how I feel about this.
- Enum(Vec<String>),
- Named(String, Vec<Type>),
- Func(Vec<Type>, Box<Type>),
- /// The vec is the name of any generic type bindings in there
- /// 'cause we need to keep track of those apparently.
- Struct(FnvHashMap<String, Type>, Vec<Type>),
- /// Sum type.
- /// I guess this is ok?
- ///
- /// Like structs, contains a list of generic bindings.
- Sum(FnvHashMap<String, Type>, Vec<Type>),
- /// Arrays are just a type and a number.
- Array(Box<Type>, usize),
- /// A generic type parameter
- Generic(String),
-}
-
-impl Type {
- /// Search through the type and return any generic types in it.
- fn collect_generic_names(&self) -> Vec<String> {
- fn helper(t: &Type, accm: &mut Vec<String>) {
- match t {
- //Type::Primitive(_) => (),
- Type::Enum(_ts) => (),
- Type::Named(_, ts) => {
- for t in ts {
- helper(t, accm);
- }
- }
- Type::Func(args, rettype) => {
- for t in args {
- helper(t, accm);
- }
- helper(rettype, accm)
- }
- Type::Struct(body, generics) => {
- for (_, ty) in body {
- helper(ty, accm);
- }
- // This makes me a little uneasy
- // 'cause I thiiiink the whole point of the names on
- // the struct is to list *all* the generic names in it...
- // but we could have nested definitions
- // like Foo(@T) ( Bar(@G) ( {@T, @G} ) )
- for ty in generics {
- helper(ty, accm);
- }
- }
- // Just like structs
- Type::Sum(body, generics) => {
- for (_, ty) in body {
- helper(ty, accm);
- }
- for ty in generics {
- helper(ty, accm);
- }
- }
- Type::Array(ty, _size) => {
- helper(ty, accm);
- }
- Type::Generic(s) => {
- // Deduplicating these things while maintaining ordering
- // is kinda screwy.
- // This works, it's just, yanno, also O(n^2)
- // Could use a set to check membership , but fuckit for now.
- if !accm.contains(s) {
- accm.push(s.clone());
- }
- }
- }
- }
- let mut accm = vec![];
- helper(self, &mut accm);
- accm
- }
-
- /// Returns the type parameters *specified by the toplevel type*.
- /// Does *not* recurse to all types below it!
- /// ...except for function args apparently.
- fn get_type_params(&self) -> Vec<String> {
- fn helper(t: &Type, accm: &mut Vec<String>) {
- match t {
- //Type::Primitive(_) => (),
- Type::Enum(_ts) => (),
- Type::Named(_, generics) => {
- for g in generics {
- helper(g, accm);
- }
- }
- Type::Func(args, rettype) => {
- for t in args {
- helper(t, accm);
- }
- helper(rettype, accm)
- }
- Type::Struct(_body, generics) => {
- for g in generics {
- helper(g, accm);
- }
- }
- Type::Sum(_body, generics) => {
- for g in generics {
- helper(g, accm);
- }
- }
- Type::Array(ty, _size) => {
- helper(ty, accm);
- }
- Type::Generic(s) => {
- // Deduplicating these things while maintaining ordering
- // is kinda screwy.
- // This works, it's just, yanno, also O(n^2)
- // Could use a set to check membership , but fuckit for now.
- if !accm.contains(s) {
- accm.push(s.clone())
- }
- }
- }
- }
- let mut accm = vec![];
- helper(self, &mut accm);
- println!("Found type params for {:?}: {:?}", self, accm);
- accm
- }
-}
-
-/// A identifier to uniquely refer to our type terms
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct TypeId(usize);
-
-/// Information about a type term
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum TypeInfo {
- /// No information about the type of this type term
- Unknown,
- /// This type term is the same as another type term
- Ref(TypeId),
- Enum(Vec<String>),
- /// N-ary type constructor.
- /// It could be Int()
- /// or List(Int)
- /// or List(T)
- Named(String, Vec<TypeId>),
- /// This type term is definitely a function
- Func(Vec<TypeId>, TypeId),
- /// This is definitely some kind of struct
- Struct(FnvHashMap<String, TypeId>),
- /// Definitely a sum type
- Sum(FnvHashMap<String, TypeId>),
- Array(TypeId, usize),
- /// This is some generic type that has a name like @A
- /// AKA a type parameter.
- TypeParam(String),
-}
-
-pub fn compile(filename: &str, src: &str) -> Vec<u8> {
- let mut parser = parser::Parser::new(filename, src);
- let ast = parser.parse();
- let ast2 = ast.lower();
- typeck::typecheck(&ast2);
- vec![]
-}
R fml/src/main.rs => +0 -79
@@ 1,79 0,0 @@
-//! The main compiler driver program.
-
-use std::path::PathBuf;
-
-use argh::FromArgs;
-
-/// compiler
-#[derive(Debug, FromArgs)]
-struct Opt {
- /// save generated Rust code?
- //#[argh(switch, short = 's')]
- //save: bool,
-
- /// run resulting program immediately, handy for unit tests. Does not produce an executable.
- //#[argh(switch, short = 'r')]
- //run: bool,
-
- /// output file name
- //#[argh(option, short = 'o')]
- //out: Option<PathBuf>,
-
- /// input files
- #[argh(positional)]
- file: PathBuf,
-}
-
-fn main() -> std::io::Result<()> {
- //let opt = parse_args();
- let opt: Opt = argh::from_env();
-
- let src = std::fs::read_to_string(&opt.file)?;
- let _output = fml::compile(&opt.file.to_str().unwrap(), &src);
- /*
- let mut rust_file;
- // Output to file
- {
- rust_file = opt.file.clone();
- rust_file.set_extension("rs");
- std::fs::write(&rust_file, &output)?;
- }
- // Invoke rustc
- let exe_file = if let Some(out) = opt.out {
- out
- } else {
- let mut exe_file = opt.file.clone();
- exe_file.set_extension(std::env::consts::EXE_EXTENSION);
- exe_file
- };
- use std::process::{Command, Stdio};
- let res = Command::new("rustc")
- .stdin(Stdio::null())
- .stdout(Stdio::inherit())
- .arg("-o")
- .arg(&exe_file)
- .arg(&rust_file)
- .output()
- .expect("Failed to execute rustc");
- if !res.status.success() {
- dbg!(&res);
- panic!("Generated Rust code that could not be compiled");
- }
-
- // delete Rust files if we want to
- if !opt.save {
- std::fs::remove_file(rust_file).unwrap();
- }
-
- // Run output program if we want to
- if opt.run {
- let res = Command::new(&exe_file)
- .stdin(Stdio::inherit())
- .stdout(Stdio::inherit())
- .output();
- std::fs::remove_file(&exe_file).unwrap();
- res.expect("Failed to run program");
- }
- */
- Ok(())
-}
R fml/src/parser.rs => +0 -896
@@ 1,896 0,0 @@
-use std::mem::Discriminant as Discr;
-use std::ops::Range;
-
-use codespan_reporting as cs;
-use codespan_reporting::diagnostic::{Diagnostic, Label};
-use fnv::FnvHashMap;
-use logos::{Lexer, Logos};
-
-use crate::ast;
-use crate::*;
-
-fn eat_block_comment(lex: &mut Lexer<TokenKind>) -> String {
- let mut nest_depth = 1;
- while nest_depth != 0 {
- const DELIMITER_BYTES: usize = 2;
- let next_bit = &lex.remainder().get(..DELIMITER_BYTES);
- // Lexer::bump() works in bytes, not chars, so we have to track the
- // number of bytes we are stepping forward so we don't try to lex
- // the middle of a UTF-8 char.
- let bytes_to_advance = match next_bit {
- Some("/-") => {
- nest_depth += 1;
- DELIMITER_BYTES
- }
- Some("-/") => {
- nest_depth -= 1;
- DELIMITER_BYTES
- }
- Some(other) => other
- .chars()
- .next()
- .unwrap_or_else(|| panic!("Invalid UTF-8 in input file? This should probably never happen otherwise."))
- .len_utf8(),
- None => panic!("Unclosed block comment?"),
- };
- lex.bump(bytes_to_advance);
- }
- String::from("")
-}
-
-#[allow(missing_docs)]
-#[derive(Logos, Debug, PartialEq, Clone)]
-pub enum TokenKind {
- #[regex("[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().to_owned())]
- Ident(String),
- #[regex("true|false", |lex| lex.slice().parse())]
- Bool(bool),
- #[regex("[0-9][0-9_]*", |lex| lex.slice().parse())]
- Integer(i32),
-
- // Decl stuff
- #[token("fn")]
- Fn,
- #[token("type")]
- Type,
-
- // Keywords
- #[token("let")]
- Let,
- #[token("end")]
- End,
- #[token("struct")]
- Struct,
- #[token("const")]
- Const,
- #[token("enum")]
- Enum,
- #[token("sum")]
- Sum,
-
- // Punctuation
- #[token("(")]
- LParen,
- #[token(")")]
- RParen,
- #[token("{")]
- LBrace,
- #[token("}")]
- RBrace,
- #[token("[")]
- LBracket,
- #[token("]")]
- RBracket,
- #[token(",")]
- Comma,
- #[token(".")]
- Period,
- #[token(":")]
- Colon,
- #[token(";")]
- Semicolon,
- #[token("=")]
- Equals,
- #[token("@")]
- At,
- #[token("$")]
- Dollar,
-
- // We save comment strings so we can use this same
- // parser as a reformatter or such.
- #[regex(r"--.*\n", |lex| lex.slice().to_owned())]
- #[regex(r"/-.*", eat_block_comment)]
- Comment(String),
-
- #[error]
- #[regex(r"[ \t\n\f]+", logos::skip)]
- Error,
-}
-
-impl TokenKind {
- /// Shortcut for std::mem::discriminant()
- fn discr(&self) -> Discr<Self> {
- std::mem::discriminant(self)
- }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct Token {
- pub kind: TokenKind,
- pub span: Range<usize>,
-}
-
-impl Token {
- fn new(kind: TokenKind, span: Range<usize>) -> Self {
- Token { kind, span }
- }
-}
-
-use self::TokenKind as T;
-
-// This is not dead code but sometimes cargo thinks some of it fields are, since their usage is
-// cfg'd out in unit tests.
-#[allow(dead_code)]
-struct ErrorReporter {
- files: cs::files::SimpleFiles<String, String>,
- file_id: usize,
- config: cs::term::Config,
-}
-
-impl ErrorReporter {
- fn new(filename: &str, src: &str) -> Self {
- use codespan_reporting::files::SimpleFiles;
- let mut files = SimpleFiles::new();
- let file_id = files.add(filename.to_owned(), src.to_owned());
-
- Self {
- files,
- file_id,
- config: codespan_reporting::term::Config::default(),
- }
- }
-
- fn error(&self, _diag: &Diagnostic<usize>) -> ! {
- #[cfg(not(test))]
- {
- use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
- let writer = StandardStream::stderr(ColorChoice::Always);
- cs::term::emit(&mut writer.lock(), &self.config, &self.files, _diag)
- .expect("Could not print error message");
- }
- panic!("Error somewhere in parser")
- }
-}
-
-/// The core parser struct. It provides basic methods for
-/// manipulating the input stream, and the `parse()` method to
-/// try to drive the given input to completion.
-pub struct Parser<'input> {
- lex: std::iter::Peekable<logos::SpannedIter<'input, TokenKind>>,
- source: &'input str,
- err: ErrorReporter,
-}
-
-impl<'input> Parser<'input> {
- pub fn new(filename: &str, source: &'input str) -> Self {
- let lex = TokenKind::lexer(source).spanned().peekable();
- let err = ErrorReporter::new(filename, source);
- Parser { lex, source, err }
- }
-
- /// Read all its input and returns an Ast.
- ///
- /// Currently just panics on error.
- pub fn parse(&mut self) -> ast::Ast {
- let mut decls = vec![];
- while let Some(d) = self.parse_decl() {
- decls.push(d);
- }
- ast::Ast { decls }
- }
-
- /// Returns the next token, with span.
- fn next(&mut self) -> Option<Token> {
- let t = self.lex.next().map(|(tok, span)| Token::new(tok, span));
- match t {
- // Recurse to skip comments
- Some(Token {
- kind: T::Comment(_),
- ..
- }) => self.next(),
- _ => t,
- }
- }
-
- /// Peeks the next token, with span.
- fn peek(&mut self) -> Option<Token> {
- let t = self
- .lex
- .peek()
- .map(|(tok, span)| Token::new(tok.clone(), span.clone()));
- match t {
- // Skip comments
- Some(Token {
- kind: T::Comment(_),
- ..
- }) => {
- // This must be self.lex.next(), not just self.next().
- let _ = self.lex.next();
- self.peek()
- }
- _ => t,
- }
- }
-
- /// Takes a string describing what it expected, and a token that is what
- /// it actually got.
- fn error(&self, expected: &str, token_or_eof: Option<Token>) -> ! {
- let diag = if let Some(got) = token_or_eof {
- let msg = format!(
- "Parse error on token {:?} from str {}. Expected {}",
- got.kind,
- &self.source[got.span.clone()],
- expected
- );
- Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, got.span)])
- } else {
- let len = self.source.len();
- let msg = format!("Parse error on end of file. Expected {}", expected);
- Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, len..len)])
- };
- self.err.error(&diag);
- }
-
- /// Consume a token, we don't care what it is.
- /// Presumably because we've already peeked at it.
- fn drop(&mut self) {
- self.next();
- }
-
- /// Consume a token that doesn't return anything
- fn expect(&mut self, expected: TokenKind) {
- match self.next() {
- Some(t) if t.kind == expected => (),
- Some(t) => {
- let msg = format!(
- "Parse error on token {:?} from str {}. Expected token: {:?}",
- t.kind,
- &self.source[t.span.clone()],
- expected
- );
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, t.span)]);
-
- self.err.error(&diag);
- }
- None => {
- let msg = format!(
- "Parse error: Got end of input or malformed token. Expected token: {:?}",
- expected
- );
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, 0..0)]);
-
- self.err.error(&diag);
- }
- }
- }
-
- /// Returns whether the next token in the stream is what is expected.
- ///
- /// Basically, we can't (easily) pass in a strongly-typed enum discriminant
- /// and have only that checked, we have to pass the full enum.
- /// Ironically, this is exactly the sort of thing I want Garnet to be
- /// able to do nicely.
- ///
- /// ...double ironically, the above paragraph is incorrect as of 1.21,
- /// because std::mem::discriminant() is better than I thought.
- fn peek_is(&mut self, expected: Discr<TokenKind>) -> bool {
- if let Some(got) = self.peek() {
- std::mem::discriminant(&got.kind) == expected
- } else {
- false
- }
- }
-
- /// Returns whether the next token in the stream is what is expected,
- /// and consume it if so.
- ///
- /// I ended up seeing a lot of `if self.peek_is(thing) { self.expect(thing)`
- /// so maybe this helps.
- fn peek_expect(&mut self, expected: Discr<TokenKind>) -> bool {
- if self.peek_is(expected) {
- self.drop();
- true
- } else {
- false
- }
- }
-
- /// Consume an identifier and return its interned symbol.
- /// Note this returns a String, not a TypeInfo...
- fn expect_ident(&mut self) -> String {
- match self.next() {
- Some(Token {
- kind: T::Ident(s), ..
- }) => s.to_string(),
- Some(Token { kind, span }) => {
- let msg = format!("Parse error: got token {:?}. Expected identifier.", kind,);
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, span)]);
-
- self.err.error(&diag);
- }
- None => {
- let msg = format!(
- "Parse error: Got end of input or malformed token. Expected identifier",
- );
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, 0..0)]);
-
- self.err.error(&diag);
- }
- }
- }
-
- /// Consumes a number and returns it.
- fn expect_int(&mut self) -> i32 {
- match self.next() {
- Some(Token {
- kind: T::Integer(s),
- ..
- }) => s,
- Some(Token { kind, span }) => {
- let msg = format!("Parse error: got token {:?}. Expected integer.", kind,);
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, span)]);
-
- self.err.error(&diag);
- }
- None => {
- let msg = format!(
- "Parse error: Got end of input or malformed token. Expected integer.",
- );
- let diag = Diagnostic::error()
- .with_message(msg)
- .with_labels(vec![Label::primary(self.err.file_id, 0..0)]);
-
- self.err.error(&diag);
- }
- }
- }
-
- /// Returns None on EOF.
- fn parse_decl(&mut self) -> Option<ast::Decl> {
- match self.next() {
- Some(Token { kind: T::Fn, .. }) => Some(self.parse_fn()),
- Some(Token { kind: T::Type, .. }) => Some(self.parse_typedef()),
- Some(Token { kind: T::Const, .. }) => Some(self.parse_const()),
- Some(other) => self.error("start of decl", Some(other)),
- None => None,
- }
- }
-
- /// typedef = "type" ident "=" type
- fn parse_typedef(&mut self) -> ast::Decl {
- let name = self.expect_ident();
- let mut params = vec![];
- if self.peek_expect(T::LParen.discr()) {
- while !self.peek_is(T::RParen.discr()) {
- self.expect(T::At);
- let name = self.expect_ident();
- params.push(name);
- if !self.peek_expect(T::Comma.discr()) {
- break;
- }
- }
- self.expect(T::RParen);
- }
-
- self.expect(T::Equals);
- let ty = self.parse_type();
- ast::Decl::TypeDef { name, params, ty }
- }
-
- fn parse_const(&mut self) -> ast::Decl {
- let name = self.expect_ident();
- self.expect(T::Equals);
- let init = self
- .parse_expr(0)
- .expect("Expected expression after `const ... =`, did not get one");
- ast::Decl::ConstDef { name, init }
- }
-
- fn parse_fn(&mut self) -> ast::Decl {
- let name = self.expect_ident();
- let signature = ast::Signature {
- ..self.parse_fn_signature()
- };
- self.expect(T::Equals);
- let body = self.parse_exprs();
- self.expect(T::End);
- ast::Decl::Function {
- name,
- signature,
- body,
- }
- }
-
- /// signature = fn_args [":" typename]
- fn parse_fn_signature(&mut self) -> ast::Signature {
- let params = self.parse_fn_args();
- let rettype = self.parse_type();
- ast::Signature { params, rettype }
- }
-
- /// sig = ident ":" typename
- /// fn_args = "(" [sig {"," sig} [","]] ")"
- fn parse_fn_args(&mut self) -> Vec<(String, Type)> {
- let mut args = vec![];
- self.expect(T::LParen);
-
- while let Some((T::Ident(_i), _span)) = self.lex.peek() {
- let name = self.expect_ident();
- let tname = self.parse_type();
- args.push((name, tname));
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::RParen);
- args
- }
-
- fn parse_fn_type(&mut self) -> Type {
- let params = self.parse_type_list();
- let rettype = self.parse_type();
- Type::Func(params, Box::new(rettype))
- }
-
- /// type_list = "(" [type {"," type} [","] ")"
- fn parse_type_list(&mut self) -> Vec<Type> {
- let mut args = vec![];
- self.expect(T::LParen);
-
- while !self.peek_is(T::RParen.discr()) {
- let tname = self.parse_type();
- args.push(tname);
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::RParen);
- args
- }
-
- fn parse_tuple_type(&mut self) -> Type {
- let mut body = vec![];
- while !self.peek_is(T::RBrace.discr()) {
- let t = self.parse_type();
- body.push(t);
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::RBrace);
- Type::Named("Tuple".to_string(), body)
- }
-
- fn parse_exprs(&mut self) -> Vec<ast::ExprNode> {
- let mut exprs = vec![];
- let tok = self.peek();
- while let Some(e) = self.parse_expr(0) {
- // Hey and if we have a semicolon after an expr we can just eat it
- self.peek_expect(T::Semicolon.discr());
- exprs.push(e);
- }
- if exprs.is_empty() {
- self.error(
- "non-empty expression block, must have at least one value.",
- tok,
- );
- }
- exprs
- }
-
- /// Returns None if there is no valid expression,
- /// which usually means the end of a block or such.
- ///
- /// This departs from pure recursive descent and uses a Pratt
- /// parser to parse math expressions and such.
- fn parse_expr(&mut self, min_bp: usize) -> Option<ast::ExprNode> {
- let t = self.peek()?;
- let token = &t.kind;
- let mut lhs = match token {
- T::Integer(_) => ast::ExprNode::new(ast::Expr::int(self.expect_int())),
- T::Bool(b) => {
- self.drop();
- ast::ExprNode::new(ast::Expr::Lit {
- val: ast::Literal::Bool(*b),
- })
- }
- T::Ident(_) => {
- let ident = self.expect_ident();
- ast::ExprNode::new(ast::Expr::Var { name: ident })
- }
- T::Struct => self.parse_struct_literal(),
- T::Let => self.parse_let(),
- T::Fn => self.parse_lambda(),
- // Parenthesized expr's
- T::LParen => {
- self.drop();
- let lhs = self.parse_expr(0)?;
- self.expect(T::RParen);
- lhs
- }
- // Tuple or struct literal
- T::LBrace => self.parse_constructor(),
- // Array literal
- T::LBracket => self.parse_array_constructor(),
-
- // Something else not a valid expr
- _x => return None,
- };
- // Parse a postfix or infix expression with a given
- // binding power or greater.
- while let Some((op_token, _span)) = self.lex.peek().cloned() {
- // Is our token a postfix op?
- if let Some((l_bp, ())) = postfix_binding_power(&op_token) {
- if l_bp < min_bp {
- break;
- }
- lhs = match op_token {
- T::LParen => {
- let params = self.parse_function_args();
- ast::ExprNode::new(ast::Expr::Funcall { func: lhs, params })
- }
- // If we see `foo {bar}`
- // then parse it as `foo({bar})`
- // Thanks Lua!!!
- T::LBrace => {
- let params = self.parse_constructor();
- ast::ExprNode::new(ast::Expr::Funcall {
- func: lhs,
- params: vec![params],
- })
- }
- T::Period => {
- self.expect(T::Period);
- let struct_field_name = self.expect_ident();
- ast::ExprNode::new(ast::Expr::StructRef {
- e: lhs,
- name: struct_field_name,
- })
- }
- T::Dollar => {
- self.expect(T::Dollar);
- ast::ExprNode::new(ast::Expr::TypeUnwrap { e: lhs })
- }
- _ => return None,
- };
- continue;
- }
- /*
- // Is our token an infix op?
- if let Some((l_bp, r_bp)) = infix_binding_power(&op_token) {
- if l_bp < min_bp {
- break;
- }
- self.drop();
- let rhs = self.parse_expr(r_bp).unwrap();
- // Not all things that parse like binary operations
- // actually produce the "BinOp" expression type.
- lhs = match op_token {
- // x = y
- T::Equals => ast::Expr::Assign {
- lhs: Box::new(lhs),
- rhs: Box::new(rhs),
- },
- _ => {
- let bop = bop_for(&op_token).unwrap();
- ast::Expr::BinOp {
- op: bop,
- lhs: Box::new(lhs),
- rhs: Box::new(rhs),
- }
- }
- };
- continue;
- }
- */
- // None of the above, so we are done parsing the expr
- break;
- }
- Some(lhs)
- }
-
- fn parse_function_args(&mut self) -> Vec<ast::ExprNode> {
- let mut params = vec![];
- self.expect(T::LParen);
- while let Some(expr) = self.parse_expr(0) {
- params.push(expr);
- if !self.peek_is(T::Comma.discr()) {
- break;
- }
- self.expect(T::Comma);
- }
- self.expect(T::RParen);
- params
- }
-
- /// let = "let" ident ":" typename "=" expr
- fn parse_let(&mut self) -> ast::ExprNode {
- self.expect(T::Let);
- let varname = self.expect_ident();
- let typename = if self.peek_expect(T::Equals.discr()) {
- None
- } else {
- let t = Some(self.parse_type());
- self.expect(T::Equals);
- t
- };
- let init = self
- .parse_expr(0)
- .expect("Expected expression after `let ... =`, did not get one");
- ast::ExprNode::new(ast::Expr::Let {
- varname,
- typename,
- init,
- })
- }
-
- /// lambda = "fn" "(" ...args... ")" [":" typename] = {exprs} "end"
- fn parse_lambda(&mut self) -> ast::ExprNode {
- self.expect(T::Fn);
- let signature = self.parse_fn_signature();
- self.expect(T::Equals);
- let body = self.parse_exprs();
- self.expect(T::End);
- ast::ExprNode::new(ast::Expr::Lambda { signature, body })
- }
-
- fn parse_array_constructor(&mut self) -> ast::ExprNode {
- self.expect(T::LBracket);
- let mut body = vec![];
- while let Some(expr) = self.parse_expr(0) {
- body.push(expr);
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::RBracket);
- ast::ExprNode::new(ast::Expr::ArrayCtor { body })
- }
-
- /// If we see `.foo = bar` in our thing then it's a struct
- fn parse_constructor(&mut self) -> ast::ExprNode {
- self.expect(T::LBrace);
- if self.peek_is(T::Period.discr()) {
- self.parse_struct_literal()
- } else {
- self.parse_tuple_literal()
- }
- }
-
- /// struct constructor = "{" "." ident "=" expr {"," ...} "}"
- fn parse_struct_literal(&mut self) -> ast::ExprNode {
- let body = self.parse_struct_lit_fields();
- self.expect(T::RBrace);
- ast::ExprNode::new(ast::Expr::StructCtor { body })
- }
-
- /// tuple constructor = "{" [expr {"," expr} [","] "}"
- fn parse_tuple_literal(&mut self) -> ast::ExprNode {
- let mut body = vec![];
- while let Some(expr) = self.parse_expr(0) {
- body.push(expr);
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::RBrace);
- ast::ExprNode::new(ast::Expr::TupleCtor { body })
- }
-
- fn parse_struct_lit_fields(&mut self) -> FnvHashMap<String, ast::ExprNode> {
- let mut fields = FnvHashMap::default();
-
- loop {
- if self.peek_expect(T::Period.discr()) {
- let name = self.expect_ident();
- self.expect(T::Equals);
- let vl = self.parse_expr(0).unwrap();
- fields.insert(name, vl);
- } else {
- break;
- }
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- fields
- }
-
- fn parse_type(&mut self) -> Type {
- let t = self.next().unwrap_or_else(|| self.error("type", None));
- let mut ty = match t.kind {
- T::Ident(ref s) => {
- let type_params = if self.peek_is(T::LParen.discr()) {
- self.parse_type_list()
- } else {
- vec![]
- };
- Type::Named(s.clone(), type_params)
- }
- T::At => {
- let s = self.expect_ident();
- Type::Generic(s)
- }
- T::LBrace => {
- let tuptype = self.parse_tuple_type();
- tuptype
- }
- T::Fn => {
- let fntype = self.parse_fn_type();
- fntype
- }
- T::Struct => self.parse_struct_type(),
- T::Enum => self.parse_enum_type(),
- T::Sum => self.parse_sum_type(),
- // Apparently all our types are prefix, which is weird.
- // Fix this someday.
- _ => self.error("type", Some(t)),
- };
- // Array? (Or other postfix shit, this be gettin whack)
- while self.peek_is(T::LBracket.discr()) {
- self.expect(T::LBracket);
- let len = self.expect_int();
- assert!(len >= 0);
- self.expect(T::RBracket);
- ty = Type::Array(Box::new(ty), len as usize);
- }
- ty
- }
-
- /// isomorphic-ish with parse_type_list()
- fn parse_struct_type(&mut self) -> Type {
- let mut fields = FnvHashMap::default();
-
- loop {
- match self.lex.peek() {
- Some((T::Ident(_i), _span)) => {
- let name = self.expect_ident();
- self.expect(T::Colon);
- let vl = self.parse_type();
- fields.insert(name, vl);
- }
- _ => break,
- }
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::End);
- // Pull any @Foo types out of the structure's
- // declared types
- // To do this we have to recurse down into any
- // complex types THOSE may define (such as functions)
- // and pull out those types too.
- //
- // This feels kinda jank but appears to work
- let generic_names: Vec<_> = fields
- .iter()
- .map(|(_nm, ty)| ty.get_type_params())
- .flatten()
- .map(|ty| Type::Generic(ty))
- .collect();
- Type::Struct(fields, generic_names)
- }
-
- /// isomorphic with parse_type_list()
- fn parse_enum_type(&mut self) -> Type {
- let mut fields = vec![];
- while !self.peek_is(T::End.discr()) {
- let field = self.expect_ident();
- fields.push(field);
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::End);
- Type::Enum(fields)
- }
-
- /// isomorphic-ish with parse_type_list()
- fn parse_sum_type(&mut self) -> Type {
- let mut fields = FnvHashMap::default();
- while !self.peek_is(T::End.discr()) {
- let field = self.expect_ident();
- let ty = self.parse_type();
- fields.insert(field, ty);
-
- if self.peek_expect(T::Comma.discr()) {
- } else {
- break;
- }
- }
- self.expect(T::End);
- // Pull any @Foo types out of the structure's
- // declared types, again just like parse_struct_type above.
- let generic_names: Vec<_> = fields
- .iter()
- .map(|(_nm, ty)| ty.get_type_params())
- .flatten()
- .map(|ty| Type::Generic(ty))
- .collect();
- Type::Sum(fields, generic_names)
- }
-}
-
-/// Specifies binding power of postfix operators.
-///
-/// No idea if the precedences for these are sensible,
-/// I think they only actually relate with infix operators,
-/// not each other.
-fn postfix_binding_power(op: &TokenKind) -> Option<(usize, ())> {
- match op {
- // "$" is our "type unwrap" operator, which is weird but ok
- T::Dollar => Some((130, ())),
- // "(" opening function call args
- T::LParen => Some((120, ())),
- // "{" opening function call for single struct arg
- T::LBrace => Some((119, ())),
- // "." separating struct refs a la foo.bar
- T::Period => Some((110, ())),
- // "[" opening array dereference
- T::LBracket => Some((100, ())),
- _x => None,
- }
-}
-
-/*
-/// Specifies binding power of infix operators.
-/// The binding power on one side should always be trivially
-/// greater than the other, so there's never ambiguity.
-fn infix_binding_power(op: &TokenKind) -> Option<(usize, usize)> {
- // Right associations are slightly more powerful so we always produce
- // a deterministic tree.
- match op {
- T::Mul | T::Div | T::Mod => Some((100, 101)),
- T::Plus | T::Minus => Some((90, 91)),
- T::Lt | T::Gt | T::Lte | T::Gte => Some((80, 81)),
- T::Equal | T::NotEqual => Some((70, 71)),
- T::And => Some((60, 61)),
- // Logical xor has same precedence as or, I guess? It's sorta an odd duck.
- T::Or | T::Xor => Some((50, 51)),
- // Assignment
- T::Equals => Some((10, 11)),
-
- _ => None,
- }
-}
-*/
R fml/src/typeck.rs => +0 -851
@@ 1,851 0,0 @@
-use std::cell::RefCell;
-use std::rc::Rc;
-
-use fnv::{FnvHashMap, FnvHashSet};
-
-use crate::*;
-
-/// Type checking engine
-#[derive(Default)]
-struct Tck {
- /// Used to generate unique IDs
- id_counter: usize,
- /// Binding from type vars to what we know about the type
- vars: FnvHashMap<TypeId, TypeInfo>,
- /// What we know about the type of each node in the AST.
- types: FnvHashMap<ast::AstId, TypeId>,
-}
-
-impl Tck {
- /// Save the type variable associated with the given expr
- fn set_expr_type(&mut self, expr: &ast::ExprNode, ty: TypeId) {
- assert!(
- self.types.get(&expr.id).is_none(),
- "Redefining known type, not suuuuure if this is bad or not. Probably is though, since we should always be changing the meaning of an expr's associated type variable instead."
- );
- self.types.insert(expr.id, ty);
- }
-
- /// Panics if the expression's type is not set.
- fn get_expr_type(&mut self, expr: &ast::ExprNode) -> TypeId {
- *self.types.get(&expr.id).unwrap_or_else(|| {
- panic!(
- "Could not get type of expr with ID {:?}!\nExpr was {:?}",
- expr.id, expr
- )
- })
- }
-
- /// Create a new type term with whatever we have about its type
- pub fn insert(&mut self, info: TypeInfo) -> TypeId {
- // Generate a new ID for our type term
- self.id_counter += 1;
- let id = TypeId(self.id_counter);
- assert!(self.vars.get(&id).is_none(), "Can't happen");
- self.vars.insert(id, info);
- id
- }
-
- /// Create a new type term out of a known type, such as if we
- /// declare a var's type.
- pub fn insert_known(&mut self, t: &Type) -> TypeId {
- // Recursively insert all subtypes.
- let tinfo = match t {
- //Type::Primitive(_) => todo!(),
- Type::Enum(vals) => TypeInfo::Enum(vals.clone()),
- Type::Named(s, args) => {
- let new_args = args.iter().map(|t| self.insert_known(t)).collect();
- TypeInfo::Named(s.clone(), new_args)
- }
- Type::Func(args, rettype) => {
- let new_args = args.iter().map(|t| self.insert_known(t)).collect();
- let new_rettype = self.insert_known(rettype);
- TypeInfo::Func(new_args, new_rettype)
- }
- Type::Generic(s) => TypeInfo::TypeParam(s.to_string()),
- Type::Array(ty, len) => {
- let new_body = self.insert_known(&ty);
- TypeInfo::Array(new_body, *len)
- }
- // TODO: Generics?
- Type::Struct(body, _names) => {
- let new_body = body
- .iter()
- .map(|(nm, t)| (nm.clone(), self.insert_known(t)))
- .collect();
- TypeInfo::Struct(new_body)
- }
- // TODO: Generics?
- Type::Sum(body, _names) => {
- let new_body = body
- .iter()
- .map(|(nm, t)| (nm.clone(), self.insert_known(t)))
- .collect();
- TypeInfo::Sum(new_body)
- }
- };
- self.insert(tinfo)
- }
-
- /// Panics on invalid field name or not a struct type
- pub fn get_struct_field_type(
- &mut self,
- symtbl: &Symtbl,
- struct_type: TypeId,
- field_name: &str,
- ) -> TypeId {
- use TypeInfo::*;
- match self.vars[&struct_type].clone() {
- Ref(t) => self.get_struct_field_type(symtbl, t, field_name),
- Struct(body) => body.get(field_name).cloned().unwrap_or_else(|| {
- panic!(
- "Struct has no field {}, valid fields are: {:#?}",
- field_name, body
- )
- }),
- other => {
- panic!(
- "Tried to get struct field {} from non-struct type {:?}",
- field_name, other
- )
- }
- }
- }
-
- /// Make the types of two type terms equivalent (or produce an error if
- /// there is a conflict between them)
- pub fn unify(&mut self, symtbl: &Symtbl, a: TypeId, b: TypeId) -> Result<(), String> {
- println!("> Unifying {:?} with {:?}", self.vars[&a], self.vars[&b]);
- // If a == b then it's a little weird but shoooooould be fine
- // as long as we don't get any mutual recursion or self-recursion
- // involved
- // Per MBones:
- // Yes it makes sense. The unifier is tasked with solving literally whatever equations you throw at it, and this is an important edge case to check for (to avoid accidentally making cyclic datastructures). (The correct action from the unifier is to succeed with no updates, since it's already equal to itself)
- if a == b {
- return Ok(());
- }
- use TypeInfo::*;
- match (self.vars[&a].clone(), self.vars[&b].clone()) {
- // Follow any references
- (Ref(a), _) => self.unify(symtbl, a, b),
- (_, Ref(b)) => self.unify(symtbl, a, b),
-
- // When we don't know anything about either term, assume that
- // they match and make the one we know nothing about reference the
- // one we may know something about
- (Unknown, _) => {
- self.vars.insert(a, TypeInfo::Ref(b));
- Ok(())
- }
- (_, Unknown) => {
- self.vars.insert(b, TypeInfo::Ref(a));
- Ok(())
- }
-
- // For type constructors, if their names are the same we try
- // to unify their args
- (Named(n1, args1), Named(n2, args2)) => {
- if n1 == n2 && args1.len() == args2.len() {
- for (arg1, arg2) in args1.iter().zip(args2.iter()) {
- self.unify(symtbl, *arg1, *arg2)?;
- }
- Ok(())
- } else {
- panic!(
- "Mismatch between types {}({:?}) and {}({:?})",
- n1, args1, n2, args2
- )
- }
- }
- // When unifying complex types, we must check their sub-types. This
- // can be trivially implemented for tuples, sum types, etc.
- (Func(a_i, a_o), Func(b_i, b_o)) => {
- if a_i.len() != b_i.len() {
- return Err(String::from("Arg lists are not same length"));
- }
- for (arg_a, arg_b) in a_i.iter().zip(b_i) {
- self.unify(symtbl, *arg_a, arg_b)?;
- }
- self.unify(symtbl, a_o, b_o)
- }
- (Struct(body1), Struct(body2)) => {
- for (nm, t1) in body1.iter() {
- let t2 = body2[nm];
- self.unify(symtbl, *t1, t2)?;
- }
- // Now we just do it again the other way around
- // which is a dumb but effective way of making sure
- // struct2 doesn't have any fields that struct1 doesn't.
- for (nm, t2) in body2.iter() {
- let t1 = body1[nm];
- self.unify(symtbl, t1, *t2)?;
- }
- Ok(())
- }
- (Sum(body1), Sum(body2)) => {
- // Same as struct types
- for (nm, t1) in body1.iter() {
- let t2 = body2[nm];
- self.unify(symtbl, *t1, t2)?;
- }
- for (nm, t2) in body2.iter() {
- let t1 = body1[nm];
- self.unify(symtbl, t1, *t2)?;
- }
- Ok(())
- }
- (Array(body1, len1), Array(body2, len2)) if len1 == len2 => {
- self.unify(symtbl, body1, body2)
- }
- // For declared type parameters like @T they match if their names match.
- // TODO: And if they have been declared? Not sure we can ever get to
- // here if that's the case.
- (TypeParam(s1), TypeParam(s2)) if s1 == s2 => Ok(()),
- // If no previous attempts to unify were successful, raise an error
- (a, b) => {
- self.print_types();
- Err(format!("Conflict between {:?} and {:?}", a, b))
- }
- }
- }
-
- /// Attempt to reconstruct a concrete type from the given type term ID. This
- /// may fail if we don't yet have enough information to figure out what the
- /// type is.
- pub fn reconstruct(&self, id: TypeId) -> Result<Type, String> {
- use TypeInfo::*;
- match &self.vars[&id] {
- Unknown => Err(format!("Cannot infer type for type ID {:?}", id)),
- Ref(id) => self.reconstruct(*id),
- Enum(ts) => Ok(Type::Enum(ts.clone())),
- Named(s, args) => {
- let arg_types: Result<Vec<_>, _> =
- args.iter().map(|x| self.reconstruct(*x)).collect();
- Ok(Type::Named(s.clone(), arg_types?))
- }
- Func(args, rettype) => {
- let real_args: Result<Vec<Type>, String> =
- args.into_iter().map(|arg| self.reconstruct(*arg)).collect();
- Ok(Type::Func(
- real_args?,
- Box::new(self.reconstruct(*rettype)?),
- ))
- }
- TypeParam(name) => Ok(Type::Generic(name.to_owned())),
- Struct(body) => {
- let real_body: Result<FnvHashMap<_, _>, String> = body
- .iter()
- .map(|(nm, t)| {
- let new_t = self.reconstruct(*t)?;
- Ok((nm.clone(), new_t))
- })
- .collect();
- // TODO: The empty params here feels suspicious, verify.
- let params = vec![];
- Ok(Type::Struct(real_body?, params))
- }
- Array(ty, len) => {
- let real_body = self.reconstruct(*ty)?;
- Ok(Type::Array(Box::new(real_body), *len))
- }
- Sum(body) => {
- let real_body: Result<FnvHashMap<_, _>, String> = body
- .iter()
- .map(|(nm, t)| {
- let new_t = self.reconstruct(*t)?;
- Ok((nm.clone(), new_t))
- })
- .collect();
- let params = vec![];
- Ok(Type::Sum(real_body?, params))
- }
- }
- }
-
- fn print_types(&self) {
- let mut vars_report: Vec<_> = self.vars.iter().collect();
- vars_report.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
- for (k, v) in vars_report.iter() {
- print!(" ${} => {:?}\n", k.0, v);
- }
- }
-
- /// Kinda the opposite of reconstruction; takes a concrete type
- /// and generates a new type with unknown's (type variables) for the generic types (type
- /// parameters)
- ///
- /// The named_types is a *local* binding of generic type names to type variables.
- /// We use this to make multiple mentions of the same type name, such as
- /// `id :: T -> T`, all refer to the same type variable.
- /// Feels Weird but it works.
- ///
- /// This has to actually be an empty hashtable on the first instantitaion
- /// instead of the symtbl, since the symtbl is full of type parameter names from the
- /// enclosing function and those are what we explicitly want to get away from.
- fn instantiate(&mut self, t: &Type, known_types: Option<FnvHashMap<String, TypeId>>) -> TypeId {
- fn helper(tck: &mut Tck, named_types: &mut FnvHashMap<String, TypeId>, t: &Type) -> TypeId {
- let typeinfo = match t {
- //Type::Primitive(_) => todo!(),
- Type::Enum(vals) => TypeInfo::Enum(vals.clone()),
- Type::Named(s, args) => {
- let inst_args: Vec<_> =
- args.iter().map(|t| helper(tck, named_types, t)).collect();
- TypeInfo::Named(s.clone(), inst_args)
- }
- Type::Generic(s) => {
- // If we know this is is a particular generic, match wiht it
- if let Some(ty) = named_types.get(s) {
- TypeInfo::Ref(*ty)
- } else {
- panic!("Referred to unknown generic named {}", s);
- }
- }
- Type::Func(args, rettype) => {
- let inst_args: Vec<_> =
- args.iter().map(|t| helper(tck, named_types, t)).collect();
- let inst_ret = helper(tck, named_types, rettype);
- TypeInfo::Func(inst_args, inst_ret)
- }
- Type::Struct(body, _names) => {
- let inst_body = body
- .iter()
- .map(|(nm, ty)| (nm.clone(), helper(tck, named_types, ty)))
- .collect();
- TypeInfo::Struct(inst_body)
- }
- Type::Array(ty, len) => {
- let inst_ty = helper(tck, named_types, &*ty);
- TypeInfo::Array(inst_ty, *len)
- }
- Type::Sum(body, _names) => {
- let inst_body = body
- .iter()
- .map(|(nm, ty)| (nm.clone(), helper(tck, named_types, ty)))
- .collect();
- TypeInfo::Sum(inst_body)
- }
- };
- tck.insert(typeinfo)
- }
- // We have to pluck any unknowns out of the toplevel type and create
- // new type vars for them.
- // We don't worry about binding those vars or such, that is what unification
- // will do later.
- // We do have to take any of those unknowns that are actually
- // known and preserve that knowledge though.
- let known_types = &mut known_types.unwrap_or_else(Default::default);
- let type_params = t.get_type_params();
- // Probably a cleaner way to do this but oh well.
- // We to through the type params, and if any of them
- // are unknown we put a new TypeInfo::Unknown in for them.
- for param in type_params {
- known_types
- .entry(param)
- .or_insert_with(|| self.insert(TypeInfo::Unknown));
- }
- helper(self, known_types, t)
- }
-}
-
-#[derive(Clone, Default)]
-struct ScopeFrame {
- symbols: FnvHashMap<String, TypeId>,
- types: FnvHashMap<String, Type>,
-}
-
-/// Basic symbol table that maps names to type ID's
-/// and manages scope.
-/// Looks ugly, works well.
-#[derive(Clone)]
-struct Symtbl {
- frames: Rc<RefCell<Vec<ScopeFrame>>>,
-}
-
-impl Default for Symtbl {
- /// We start with an empty toplevel scope existing.
- fn default() -> Self {
- Self {
- frames: Rc::new(RefCell::new(vec![ScopeFrame::default()])),
- }
- }
-}
-
-pub struct ScopeGuard {
- scope: Symtbl,
-}
-
-impl Drop for ScopeGuard {
- fn drop(&mut self) {
- self.scope
- .frames
- .borrow_mut()
- .pop()
- .expect("Scope stack underflow");
- }
-}
-
-impl Symtbl {
- fn push_scope(&self) -> ScopeGuard {
- self.frames.borrow_mut().push(ScopeFrame::default());
- ScopeGuard {
- scope: self.clone(),
- }
- }
-
- fn add_var(&self, var: impl AsRef<str>, ty: TypeId) {
- self.frames
- .borrow_mut()
- .last_mut()
- .expect("Scope stack underflow")
- .symbols
- .insert(var.as_ref().to_owned(), ty);
- }
-
- /// Checks whether the var exists in the currently alive scopes
- fn get_var_binding(&self, var: impl AsRef<str>) -> Option<TypeId> {
- for scope in self.frames.borrow().iter().rev() {
- let v = scope.symbols.get(var.as_ref());
- if v.is_some() {
- return v.cloned();
- }
- }
- return None;
- }
-
- fn add_type(&self, name: impl AsRef<str>, ty: &Type) {
- self.frames
- .borrow_mut()
- .last_mut()
- .expect("Scope stack underflow")
- .types
- .insert(name.as_ref().to_owned(), ty.to_owned());
- }
-
- fn get_type(&self, ty: impl AsRef<str>) -> Option<Type> {
- for scope in self.frames.borrow().iter().rev() {
- let v = scope.types.get(ty.as_ref());
- if v.is_some() {
- return v.cloned();
- }
- }
- return None;
- }
-}
-
-fn infer_lit(lit: &ast::Literal) -> TypeInfo {
- match lit {
- ast::Literal::Integer(_) => TypeInfo::Named("I32".to_string(), vec![]),
- ast::Literal::Bool(_) => TypeInfo::Named("Bool".to_string(), vec![]),
- ast::Literal::EnumLit(nm, _) => TypeInfo::Named(nm.to_string(), vec![]),
- }
-}
-fn typecheck_func_body(
- name: Option<&str>,
- tck: &mut Tck,
- symtbl: &Symtbl,
- signature: &ast::Signature,
- body: &[ast::ExprNode],
-) -> Result<TypeId, String> {
- println!(
- "Typechecking function {:?} with signature {:?}",
- name, signature
- );
- // Insert info about the function signature
- let mut params = vec![];
- for (_paramname, paramtype) in &signature.params {
- let p = tck.insert_known(paramtype);
- params.push(p);
- }
- let rettype = tck.insert_known(&signature.rettype);
- println!(
- "signature is: {:?}",
- TypeInfo::Func(params.clone(), rettype.clone())
- );
- let f = tck.insert(TypeInfo::Func(params, rettype));
-
- // If we have a name (ie, are not a lambda), bind the function's type to its name
- // A gensym might make this easier/nicer someday, but this works for now.
- //
- // Note we do this *before* pushing the scope and checking its body,
- // so this will add the function's name to the outer scope.
- if let Some(n) = name {
- symtbl.add_var(n, f);
- }
-
- // Add params to function's scope
- let _guard = symtbl.push_scope();
- for (paramname, paramtype) in &signature.params {
- let p = tck.insert_known(paramtype);
- symtbl.add_var(paramname, p);
- }
-
- // Typecheck body
- for expr in body {
- typecheck_expr(tck, symtbl, expr)?;
- // TODO here: unit type for expressions and such
- }
- let last_expr = body.last().expect("empty body, aieeee");
- let last_expr_type = tck.get_expr_type(last_expr);
- println!(
- "Unifying last expr...? Is type {:?}, we want {:?}",
- last_expr_type, rettype
- );
- tck.unify(symtbl, last_expr_type, rettype)?;
-
- for expr in body {
- let t = tck.get_expr_type(expr);
- tck.reconstruct(t).unwrap();
- }
-
- println!(
- "Typechecked function {}, types are",
- name.unwrap_or("(lambda)")
- );
- tck.print_types();
- Ok(f)
-}
-
-fn typecheck_expr(tck: &mut Tck, symtbl: &Symtbl, expr: &ast::ExprNode) -> Result<TypeId, String> {
- use ast::Expr::*;
- match &*expr.node {
- Lit { val } => {
- let lit_type = infer_lit(val);
- let typeid = tck.insert(lit_type);
- tck.set_expr_type(expr, typeid);
- Ok(typeid)
- }
- Var { name } => {
- let ty = symtbl
- .get_var_binding(name)
- .unwrap_or_else(|| panic!("unbound var: {:?}", name));
- tck.set_expr_type(expr, ty);
- Ok(ty)
- }
- Let {
- varname,
- typename,
- init,
- } => {
- typecheck_expr(tck, symtbl, init)?;
- let init_expr_type = tck.get_expr_type(init);
- // Does our let decl have a type attached to it?
- let var_type = if let Some(t) = typename {
- tck.insert_known(t)
- } else {
- tck.insert(TypeInfo::Unknown)
- };
- tck.unify(symtbl, init_expr_type, var_type)?;
-
- // A `let` expr returns unit, not the type of `init`
- let unit_type = tck.insert(TypeInfo::Named("Tuple".to_owned(), vec![]));
- tck.set_expr_type(expr, unit_type);
-
- symtbl.add_var(varname, var_type);
- Ok(var_type)
- }
- Lambda { signature, body } => {
- let t = typecheck_func_body(None, tck, symtbl, signature, body)?;
- tck.set_expr_type(expr, t);
- Ok(t)
- }
- StructRef { e, name } => {
- typecheck_expr(tck, symtbl, e)?;
- let struct_type = tck.get_expr_type(e);
- println!("Heckin struct... Type of {:?} is {:?}", e, struct_type);
- // struct_type is the type of the struct... but the
- // type of this structref expr is the type of the *field in the struct*.
- let struct_field_type = tck.get_struct_field_type(symtbl, struct_type, name);
- println!(
- "Heckin struct ref... Type of {:?}.{} is {:?}",
- e, name, struct_field_type
- );
- tck.set_expr_type(expr, struct_field_type);
-
- match tck.reconstruct(struct_type)? {
- Type::Struct(body, _names) => Ok(tck.insert_known(&body[name])),
- Type::Named(s, _args) => {
- let hopefully_a_struct = symtbl.get_type(s).unwrap();
- match hopefully_a_struct {
- Type::Struct(body, _names) => Ok(tck.insert_known(&body[name])),
- _other => Err(format!("Yeah I know this is wrong bite me")),
- }
- }
- other => Err(format!(
- "Tried to get field named {} but it is an {:?}, not a struct",
- name, other
- )),
- }
- }
- TupleCtor { body } => {
- let body_types: Result<Vec<_>, _> = body
- .iter()
- .map(|expr| typecheck_expr(tck, symtbl, expr))
- .collect();
- let body_types = body_types?;
- let tuple_type = TypeInfo::Named("Tuple".to_string(), body_types);
- let typeid = tck.insert(tuple_type);
- tck.set_expr_type(expr, typeid);
- Ok(typeid)
- }
- Funcall { func, params } => {
- // Oh, defined generics are "easy".
- // Each time I call a function I create new type
- // vars for its generic args.
- // Apparently that is the "instantiation".
-
- let func_type = typecheck_expr(tck, symtbl, func)?;
- // We know this will work because we require full function signatures
- // on our functions.
- let actual_func_type = tck.reconstruct(func_type)?;
- match &actual_func_type {
- Type::Func(_args, _rettype) => {
- println!("Calling function {:?} is {:?}", func, actual_func_type);
- // So when we call a function we need to know what its
- // type params are. Then we bind those type parameters
- // to things.
- }
- _ => panic!("Tried to call something not a function"),
- }
-
- // Synthesize what we know about the function
- // from the call.
- let mut params_list = vec![];
- for param in params {
- typecheck_expr(tck, symtbl, param)?;
- let param_type = tck.get_expr_type(param);
- params_list.push(param_type);
- }
- // We don't know what the expected return type of the function call
- // is yet; we make a type var that will get resolved when the enclosing
- // expression is.
- let rettype_var = tck.insert(TypeInfo::Unknown);
- let funcall_var = tck.insert(TypeInfo::Func(params_list.clone(), rettype_var));
-
- // Now I guess this is where we make a copy of the function
- // with new generic types.
- // Is this "instantiation"???
- // Yes it is. Differentiate "type parameters", which are the
- // types a function takes as input (our `Generic` or `TypeParam`
- // things I suppose), from "type variables" which are the TypeId
- // we have to solve for.
- //
- // So we go through the generics the function declares and create
- // new type vars for each of them.
- let heck = tck.instantiate(&actual_func_type, None);
- tck.unify(symtbl, heck, funcall_var)?;
-
- tck.set_expr_type(expr, rettype_var);
- Ok(rettype_var)
- }
-
- StructCtor { body } => {
- let body_types: Result<FnvHashMap<_, _>, _> = body
- .iter()
- .map(|(name, expr)| {
- // ? in map doesn't work too well...
- println!("Checking field {} expr {:?}", name, expr);
- match typecheck_expr(tck, symtbl, expr) {
- Ok(t) => Ok((name.to_string(), t)),
- Err(s) => Err(s),
- }
- })
- .collect();
- println!("Typechecking struct ctor: {:?}", body_types);
- let body_types = body_types?;
- let struct_type = TypeInfo::Struct(body_types);
- let typeid = tck.insert(struct_type);
- tck.set_expr_type(expr, typeid);
- Ok(typeid)
- }
- TypeCtor {
- name,
- type_params,
- body,
- } => {
- let named_type = symtbl.get_type(name).expect("Unknown type constructor");
- println!("Got type named {}: is {:?}", name, named_type);
- // Ok if we have declared type params we gotta instantiate them
- // to match the type's generics.
- //let type_param_names = named_type.get_generic_args();
- let type_param_names = named_type.get_type_params();
- assert_eq!(
- type_params.len(),
- type_param_names.len(),
- "Type '{}' expected params {:?} but got params {:?}",
- name,
- type_param_names,
- type_params
- );
- let tid = tck.instantiate(&named_type, None);
- println!("Instantiated {:?} into {:?}", named_type, tid);
-
- //let tid = tck.insert_known(&named_type);
- let body_type = typecheck_expr(tck, symtbl, body)?;
- println!("Expected type is {:?}, body type is {:?}", tid, body_type);
- tck.unify(symtbl, tid, body_type)?;
- println!("Done unifying type ctor");
- // The type the expression returns
- let constructed_type =
- tck.insert_known(&Type::Named(name.clone(), type_params.clone()));
- tck.set_expr_type(expr, constructed_type);
- Ok(constructed_type)
- }
- TypeUnwrap { e } => {
- let mut body_type = typecheck_expr(tck, symtbl, e)?;
- loop {
- // I guess we follow TypeInfo references the stupid way?
- // We don't have a convenient place to recurse for this,
- // apparently, which is already a Smell but let's see
- // where this takes us.
- let well_heck = tck.vars[&body_type].clone();
- match well_heck.clone() {
- TypeInfo::Named(nm, params) => {
- println!("Unwrapping type {}{:?}", nm, params);
- let t = symtbl
- .get_type(&nm)
- .expect("Named type doesn't name anything?!!");
- println!("Inner type is {:?}", t);
- // t is a concrete Type, not a TypeInfo that may have
- // unknowns, so we instantiate it to sub out any of its
- // type params with new unknowns.
- //
- // But then we have to bind those type params to
- // what we *know* about the type already...
- let type_param_names = t.collect_generic_names();
- let known_type_params = type_param_names
- .iter()
- .cloned()
- .zip(params.iter().cloned())
- .collect();
- let inst_t = tck.instantiate(&t, Some(known_type_params));
- //let heckin_hecker = tck.insert(well_heck);
- //tck.unify(symtbl, inst_t, heckin_hecker)?;
- tck.set_expr_type(expr, inst_t);
- return Ok(inst_t);
- }
- TypeInfo::Ref(other) => {
- body_type = other;
- // and loop to try again
- }
- other => panic!("Cannot unwrap non-named type {:?}", other),
- }
- }
- }
- SumCtor {
- name,
- variant,
- body,
- } => {
- let named_type = symtbl.get_type(name).expect("Unknown sum type constructor");
- /*
- let body_type = typecheck_expr(tck, symtbl, body)?;
- let well_heck = tck.vars[&body_type].clone();
- match well_heck.clone() {
- Type::Sum(sum_body, _generics) => {
- todo!()
- }
- */
-
- // This might be wrong, we can probably do it the other way around
- // like we do with TypeUnwrap: start by checking the inner expr type and make
- // sure it matches what we expect. Generics might require that.
- //
- // TODO: Generics
- match named_type.clone() {
- Type::Sum(sum_body, _generics) => {
- let variant_type = &sum_body[variant];
- let variant_typeid = tck.insert_known(variant_type);
- let body_type = typecheck_expr(tck, symtbl, body)?;
- tck.unify(symtbl, variant_typeid, body_type)?;
-
- // The expr is the type we expect, our return type is the
- // sum type we conjure up
- // TODO: Might be easier to have our compiler generate
- // the TypeCtor for it?
- let rettype = tck.insert_known(&Type::Named(name.clone(), vec![]));
- tck.set_expr_type(expr, rettype);
- Ok(rettype)
- }
- _ => unreachable!("This code is compiler generated, should never happen!"),
- }
- }
- ArrayCtor { body } => {
- let len = body.len();
- // So if the body has len 0 we can't know what type it is.
- // So we create a new unknown and then try unifying it with
- // all the expressions in the body.
- let body_type = tck.insert(TypeInfo::Unknown);
- for expr in body {
- let expr_type = typecheck_expr(tck, symtbl, expr)?;
- tck.unify(symtbl, body_type, expr_type)?;
- }
- let arr_type = tck.insert(TypeInfo::Array(body_type, len));
- tck.set_expr_type(expr, arr_type);
- Ok(arr_type)
- }
- ArrayRef { e, idx } => todo!(),
- }
-}
-
-/// From example code:
-/// "In reality, the most common approach will be to walk your AST, assigning type
-/// terms to each of your nodes with whatever information you have available. You
-/// will also need to call `engine.unify(x, y)` when you know two nodes have the
-/// same type, such as in the statement `x = y;`."
-pub fn typecheck(ast: &ast::Ast) {
- let tck = &mut Tck::default();
- let symtbl = &mut Symtbl::default();
- for decl in &ast.decls {
- use ast::Decl::*;
-
- match decl {
- Function {
- name,
- signature,
- body,
- } => {
- let t = typecheck_func_body(Some(name), tck, symtbl, signature, body);
- t.unwrap_or_else(|e| {
- println!("Error, type context is:");
- tck.print_types();
- panic!("Error while typechecking function {}:\n{}", name, e)
- });
- }
- TypeDef { name, params, ty } => {
- // Make sure that there are no unbound generics in the typedef
- // that aren't mentioned in the params.
- let generic_names: FnvHashSet<String> =
- ty.collect_generic_names().into_iter().collect();
- let param_names: FnvHashSet<String> = params.iter().cloned().collect();
- let difference: Vec<_> = generic_names
- .symmetric_difference(¶m_names)
- // gramble gramble &String
- .map(|s| s.as_str())
- .collect();
- if difference.len() != 0 {
- let differences = difference.join(", ");
- panic!("Error in typedef {}: Type params do not match generics mentioned in body. Unmatched types: {}", name, differences);
- }
-
- // Remember that we know about a type with this name
- symtbl.add_type(name, ty)
- }
- ConstDef { name, init } => {
- // The init expression is typechecked in its own
- // scope, since it may theoretically be a `let` or
- // something that introduces new names inside it.
- let init_type = {
- let _guard = symtbl.push_scope();
- let t = typecheck_expr(tck, symtbl, init).unwrap();
- t
- };
- println!("Typechecked const {}, type is {:?}", name, init_type);
- symtbl.add_var(name, init_type);
- }
- }
- }
- // Print out toplevel symbols
- for (name, id) in &symtbl.frames.borrow().last().unwrap().symbols {
- println!("value {} type is {:?}", name, tck.reconstruct(*id));
- }
-}
R fml/tests/main.rs => +0 -48
@@ 1,48 0,0 @@
-//! Test suite that typechecks reference programs from `tests/programs/`
-//! and checks their output.
-//!
-//! Uses the `lang_tester` crate, which is a little wobbly in places,
-//! but the best I can find.
-
-use std::{fs::read_to_string, process::Command};
-
-use lang_tester::LangTester;
-
-static COMMENT_PREFIX: &str = "--";
-
-fn main() {
- // We just typecheck things and see if they pass
- LangTester::new()
- .test_dir("tests/programs/")
- // Only use files named `*.gt` as test files.
- .test_file_filter(|p| {
- p.extension()
- .map(std::ffi::OsStr::to_str)
- .unwrap_or(Some(""))
- .unwrap()
- == "gt"
- })
- // Extract the first sequence of commented line(s) as the tests.
- .test_extract(|p| {
- read_to_string(p)
- .unwrap()
- .lines()
- // Skip non-commented lines at the start of the file.
- .skip_while(|l| !l.starts_with(COMMENT_PREFIX))
- // Extract consecutive commented lines.
- .take_while(|l| l.starts_with(COMMENT_PREFIX))
- // Strip the initial "--" from commented lines.
- .map(|l| &l[COMMENT_PREFIX.len()..])
- .collect::<Vec<_>>()
- .join("\n")
- })
- // We have one test commands:
- // * `check`: runs garnetc.
- .test_cmds(move |p| {
- // Test command 1: check `x.gt`
- let mut check = Command::new("cargo");
- check.args(&["run", "--", p.to_str().unwrap()]);
- vec![("Check", check)]
- })
- .run();
-}
R fml/tests/programs/test1.gt => +0 -41
@@ 1,41 0,0 @@
--- Check:
--- status: success
-
-fn foo(x I32) I32 =
- let y I32 = x
- y
-end
-
-fn bar() I32 =
- 12
-end
-
-fn baz(x Bool) Bool =
- x
-end
-
-/-
-fn invalid1(x I32) Bool =
- let y Bool = x
- y
-end
-
-fn invalid2(x I32) Bool =
- let y I32 = x
- y
-end
--/
-
-fn identity(i @T) @T =
- i
-end
-
-fn main() I32 =
- --let x I32 = foo(12)
- --let y Bool = baz(true)
- --let a I32 = identity(1)
- --let b Bool = identity2(true)
- --let c Bool = identity(false)
- --let d I32 = identity2(12)
- 12
-end
R fml/tests/programs/test2.gt => +0 -13
@@ 1,13 0,0 @@
--- Check:
--- status: success
-
-fn identity(i @T) @T =
- let y @T = i
- y
-end
-
-fn main() I32 =
- let a I32 = identity(1)
- let c Bool = identity(false)
- 3
-end
R fml/tests/programs/test_array1.gt => +0 -17
@@ 1,17 0,0 @@
--- Check:
--- status: success
-
-fn identity(i @T) @T =
- let y @T = i
- y
-end
-
-fn foo(i @T[3]) @T[3] =
- i
-end
-
-fn main() I32 =
- let a I32[4] = identity([1, 2, 3, 4])
- let b = foo([1, 2, 3])
- 3
-end
R fml/tests/programs/test_array2.gt => +0 -7
@@ 1,7 0,0 @@
--- Check:
--- status: error
-
-fn main() I32 =
- let a I32[5] = [1, 2, 3, 4]
- 3
-end
R fml/tests/programs/test_array3.gt => +0 -7
@@ 1,7 0,0 @@
--- Check:
--- status: success
-
-fn main() I32 =
- let a I32[3][3] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
- 3
-end
R fml/tests/programs/test_enum1.gt => +0 -18
@@ 1,18 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = enum
- X, Y, Z,
-end
-
-fn thing(i Foo) Foo =
- i
-end
-
-
-fn main() I32 =
- let x Foo = Foo.X
- let y = thing(Foo.Y)
- 3
-end
R fml/tests/programs/test_failure.gt => +0 -33
@@ 1,33 0,0 @@
--- Check:
--- status: error
-
-fn foo(x I32) I32 =
- let y I32 = x
- y
-end
-
-fn bar() I32 =
- 12
-end
-
-fn baz(x Bool) Bool =
- x
-end
-
-fn identity(i @T) @T =
- i
-end
-
-fn identity2(i @X) @X =
- identity(i)
-end
-
-fn main() I32 =
- --let x I32 = foo(12)
- --let y Bool = baz(true)
- let a I32 = identity(1)
- let b Bool = identity2(true)
- let c Bool = identity(false)
- let d Bool = identity2(12)
- 3
-end
R fml/tests/programs/test_forever1.gt => +0 -13
@@ 1,13 0,0 @@
--- Check:
--- status: success
-
--- Who doesn't like infinite recursion?
--- Actually I'm not sure what this should do.
--- ...According to OCaml and Rust, the *type checking* is fine.
-fn zoom(i @T) @T =
- zoom(i)
-end
-
-fn main() I32 =
- 3
-end
R fml/tests/programs/test_forever2.gt => +0 -12
@@ 1,12 0,0 @@
--- Check:
--- status: success
-
-
--- ...Seems legit I guess?
-fn zoom(i I32) I32 =
- zoom(i)
-end
-
-fn main() I32 =
- 3
-end
R fml/tests/programs/test_lambda1.gt => +0 -9
@@ 1,9 0,0 @@
--- Check:
--- status: success
-
-
-fn main() I32 =
- let f fn(Bool) Bool = fn(val Bool) Bool = val end
- let a Bool = f(true)
- 3
-end
R fml/tests/programs/test_lambda2.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: success
-
-
-fn apply_bool(f fn(Bool) Bool, arg Bool) Bool =
- f(arg)
-end
-
-
-fn main() I32 =
- let f fn(Bool) Bool = fn(val Bool) Bool = val end
- let a Bool = apply_bool(f, true)
- 3
-end
R fml/tests/programs/test_lambda3.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: success
-
-
-fn apply(f fn(@T) @T, arg @T) @T =
- f(arg)
-end
-
-
-fn main() I32 =
- let f fn(Bool) Bool = fn(val Bool) Bool = val end
- let a Bool = apply(f, true)
- 3
-end
R fml/tests/programs/test_lambda4.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: success
-
-
-fn apply(f fn(@In) @Out, arg @In) @Out =
- f(arg)
-end
-
-
-fn main() I32 =
- let f1 fn(Bool) Bool = fn(val Bool) Bool = val end
- let f2 fn(Bool) I32 = fn(val Bool) I32 = 4 end
- let a Bool = apply(f1, true)
- let b I32 = apply(f2, true)
- 3
-end
R fml/tests/programs/test_lambda5.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: error
-
-
-fn apply(f fn(@In) @Out, arg @In) @Out =
- f(arg)
-end
-
-
-fn main() I32 =
- let f1 fn(Bool) Bool = fn(val Bool) Bool = val end
- let a I32 = apply(f1, true)
- 3
-end
R fml/tests/programs/test_let1.gt => +0 -28
@@ 1,28 0,0 @@
--- Check:
--- status: success
-
-
-type Foo(@Thing) = struct
- a: @Thing,
- b: Bool
-end
-
-fn id(x @T) @T =
- x
-end
-
-
-fn main() I32 =
- let f1 = id(Foo {
- .a = 3,
- .b = false
- })
-
- let f2 = id(Foo {
- .a = true,
- .b = false
- })
-
- let f3 = f1
- 3
-end
R fml/tests/programs/test_module1.gt => +0 -29
@@ 1,29 0,0 @@
--- Check:
--- status: success
-
--- A hasher that only accepts basic integers and always returns an
--- integer. Implements a particular hash algorithm, with optional
--- state in it.
--- Analogous to Rust's std::hash::Hasher
--- We don't have mutation yet, so this just returns a new state.
---
--- No associated types either, so we just make it a type param for now,
--- which surprisingly appears to work.
-type Hasher(@Self, @Out) = struct
- write: fn(@Self, I32) @Self,
- finish: fn(@Self) @Out,
-end
-
-type DumbHashState = I32
-
-
-fn main() I32 =
- let dumbhasher Hasher(DumbHashState, I32) = Hasher {
- .write = fn(s DumbHashState, i I32) DumbHashState = DumbHashState(i) end,
- .finish = fn(s DumbHashState) I32 = 3 end
- }
- let hash_state = DumbHashState(1)
- let updated_state = dumbhasher$.write(hash_state, 12)
- let hash = dumbhasher$.finish(updated_state)
- hash
-end
R fml/tests/programs/test_module2.gt => +0 -19
@@ 1,19 0,0 @@
--- Check:
--- status: success
-
--- Dummy type
-type String = I32
-
-type Show(@Self) = struct
- show: fn(@Self) String
-end
-
-const IntShow = Show {
- .show = fn(x I32) String = String(0) end
-}
-
-fn show(show Show(@T), val @T) String =
- show$.show(val)
-end
-
-
R fml/tests/programs/test_module3.gt => +0 -83
@@ 1,83 0,0 @@
--- Check:
--- status: success
-
--- Fine let's try something simpler.
-
-type Eq(@Self) = struct
- eq: fn(@Self, @Self) Bool,
-end
-
--- The name here has to be something other than Eq(I32) 'cause we
--- don't have specialization and we don't attach these names to
--- types in any way.
---
--- To ponder: What if we did attach names to types, or had
--- specialization? The latter evokes the Instance Problem, the former
--- I suppose is a way around it.
-const IntEq = Eq {
- .eq = fn(lhs I32, rhs I32) Bool =
- true
- end,
-}
-
-type Ord(@Self) = struct
- cmp: fn(@Self, @Self) I32,
-end
-
-const IntOrd = Ord {
- .cmp = fn(lhs I32, rhs I32) I32 =
- 0
- end,
-}
-
-
-type From(@Self, @In) = struct
- from: fn(@In) @Self
-end
-
-const BoolFromInt = From {
- .from = fn(i I32) Bool = false end
-}
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
-end
-
-type Len(@Self) = struct
- len: fn(@Self) I32,
-end
-
-const ListLen = Len {
- .len = fn(self List(@T)) I32 = 0 end
-}
-
-fn module_len(impl Len(@T), l @T) I32 =
- let total I32 = 0
- impl$.len(l)
-end
-
--- Specialize it just to make sure everything fits together...
-fn list_len(l List(@T)) I32 =
- module_len(ListLen, l)
-end
-
-const ListIdx = Idx {
- .idx = fn(self List(@T), i I32) @T = self$.dummy_data end
-}
-
--- Generalized thingy...
-fn idx(l List(@T)) @T =
- let total I32 = 3
- ListIdx$.idx(l, total)
-end
-
--- Can we make another instance for a more specialized type?
-const IntListIdx = Idx {
- .idx = fn(self List(I32), i I32) I32 = self$.dummy_data end
-}
-
R fml/tests/programs/test_module4.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: error
-
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32): @Output,
-end
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-fn foo(self List(I32), i I32) Bar =
- self$.dummy_data
-end
-
-
R fml/tests/programs/test_module5.gt => +0 -48
@@ 1,48 0,0 @@
--- Check:
--- status: error
-
-
-/- From modular implicits paper:
-module type Monad = sig
- type + 'a t
- val return : 'a -> 'a t
- val bind : 'a t -> ( 'a -> 'b t ) -> 'b t
-end
-
-let return { M : Monad } x = M . return x
-
-let (>>=) { M : Monad } m k = M . bind m k
-
-let map { M : Monad } ( m : 'a M . t ) f =
- m >>= fun x -> return ( f x )
-
-let join { M : Monad } ( m : 'a M . t M . t ) =
- m >>= fun x -> x
-
-let unless { M : Monad } p ( m : unit M . t ) =
- if p then return () else m
-
-implicit module Monad_option = struct
- type 'a t = 'a option
- let return x = Some x
- let bind m k =
- match m with
- | None -> None
- | Some x -> k x
-end
-
-implicit module Monad_list = struct
- type 'a t = 'a list
- let return x = [ x ]
- let bind m k = List . concat ( List . map k m )
-end
--/
-
--- The bad news is, we don't seem to be able to implement real monads
--- The good news is, we don't seem to be able to implement real monads
--- I think if we had associated types?
-type Monad(@A, @M) = struct
- return: fn(@A) @M(@A),
- bind: fn(@M(@A), fn(@A) @M(@B)) @M(@B)
-end
-
R fml/tests/programs/test_module6.gt => +0 -35
@@ 1,35 0,0 @@
--- Check:
--- status: success
-
--- TODO: BUGGO: This test occasionally fails to pass, *hopefully*
--- because of HashMap ordering shenanigans. Investigate.
-
--- A generic functional map from a key to a value
-type Map(@T, @K, @V) = struct
- get: fn(@T, @K) @V,
- set: fn(@T, @K, @V) @T
-end
-
--- Implement Map for a cell type
-type Cell(@K, @V) = struct
- key: @K,
- val: @V
-end
-
-fn make_cell_map(k @K, v @V) Map(Cell(@K, @V), @K, @V) =
- let module = Map {
- -- I guess we pretend this always succeeds,
- -- since I don't really feel like implementing if's
- .get = fn(t Cell(@K, @V), key @K) @V =
- t$.val
- end,
- -- Just create a new cell with the given key and val
- .set = fn(t Cell(@K, @V), key @K, val @V) Cell(@K, @V) =
- Cell {
- .key = key,
- .val = val,
- }
- end
- }
- module
-end
R fml/tests/programs/test_module7.gt => +0 -44
@@ 1,44 0,0 @@
--- Check:
--- status: success
-
-
-/- Another monad-ish attempt, this also gets called map
-From modular implicits paper section 3.4
-
-module type Functor = sig
- type + 'a t
- val map : ('a -> 'b) -> 'a t -> 'b t
-end
--/
-
-type Functor(@T1, @T2) = struct
- map: fn(@T1) @T2,
-end
-
--- Implement Map for a cell type
-type Cell(@V) = struct
- val: @V
-end
-
-fn make_cell_functor(f fn(@A) @B) Functor(Cell(@A), Cell(@B)) =
- Functor {
- .map = fn(cell Cell(@A)) Cell(@B) =
- Cell {
- .val = f(cell$.val)
- }
- end
- }
-end
-
-fn f(i Bool) I32 =
- 12
-end
-
-fn main() I32 =
- let test_cell = Cell {
- .val = true
- }
- let thing = make_cell_functor(f)
- let test_result Cell(I32) = thing$.map(test_cell)
- 0
-end
R fml/tests/programs/test_module_specialization1.gt => +0 -18
@@ 1,18 0,0 @@
--- Check:
--- status: success
-
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
-end
-
--- Can we make an instance for a specialization of List(T)?
-const IntListIdx = Idx {
- .idx = fn(self List(I32), i I32) I32 = self$.dummy_data end
-}
-
R fml/tests/programs/test_struct1.gt => +0 -22
@@ 1,22 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = struct
- a: I32,
- b: Bool
-end
-
-fn id(x @T) @T =
- x
-end
-
-
-fn main() I32 =
- let f1 Foo = id(Foo {
- .a = 3,
- .b = false
- })
- let f2 struct a: I32, b: Bool end = id({ .a = 3, .b = false })
- 3
-end
R fml/tests/programs/test_struct2.gt => +0 -25
@@ 1,25 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = struct
- a: I32,
- b: Bool
-end
-
-fn thing(i I32) Foo =
- Foo {
- .a = i,
- .b = false,
- }
-end
-
-
-fn main() I32 =
- let f1 Foo = Foo {
- .a = 3,
- .b = false
- }
- let f2 Foo = thing(4)
- 3
-end
R fml/tests/programs/test_struct3.gt => +0 -24
@@ 1,24 0,0 @@
--- Check:
--- status: error
-
-
-type Foo = struct
- a: I32,
- b: Bool
-end
-
-fn thing(i I32) Foo =
- Foo {
- .a = i,
- .b = false,
- }
-end
-
-
-fn main() I32 =
- let f1 Foo = Foo {
- .a = 3,
- }
- let f2 Foo = thing(4)
- 3
-end
R fml/tests/programs/test_struct4.gt => +0 -26
@@ 1,26 0,0 @@
--- Check:
--- status: success
-
-
-type Foo(@Thing) = struct
- a: @Thing,
- b: Bool
-end
-
-fn id(x @T) @T =
- x
-end
-
-
-fn main() I32 =
- let f1 Foo(I32) = id(Foo {
- .a = 3,
- .b = false
- })
-
- let f1 Foo(Bool) = id(Foo {
- .a = true,
- .b = false
- })
- 3
-end
R fml/tests/programs/test_struct5.gt => +0 -25
@@ 1,25 0,0 @@
--- Check:
--- status: error
-
-type Foo(@Thing) = struct
- a: @Blarg,
- b: Bool
-end
-
-fn id(x @T) @T =
- x
-end
-
-
-fn main() I32 =
- let f1 Foo(I32) = id(Foo {
- .a = 3,
- .b = false
- })
-
- let f1 Foo(Bool) = id(Foo {
- .a = true,
- .b = false
- })
- 3
-end
R fml/tests/programs/test_struct6.gt => +0 -10
@@ 1,10 0,0 @@
--- Check:
--- status: error
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-fn foo(self List(I32), i I32) Bar =
- self.dummy_data
-end
R fml/tests/programs/test_struct7.gt => +0 -9
@@ 1,9 0,0 @@
--- Check:
--- status: success
-
-type Foo = I32
-
-fn main() I32 =
- let x I32 = Foo(3)$
- x
-end
R fml/tests/programs/test_sum1.gt => +0 -28
@@ 1,28 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = sum
- -- For now sum type things *must* have a type argument,
- -- so we just do unit
- X {},
- Y { I32, Bool, Bool },
- Z struct
- a: I32,
- b: I32
- end,
-end
-
-fn thing(i Foo) Foo =
- i
-end
-
-
-fn main() I32 =
- let x Foo = Foo.X{}
- let y Foo = Foo.Y { 12, true, false }
- let z Foo = Foo.Z { .a = 12, .b = 40 }
- let a = thing(y)
- 3
-end
-
R fml/tests/programs/test_sum2.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: success
-
-type Option(@T) = sum
- None {},
- Some @T,
-end
-
-fn main() I32 =
- let x = Option.None {}
- let y = Option.Some(12)
- 3
-end
-
R fml/tests/programs/test_tuple1.gt => +0 -8
@@ 1,8 0,0 @@
--- Check:
--- status: success
-
-
-fn main() I32 =
- let a {I32, Bool} = {3, false}
- 3
-end
R fml/tests/programs/test_tuple2.gt => +0 -13
@@ 1,13 0,0 @@
--- Check:
--- status: success
-
-
-fn identity(i @T) @T =
- let y @T = i
- y
-end
-
-fn main() I32 =
- let a {I32, Bool, I32} = identity{1, false, 3}
- 3
-end
R fml/tests/programs/test_tuple3.gt => +0 -13
@@ 1,13 0,0 @@
--- Check:
--- status: error
-
-
-fn identity(i @T) @T =
- let y @T = i
- y
-end
-
-fn main() I32 =
- let a {I32, Bool, I32} = identity{1, 2, 3}
- 3
-end
R fml/tests/programs/test_tuple4.gt => +0 -12
@@ 1,12 0,0 @@
--- Check:
--- status: success
-
-
-fn thing(i {@T1, @T2}) {@T1, @T2} =
- i
-end
-
-fn main() I32 =
- let a {I32, Bool} = thing{1, false}
- 3
-end
R fml/tests/programs/test_typedef1.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = I32
-
-fn thing(i I32) Foo =
- Foo(i)
-end
-
-
-fn main() I32 =
- let f1 Foo = Foo(3)
- let f2 Foo = thing(4)
- 3
-end
R fml/tests/programs/test_typedef2.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: success
-
-
-type Foo = {I32, Bool}
-
-fn thing(i I32) Foo =
- Foo{i, false}
-end
-
-
-fn main() I32 =
- let f1 Foo = Foo{3, true}
- let f2 Foo = thing(4)
- 3
-end
R fml/tests/programs/test_typedef3.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: error
-
-
-type Foo = {I32, @A}
-
-fn thing(i I32, x @A) Foo =
- Foo{i, x}
-end
-
-
-fn main() I32 =
- --let f1 Foo = $Foo {3, true}
- let f2 Foo = thing(4, true)
- 3
-end
R fml/tests/programs/test_typedef4.gt => +0 -20
@@ 1,20 0,0 @@
--- Check:
--- status: success
-
-
-type Foo(@A) = {I32, @A}
-
-fn thing(i I32, x @A) Foo(@A) =
- Foo{i, x}
-end
-
-fn thing2(i I32, x @A) {I32, @A} =
- {i, x}
-end
-
-fn main() I32 =
- let f1 Foo(I32) = thing(4, 5)
- let f2 Foo(Bool) = thing(4, true)
- let f2 {I32, Bool} = thing2(4, true)
- 3
-end
R fml/tests/programs/test_typedef5.gt => +0 -19
@@ 1,19 0,0 @@
--- Check:
--- status: success
-
-type Foo = {I32, Bool}
-
-fn thing(i I32, x Bool) Foo =
- Foo{i, x}
-end
-
-type Bar = I32
-
-fn main() I32 =
- let f1 Foo = thing(4, true)
- let f2 {I32, Bool} = f1$
-
- let f3 = Bar(12)
- let f4 I32 = f3$
- 3
-end
R fml/tests/programs/test_typedef5_failure.gt => +0 -19
@@ 1,19 0,0 @@
--- Check:
--- status: error
-
-type Foo = {I32, Bool}
-
-fn thing(i I32, x Bool) Foo =
- Foo{i, x}
-end
-
-type Bar = I32
-
-fn main() I32 =
- let f1 Foo = thing(4, true)
- let f2 {I32, I32} = f1$
-
- let f3 = Bar(12)
- let f4 I32 = f3$
- 3
-end
R fml/tests/programs/test_typedef6.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: success
-
-type Foo(@T) = {I32, @T}
-
-fn thing(i I32, x Bool) Foo(Bool) =
- Foo{i, x}
-end
-
-fn main() I32 =
- let f1 Foo(Bool) = thing(4, true)
- let f2 {I32, Bool} = f1$
- 3
-end
R fml/tests/programs/test_typedef7.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: error
-
-type Foo(@T) = {I32, @T}
-
-fn thing(i I32, x Bool) Foo(Bool) =
- Foo{i, x}
-end
-
-fn main() I32 =
- let f1 Foo(Bool) = thing(4, true)
- let f2 {I32, I32} = f1$
- 3
-end
R fml/tests/programs/test_typedef8.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: error
-
-type Foo(@T) = {I32, @T}
-
-fn thing2(i I32, x @Whatever) Foo(@Whatever) =
- Foo{i, x}
-end
-
-fn main() I32 =
- let f3 = thing2(3, 3)
- let f4 {I32, Bool} = f3$
- 3
-end
R fml/tests/programs/test_unnamed_failure1.gt => +0 -10
@@ 1,10 0,0 @@
--- Check:
--- status: error
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-fn should_fail() Bar =
- List { .dummy_data = 3 }
-end
R fml/tests/programs/test_unnamed_failure2.gt => +0 -17
@@ 1,17 0,0 @@
--- Check:
--- status: error
-
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
-end
-
-type List(@T) = struct
- dummy_data @T,
-end
-
-
--- This correctly fails
-const BadIntListIdx = Idx {
- .idx = fn(self List(I32), i I32) Foo = true end
-}
-
R fml/tests/programs/test_unnamed_failure3.gt => +0 -16
@@ 1,16 0,0 @@
--- Check:
--- status: error
-
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
-end
-
-type List(@T) = struct
- dummy_data: @T,
-end
-
-
--- This correctly fails
-const BadIntListIdx2 = Idx {
- .idx = fn(self List(I32), i I32) Foo = self$.dummy_data end
-}
R fml/tests/programs/test_unnamed_generic.gt => +0 -14
@@ 1,14 0,0 @@
--- Check:
--- status: error
-
-
-fn identity(i @T) @T =
- let y @Heck = i
- y
-end
-
-fn main() I32 =
- let a I32 = identity(1)
- let c Bool = identity(false)
- 3
-end
M gt/contrib/rng.gt +23 -15
@@ 10,26 10,34 @@ end
const RAND32_DEFAULT_INC U64 = 1442695040888963407
const RAND32_MULTIPLIER U64 = 6364136223846793005
---- Creates a new Rand32 with the given state and seed
-fn new_inc(seed U64, increment U64) Rand32 =
+--- Creates a new RNG state with the given seed
+--- and the default multiplier
+fn new_rng(seed U64) Rand32 =
let mut rng = Rand32 {
.state = 0,
- .increment = increment.wrapping_shl(1) bitor 1
+ .increment = RAND32_DEFAULT_INC,
}
- let _ = rand32(rng)
- rng.state = rng.state.wrapping_add(seed)
- let _ = rand32(rng)
+ let _ = rng:rand_u32()
+ rng.state = rng.state:wrapping_add(seed)
+ let _ = rng:rand_u32()
rng
end
---- Produces a random `u32` in the range `[0, u32::MAX]`.
-fn rand_u32(selff ^uniq Rand32) U32 =
- let oldstate U64 = selff^.state
- selff.state = oldstate
- .wrapping_mul(RAND32_MULTIPLIER)
- .wrapping_add(selff^.inc)
- let xorshifted U32 = (((oldstate >> 18) ^ oldstate) >> 27) as U32
- let rot U32 = (oldstate >> 59) as U32
- xorshifted.rotate_right(rot)
+--- Mutably borrows a `Rand32` and produces a random
+--- `U32` in the range `[0, u32::MAX]`.
+fn rand_u32(self ^uniq Rand32) U32 =
+ let oldstate U64 = self^.state
+ self.state = oldstate
+ :wrapping_mul(RAND32_MULTIPLIER)
+ :wrapping_add(self^.inc)
+ let xorshifted = cast(|U32| ((oldstate shr 18) xor oldstate) shr 27)
+ let rot = cast(|U32| oldstate shr 59)
+ xorshifted rotr rot
+end
+
+fn main() =
+ let mut rng = new_rand32(4)
+ let num = rng:rand_u32()
+ __println_u64(rng$.state)
end
No newline at end of file
A => gt/contrib/rng_real.gt +40 -0
@@ 0,0 1,40 @@
+--- A simple PCG RNG.
+--- Does not actually produce the correct answer, but is something
+--- that compiles with current Garnet.
+
+type Rand32 = struct
+ state: I64,
+ inc: I64,
+end
+
+const RAND32_DEFAULT_INC I64 = 1442695040888963407
+const RAND32_MULTIPLIER I64 = 6364136223846793005
+
+--- Creates a new Rand32 with the given state and seed
+fn new_inc(seed I64, increment I64) Rand32 =
+ let mut rng = Rand32 {
+ .state = 0,
+ .inc = __bor_i64(__lshift_i64(increment, 1), 1),
+ }
+ let _ = rand_u32(rng)
+ rng$.state = rng$.state + seed
+ let _ = rand_u32(rng)
+ rng
+end
+
+
+--- Produces a random `u32` in the range `[0, u32::MAX]`.
+fn rand_u32(st Rand32) I64 =
+ let mut oldstate I64 = st$.state
+ let mut st Rand32 = st
+ st$.state = oldstate * RAND32_MULTIPLIER + st$.inc
+ let xorshifted I64 = __rshift_i64(__bxor_i64(__rshift_i64(oldstate, 18), oldstate), 27)
+ let rot I64 = __rshift_i64(oldstate, 59)
+ __rshift_i64(xorshifted, rot)
+end
+
+fn main() =
+ let mut rng = new_inc(3, 4)
+ let number I64 = rand_u32(rng)
+ __println_i64(number)
+end
No newline at end of file
A => gt/core/array.gt +0 -0
A => gt/core/cmp.gt +155 -0
@@ 0,0 1,155 @@
+--- Basic module for equality and comparisons.
+---
+--- Now that I think of it, we don't have PartialEq or
+--- PartialOrd. We don't need them yet 'cause we don't
+--- have floats. I'm kinda okay with this tbh, iirc there's
+--- a semi-official standard out for ordering NaN's we
+--- should look into, and there's a broad design space of
+--- Rust crates for NaN-preventing floats and such.
+
+type Eq(T) = struct(T)
+ eq: fn(T, T) Bool,
+end
+
+
+const EqI32 Eq(I32) = Eq {
+ .eq = fn(lhs I32, rhs I32) Bool = lhs == rhs end,
+}
+
+const EqBool Eq(Bool) = Eq {
+ .eq = fn(lhs Bool, rhs Bool) Bool = lhs == rhs end,
+}
+
+--- just for a specific length rn, can change once we have
+--- slices or const generics or such
+fn all(|T| eq_impl Eq(T), val T, array [3]T) Bool =
+ let mut i I32 = 0
+ loop
+ if i == 3 then break end
+ let val2 = array[i]
+ if not eq_impl$.eq(val, val2) then return false end
+ i = i + 1
+ end
+ true
+end
+
+--- The result of a comparison between two values.
+type Ordering = enum
+ Less = -1,
+ Equal = 0,
+ Greater = 1,
+end
+
+-- Methods on Ordering
+fn is_eq(self Ordering) Bool =
+ self == Ordering.Equal
+end
+
+fn is_ne(self Ordering) Bool =
+ self != Ordering.Equal
+end
+
+fn is_lt(self Ordering) Bool =
+ self == Ordering.Less
+end
+
+fn is_ge(self Ordering) Bool =
+ self != Ordering.Less
+end
+
+fn is_gt(self Ordering) Bool =
+ self == Ordering.Greater
+end
+
+fn is_le(self Ordering) Bool =
+ self != Ordering.Greater
+end
+
+--- A module that implements comparison for a particular type.
+type Ord(T) = struct
+ cmp: fn(T, T) Ordering,
+end
+
+--- Returns the maximum of the two values passed.
+fn max(|T| ord Ord(T), self T, other T) T =
+ if ord.cmp(self, other):is_lt() then
+ self
+ else
+ other
+ end
+end
+
+--- Returns the minimum of the two values passed.
+fn min(|T| ord Ord(T), self T, other T) T =
+ if ord.cmp(self, other):is_gt() then
+ self
+ else
+ other
+ end
+end
+
+--- Returns the value if it is between the min and max values
+--- passed, or returns the min or max if it is beyond those bounds.
+fn clamp(|T| ord Ord(T), self T, min T, max T) T =
+ assert!(ord.cmp(min, max) != Ordering.Greater)
+ if ord.cmp(self, min).is_lt() then min
+ elseif ord.cmp(self, max).is_gt() then max
+ else self
+ end
+end
+
+--- Implementation of `Ord` for I32
+const OrdI32 Ord(I32) = Ord {
+ .cmp = fn(x I32, y I32) Ordering =
+ if x > y then Ordering.Greater
+ elseif x < y then Ordering.Less
+ else Ordering.Equal
+ end
+ end
+}
+
+--- A functor that implements "Ord(Option(T)) where T: Ord".
+--- `None` is always less than `Some(x)` for any `x`.
+fn option_ord(|T| ord Ord(T)) Ord(Option(T)) =
+ Ord {
+ .cmp = fn(x Option(T), y Option(T)) Ordering =
+ -- There's probably some fancy Option combinator to use
+ -- here but I can never keep em straight.
+ match {x, y} with
+ | {Some(x_inner), Some(y_inner)} -> ord.cmp(x_inner, y_inner)
+ | {None, None} -> Ordering.Equal
+ | {Some(_), None} -> Ordering.Greater
+ | {None, Some(_)} -> Ordering.Less
+ end
+ end
+ }
+end
+
+const OrdOptionI32: Ord(Option(I32)) = option_ord(OrdI32)
+
+--- A functor that implements `Ord(Result(T, E)) where T, E: Ord`.
+---
+--- We take Rust's approch, ofc, any Ok is less than any Err
+fn result_ord(|T, E| ord_t Ord(T), ord_e Ord(E)) Ord(Result(T, E)) =
+ .cmp = fn(x Result(T, E), y Result(T, E)) Ordering =
+ match {x, y} with
+ | {Ok xx, Ok yy} -> ord_t.cmp(xx, yy)
+ | {Err xx, Err yy} -> ord_e.cmp(xx, yy)
+ | {Ok _, Err _} -> Ordering.Less
+ | {Err _, Ok _} -> Ordering.Greater
+ end
+end
+
+--- Create an implementation of Eq from the given implementation
+--- of Ord.
+---
+--- TODO: This is also kinda a classic coherence problem with modules
+--- iirc, so investigate further. I thiiiiink we have to bundle
+--- the Ord impl into the Eq one?
+fn eq_from_ord(|T| ord Ord(T)) Eq(T) =
+ Ord {
+ .eq = fn(x T, y T) Bool =
+ ord.ord(x, y).is_eq()
+ end
+ }
+end
A => gt/core/convert.gt +39 -0
@@ 0,0 1,39 @@
+--- Conversion modules/interfaces.
+---
+--- Design ticket for stuff here: TODO
+--- from/tryfrom
+--- try (? operator)
+---
+--- Rust's `Try` trait is absolutely fascinating and bonkers, so
+--- look into it a bit more.
+
+
+--- Infallible conversions.
+type From(Self, Out) = struct(Self, Out) =
+ from: fn(Self) Out
+end
+
+--- Fallible conversions.
+type TryFrom(Self, Out, E) = struct(Self, Out, E) =
+ try_from: fn(Self) Result(Out, E)
+end
+
+--- Truncation, for example cutting off half of an U64 to make
+--- a U32.
+type Truncate(Self, Out) = struct(Self, Out) =
+ trunc: fn(Self) Out
+end
+
+--- Extend, for example sign-extending I32->I64 or zero-extending
+--- U32->U64
+type Extend(Self, Out) = struct(Self, Out) =
+ extend: fn(Self) Out
+end
+
+--- Bitcast. For example converting an I32 into a U32 or F32.
+---
+--- TODO: Should require both types have the same size and
+--- compatible layouts.
+type bitcast(Self, Out) = struct(Self, Out) =
+ bitcast: fn(Self) Out
+end
A => gt/core/fmt.gt +0 -0
M gt/core/hash.gt +27 -34
@@ 1,11 1,14 @@
---! Basic hashing function.
---!
---! For now this is SipHash 2-4, shamelessly stolen from
---! Rust's `std::hash::SipHasher`. Implemented from the
---! reference impl at https://github.com/veorq/SipHash
---!
---! We might want a stable hash function sometime too,
---! probably FNV.
+--- Basic hashing function.
+---
+--- For now this is SipHash 2-4, shamelessly stolen from
+--- Rust's `std::hash::SipHasher`. Implemented from the
+--- reference impl at https://github.com/veorq/SipHash
+---
+--- We might want a stable hash function sometime too,
+--- probably FNV. (There's also fxHash, xxHash, wyhash, etc...)
+---
+--- TODO: clean up with our built-in bit ops and conversions.
+--- Heck, just implement bit ops.
const C_ROUNDS: U32 = 2
const D_ROUNDS: U32 = 4
@@ 18,11 21,11 @@ fn u64_from_bytes_le(bytes ^[]U8) U64 =
todo("this")
end
-fn u64_to_bytes_le(bytes: U64): [8]U8 =
+fn u64_to_bytes_le(bytes U64) [8]U8 =
todo("this too")
end
-fn sipround(v0: U64, v1: U64, v2: U64, v3: U64): {U64, U64, U64, U64} =
+fn sipround(v0 U64, v1 U64, v2 U64, v3 U64) {U64, U64, U64, U64} =
let v0 = v0 + v1
let v1 = rotl(v1, 13)
let v1 = v1 bxor v0
@@ 41,38 44,28 @@ fn sipround(v0: U64, v1: U64, v2: U64, v
end
-fn siphash(input: ^[]U8, k: U128, out: ^uniq[]U8): {} =
- assert(out.len() == 8 or out.len() == 16)
- let mut v0: U64 = 0x736f6d6570736575
- let mut v1: U64 = 0x646f72616e646f6d
- let mut v2: U64 = 0x6c7967656e657261
- let mut v3: U64 = 0x7465646279746573
+fn siphash(input ^[]U8, k U128, out ^uniq[]U8) {} =
+ assert(out:len() == 8 or out:len() == 16)
+ let mut v0 U64 = 0x736f6d6570736575
+ let mut v1 U64 = 0x646f72616e646f6d
+ let mut v2 U64 = 0x6c7967656e657261
+ let mut v3 U64 = 0x7465646279746573
-- TODO: Figure out how casts work
- -- Erlang-like bit patterns?
- -- let <<k0:64, k1:64>> = k
- -- Or maybe the bit pattern is an operator that extracts bits
- -- and returns a tuple, and then we define what types those
- -- turn into?
- -- https://www.erlang.org/docs/25/programming_examples/bit_syntax.html#segments
- -- has some info on it, basically we need to say both how many bits
- -- we extract and what type those bits get turned into.
- -- let {k0, k1}: {U64, U64} = <<k0:64, k1:64>> k
- -- let {k0, k1} = <<k0:64/U64, k1:64/U64>> k
- let k0: U64 = (k band 0xFFFF_FFFF) as U32
- let k1: U64 = ((k >> 32) band 0xFFFF_FFFF) as U32
- let left: Size = input.len() band 7
- let mut b: U64 = input.len() << 56
+ -- Using convert.gt seems like a fine place to start.
+ let k0 U64 = Convert.trunc(|U128, U64| k band 0xFFFF_FFFF)
+ let k1 U64 = Convert.trunc(|U128, U64| (k >> 32) band 0xFFFF_FFFF)
+ let left Size = input:len() band 7
+ let mut b U64 = input:len() << 56
v3 = v3 bxor k1
v2 = v2 bxor k0
v1 = v1 bxor k1
v0 = v0 bxor k0
- if out.len() == 16 then
+ if out:len() == 16 then
v1 = v1 bxor 0xEE
end
- -- TODO: This hypothetical `range()` function is `start, end, step` like Python's
- for i in range(0, input.len(), 8) do
+ for i in range(0, input:len()):step_by(8) do
-- TODO: We assume Rust's slice syntax for now?
let bytes = input[i..(i+8)]
let m = u64_from_bytes_le(bytes)
@@ 89,7 82,7 @@ fn siphash(input: ^[]U8, k: U128, out: ^
-- for us.
for i in range(1,8) do
-- Round input.len() down to the closest multiple of 8
- let input_tail_idx = (input.len() / 8) * 8;
+ let input_tail_idx = (input:len() / 8) * 8;
b = b bor (input[input_tail_idx + i] << i * 8)
end
A => gt/core/iter.gt +215 -0
@@ 0,0 1,215 @@
+
+--- General-purpose iterator module.
+---
+--- We don't have borrowing yet per se, so for now this will just have
+--- owned iterators. We'll pretend everything can always be copied.
+
+--- Honestly why don't we just make iterators a closure?
+---
+--- The answer from scottmcm is basically because we want to
+--- be able to hang other information off of it, such as
+--- capacity, custom (more efficient/specialized) methods,
+--- etc.
+alias Iterator(Item) = fn() Option(Item) =
+
+/- See all this:
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:01 PM
+That's basically just Iterator::next spelled FnMut() -> Option<T>, so it'll certainly work. Though if you have Copy closures -- like rust now does (for better or worse) -- then you might hit the same issues.
+
+The big thing doing it with just a closure is that it keeps you from having a capacity hint for collect, so you're going to end up reallocating more than optimal.
+
+icefox — Today at 6:01 PM
+Heck you're right
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:02 PM
+Indeed, you'll see that rust has https://doc.rust-lang.org/std/iter/fn.from_fn.html to make an iterator from such a closure
+
+icefox — Today at 6:02 PM
+Yep, it's just annoying to define ten million nearly identical Iterator structs
+And you get like 2.5 layers deep in type metaprogramming which is painful in Rust and more painful in Garnet
+
+danielrab — Today at 6:06 PM
+it's a little junk, but you could have methods on the FnMut trait with default implementation that only exist when the return type is Option<T>
+
+dawn ❧ — Today at 6:07 PM
+from_fn with TAIT sounds like it'll be real nice for iterators i think
+
+danielrab — Today at 6:07 PM
+what's TAIT?
+
+dawn ❧ — Today at 6:07 PM
+type alias impl trait
+they're existential
+
+icefox — Today at 6:09 PM
+I'm trying to think of a good way to do it the other way around
+
+danielrab — Today at 6:09 PM
+hmm?
+like auto-implement FnMut when Iterator is implemented or something?
+
+icefox — Today at 6:10 PM
+Basically have two types of iterator, one that provides a capacity method and one that doesn't
+
+danielrab — Today at 6:10 PM
+bad
+
+icefox — Today at 6:10 PM
+And the one that doesn't can be a closure, and the one that does can be turned into a closure if you want to ignore the capacity
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:11 PM
+@icefox BTW, if you're starting from something new, you should start from https://github.com/rust-lang/rust/pull/45595, not from next.
+
+icefox — Today at 6:11 PM
+....ooo
+
+danielrab — Today at 6:11 PM
+well, I guess you could auto-implement that one that doesn't
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:12 PM
+alias Iterator(Item) = for<A, B> fn(A, fn(A, Item) -> ControlFlow<B, A>) -> ControlFlow<B, A>;
+
+danielrab — Today at 6:12 PM
+but like, iterators have a ton of optional methods, not just capacity
+
+icefox — Today at 6:12 PM
+Yes, and this is a problem for Future Fox but I have vague ideas for how to solve it
+@𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 I have a lot of reading to do apparently! Thanks.
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:13 PM
+Because iter.next() can be implemented as iter.try_for_each(Err).err() , and thus you can still do external iteration if needed, but making the core thing have internal iteration makes a bunch of things faster.
+
+danielrab — Today at 6:14 PM
+Just have methods with default implementations on your Iterator trait, whether it is actually FnMut or not
+
+icefox — Today at 6:14 PM
+Default implementations are something I don't have yet, so~
+You can conjure them forth with a functor or you can just implement functions that operate on an Iterator(T)
+
+danielrab — Today at 6:17 PM
+iter.try_for_each(Err).err() feels like such a hack
+
+icefox — Today at 6:18 PM
+So worst case you have map(Iterator(X), fn(X) Y) Iterator(Y)
+And then my_thing::map(...) which does the same thing but faster
+Which isn't ideal, so I'd prefer to avoid it, but it won't murder any puppies
+Actually that gets ugly cause of composition
+Darn it
+Ok I'm gonna need to allow full-ass custom implementations for iterators after all
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:20 PM
+It actually optimizes surprisingly well, because from the types and constants involved the compiler can see that the loop will never run more than once -- it's basically like it boils down to for x in blah { break Some(x) }, which also clearly executes 0 or 1 times, never more.
+
+icefox — Today at 6:20 PM
+Otherwise if you pass a my_thing to a function and it calls map() on it you have no way of telling it to use the better version of map()
+
+danielrab — Today at 6:25 PM
+I didn't say it was bad, I said it feels like a hack.
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:26 PM
+In terms of how it's spelled or in terms of what it's doing or ...?
+
+danielrab — Today at 6:27 PM
+In terms of how it's spelled out mainly
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:27 PM
+The actual spelling is .try_for_each(ControlFlow::Break).break_value(), but IIRC that break_value method isn't stable, so I use the short implementation in chat
+
+danielrab — Today at 6:29 PM
+That's a little better, but it still has this "do this for each element until I tell you to stop, but actually stop immediately" weirdness
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:29 PM
+Yeah. Not something I'd want people to write regularly, but very good for a default implementation
+
+icefox — Today at 6:37 PM
+Something something coroutines something something
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:46 PM
+I've yet to see a good answer for how to get collect hints from coroutines
+
+icefox — Today at 6:48 PM
+Easy, just make it return a tuple of (Option<usize>, ActualCoroutine) on first invocation
+Oh wait you said a good answer, nvm
+...actually...
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:49 PM
+right, cause you can use a size_hint without iterating and you can get one in the middle of iteration
+
+icefox — Today at 6:50 PM
+I mean it'd only return the size hint once, so doing it without iterating isn't a problem
+Getting one in the middle of iteration... Hmm, not sure what one would want that for, but...
+The downside is basically there's always another step to starting the iteration at all
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:51 PM
+Well Vec::extend calls it that way, for example.
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 6:52 PM
+Also, ExactSizeIterator::len has the same "I want it in the middle of iterating without consuming an element" problem.
+
+icefox — Today at 6:53 PM
+Oh it only calls it if it runs out of reserved space? Interesting.
+
+-/
+
+--- Make the closure for iterating over an array
+fn into_iter(|Item| arr []Item) Iterator(Item) =
+ let mut counter = 0
+ let thunk = fn() Option(Item) =
+ if counter >= arr:len() then
+ Option.None {}
+ else
+ let vl = arr[counter]
+ counter += 1
+ Option.Some vl
+ end
+ end
+ thunk
+end
+
+--- So if we use it, what does it look like?
+fn test_array_iterator() =
+ let arr []U32 = [1, 2, 3, 4, 5]
+ let mut iter = arr:into_iter()
+
+ loop
+ let next_item = iter()
+ if next_item.is_none() then break end
+ __print_u32(next_item)
+ end
+
+ -- currently-hypothetical for loop syntax
+ for itm in arr:into_iter() do
+ __print_u32(itm)
+ end
+end
+
+
+--- Ok let's make a module representing an iterator with some specific
+--- Self type with arbitrary state. That way you can treat any object
+--- as an iterator if you want to instead of having to use a closure.
+---
+--- Right now there's no way for `Self` to actually change so this isn't
+--- terribly useful, but hey.
+type IterImpl(Self, Item) = struct(Self, Item)
+ next: fn(Self) Option(Item)
+end
+
+--- Create an iterator out of any type and an IntoIter implementation
+--- for it.
+fn make_iterator(|Self, Item| impl IterImpl(Self, Item), val Self) Iterator(Item) =
+ let thunk = fn() Option(Item) =
+ impl.next(val)
+ end
+ thunk
+end
+
+fn iter_option(|T| self Option(T)) Iterator(T) =
+end
+
+fn iter_result(|T, E| self Result(T, E)) Iterator(T) =
+end
+
+--- I remember there's something squirrelly about Rust ranges and iterators
+--- but don't recall what, check it out sometime.
+fn iter_range(self Range(Size)) Iterator(Size) =
+end
M gt/core/lib.gt +0 -1
@@ 4,7 4,6 @@
type Str = {}
--- Assume 64-bit pointers
---- TODO: We don't have unsigned numbers yet.
alias Size = U64
alias Offset = I64
A => gt/core/mem.gt +0 -0
A => gt/core/num.gt +0 -0
A => gt/core/ops.gt +54 -0
@@ 0,0 1,54 @@
+--- Fundamental operators. In Rust this is anything that is overloadable.
+--- We don't really have any overloading yet, so it's more a a place to
+--- play with ideas for such things.
+
+
+--- Kinda an equivalent of Rust's Fn() trait.
+---
+--- Just pretend borrowing doesn't matter right now.
+type Apply(Self, Inputs, Outputs) = struct(Inputs, Outputs)
+ apply: fn(Self, Inputs) Outputs
+end
+
+type MyThing = struct
+ environment: U32
+end
+
+-- impl Apply for MyThing
+const ApplyMyThing: Apply(MyThing, {}, U32) = Apply {
+ .apply = fn(self MyThing, _ {}) U32 =
+ self.environment += 1
+ self.environment
+ end
+}
+
+--- Thunk-ish thingy for modular implicits
+fn apply(|Self, Inputs, Outputs| impl Apply(Self), inputs Inputs) Outputs =
+ impl.apply(inputs)
+end
+
+fn use_apply() =
+ let mut thing1 = MyThing {
+ .environment = 0
+ }
+ let mut thing2 = MyThing {
+ .environment = 100
+ }
+
+ -- I guess the secret is inferring this to call
+ -- ApplyMyThing.apply(thing1, {})
+ thing1({}) -- 1
+ thing2({}) -- 101
+
+ -- So if we have modular implicits we can do this:
+ apply(thing1, {}) -- 2
+ apply(thing2, {}) -- 102
+end
+
+
+--- Module for indexing values, a la `foo[3]`
+type Index(Self, Out, Idx) = struct(Self, Out, Idx)
+ --- Technically maybe should borrow Self and Out,
+ --- this isn't terriblt interesting without that distinction.
+ index: fn(Self, Idx) Out
+end
M gt/core/option.gt +55 -26
@@ 1,47 1,76 @@
---! Basic Option type
---!
---! TODO:
---! Match syntax --
---! Sum type details -- patterh match syntax, etc.
---! Panics, strings...
+--- Basic Option type
+---
+--- TODO:
+--- Match syntax --
+--- Sum type details -- patterh match syntax, etc.
+--- Panics, strings...
+--- module???
--- Properties: Copy, Clone, Debug, Zero, Pod when T is Pod...
-type Option[T] = sum
- None,
- Some{T},
+type Option(T) = sum
+ Some T,
+ None {},
end
--- Properties: Panics.
-fn unwrap(self: Option[T]): T =
- self:expect("Unwrap failed")
+fn unwrap(|T| self Option(T)) T =
+ expect(self, "Unwrap failed")
end
--- Properties: Panics.
-fn expect(self: Option[T], message: Str): T =
+fn expect(|T| self Option(T), message Str) T =
match self with
- | None -> panic(message)
- | Some{val} -> val
+ | None {} -> panic(message)
+ | Some val -> val
end
end
-fn unwrap_or(self: Option[T], val: T): T =
+fn unwrap_or(|T| self Option(T), val T) T =
+ match self with
+ | None {} -> val
+ | Some val -> val
+ end
+end
+
+fn unwrap_or_else(|T| self Option(T), f fn() T) T =
match self with
- | None -> val
- | Some{val} -> val
+ | None {} -> f()
+ | Some val -> val
+ end
+end
+
+fn cloned(|T| self Option(Borrow(T)) Option(T) where T: Clone =
+ match self with
+ | None {} -> None
+ | Some val -> Some({T.clone(val)})
end
end
-fn unwrap_or_else(self: Option[T], f: fn(): T): T =
+fn is_some(|T| self Option(T)) Bool =
match self with
- | None -> f()
- | Some{val} -> val
- end
+ | None {} -> false
+ | Some val -> true
+ end
+end
+
+fn is_none(|T| self Option(T)) Bool =
+ not is_some(self)
end
-fn cloned(self: Option[T &]): Option[T] where T: Clone =
- match self with
- | None -> None
- | Some{val} -> Some({T.clone(val)})
- end
+-- todo:
+-- map, inspect, take, replace,
+
+-- Hmm, what would this look like as a module?
+type Option(A) = struct(A)
+ type T(A) = sum(A)
+ Some A,
+ None {},
+ end
+
+ fn unwrap(T) A,
+ fn expect(T, Str) A,
+ fn unwrap_or(T, A) A,
+ fn unwrap_or_else(T, fn() A) A,
+ fn cloned(Borrow(T)) T,
end
A => gt/core/range.gt +61 -0
@@ 0,0 1,61 @@
+
+
+
+/-
+ icefox — Today at 5:30 PM
+I recall people once talking about a misdesign with Rust's iterators and Range, does anyone recall what the heck it was?
+
+Serayen — Today at 5:31 PM
+Ranges implement Iterator directly
+Means they can't be Copy, which is really annoying
+
+icefox — Today at 5:31 PM
+I think-- right, that was it
+Thanks
+Why can't Iterator's be Copy though, then?
+
+triadic bite — Today at 5:34 PM
+it results in unexpected behavior (hopefully someone has an example)
+
+𝔰𝔠𝔬𝔱𝔱𝔪𝔠𝔪 — Today at 5:58 PM
+see https://github.com/rust-lang/rfcs/pull/3550
+-/
+
+--- A half-open range bounded between
+--- `[start, end)`.
+---
+--- TODO: Should this contain an Ord impl? Not very
+--- meaningful without it. For now I'll leave it as no, though.
+type Range(Idx) = struct(Idx)
+ start: Idx,
+ end: Idx,
+end
+
+--- Whether the range contains the item.
+fn contains(|Idx| ord Ord(Idx), self Range(Idx), itm Idx) Bool =
+ ord.ord(self.start, itm).is_ge() and ord.ord(self.end, itm).is_lt()
+end
+
+--- Technically this only needs Eq, not Ord.
+fn is_empty(|Idx| ord Ord(Idx), self Range(Idx)) Bool =
+ ord.ord(self.start, self.end).is_eq()
+end
+
+--- Basically Python's range function that creates an iterator
+---
+--- TODO: Figure out how we're implementing iterators.
+fn range(|Idx| start Idx, end Idx) Iterator(Idx) =
+ todo()
+ Range {
+ .start = start,
+ .end = end,
+ }
+end
+
+--- Should return an iterator that steps multiple items at a time,
+--- ideally more efficiently than the naive approach...
+fn step_by(|Idx| self Range(Idx)) Iterator(Idx) =
+ todo()
+end
+
+-- todo: Iterator, Index(?)
M gt/core/result.gt +47 -17
@@ 1,23 1,33 @@
---! Basic Result type
---!
---! TODO:
---! Match syntax --
---! Sum type details -- pattern match syntax, etc.
---! Panics, strings...
+--- Basic Result type
+---
+--- TODO:
+--- Panics, strings...
+--- Our general-purpose Result type.
--- Properties: Copy, Clone, Debug, Zero, Pod when T is Pod...
-type Result[T,E] = sum
- Ok{T}
- Err{E},
+type Result(T, E) = sum(T, E)
+ Ok {T},
+ Err {E},
end
+--- Takes a Result and returns the value contained if it is Ok,
+--- otherwise panics.
+---
--- Properties: Panics.
-fn unwrap(self: Result[T,E]): T =
- self:expect("Unwrap failed")
+fn unwrap(|T, E| self Result(T,E) T =
+ expect(self, "Unwrap failed")
end
+--- Takes a Result and returns the value contained if it is Ok,
+--- otherwise panics with the given message
+---
+--- TODO: I honestly kinda don't like how hard it is to stick a sane
+--- format string in here in Rust, where you have to do
+--- `thing.expect_with(|e| format!("help I got an {}", e))`.
+--- But that's a problem for Future Garnet.
+---
--- Properties: Panics.
-fn expect(self: Result[T,E], message: Str): T =
+fn expect(|T, E| self Result(T, E), message Str) T =
match self with
| Err{_} -> panic(message)
| Ok{val} -> val
@@ 25,23 35,43 @@ fn expect(self: Result[T,E], message: St
end
-fn unwrap_or(self: Result[T,E], val: T): T =
+fn unwrap_or(|T, E| self Result(T, E), val T) T =
match self with
| Err{_} -> val
| Ok{val} -> val
end
end
-fn unwrap_or_else(self: Result[T,E], f: fn(): T): T =
+fn unwrap_or_else(|T, E| self Result(T, E), f fn() T) T =
match self with
| Err{_} -> f()
| Ok{val} -> val
end
end
-fn cloned(self: Result[T &, E]): Result[T,E] where T: Clone =
+fn is_ok(|T, E| self Result(T, E)) Bool =
match self with
- | Err{e} -> Err({e})
- | Ok{val} -> Ok({T.clone(val)})
+ | Ok{_} -> true
+ | Err{_} -> false
end
end
+
+fn is_err(|T, E| self Result(T, E)) Bool =
+ not is_ok(self)
+end
+
+--- Transforms a `Result(T, E)` into an `Option(T)`
+fn ok(|T, E| self Result(T, E)) Option(T) =
+ match self with
+ | Ok{val} -> Some(val)
+ | Err{_} -> None {}
+ end
+end
+
+--- Transforms a `Result(T, E)` into an `Option(E)`
+fn err(|T, E| self Result(T, E)) Option(T) =
+ match self with
+ | Ok{_} -> None {}
+ | Err{val} -> Some val
+ end
+end
A => gt/core/sync.gt +0 -0
A => gt/mem/base.gt +0 -0
A => gt/mem/box.gt +0 -0
A => gt/mem/hashset.gt +0 -0
A => gt/mem/rc.gt +0 -0
A => gt/mem/string.gt +0 -0
M src/ast.rs +30 -47
@@ 17,12 17,14 @@ use crate::*;
pub enum Literal {
/// An integer of some kind
Integer(i128),
- /// An integer with a known size
+ /// An integer with a known size and signedness
SizedInteger {
/// Literal value
vl: i128,
/// The size of the integer, in bytes
bytes: u8,
+ /// is_signed
+ signed: bool,
},
/// A bool literal
Bool(bool),
@@ 32,9 34,13 @@ impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Literal::Integer(i) => write!(f, "{}", i),
- Literal::SizedInteger { vl, bytes } => {
+ Literal::SizedInteger { vl, bytes, signed } => {
let size = bytes * 8;
- write!(f, "{}_I{}", vl, size)
+ if *signed {
+ write!(f, "{}_I{}", vl, size)
+ } else {
+ write!(f, "{}_U{}", vl, size)
+ }
}
Literal::Bool(b) => write!(f, "{}", b),
}
@@ 94,23 100,20 @@ pub struct Signature {
/// Return type
pub rettype: Type,
/// Type parameters
- pub typeparams: Vec<Type>,
+ pub typeparams: Vec<Sym>,
}
impl Signature {
/// Returns a lambda typedef representing the signature
pub fn to_type(&self) -> Type {
let paramtypes = self.params.iter().map(|(_nm, ty)| ty.clone()).collect();
- Type::Func(
- paramtypes,
- Box::new(self.rettype.clone()),
- self.typeparams.clone(),
- )
+ let typeparams = self.typeparams.iter().map(|nm| Type::named0(*nm)).collect();
+ Type::Func(paramtypes, Box::new(self.rettype.clone()), typeparams)
}
/// Get all the generic params out of this function sig
- pub fn generic_type_names(&self) -> Vec<Sym> {
- self.to_type().get_type_params()
+ pub fn type_params(&self) -> Vec<Sym> {
+ self.to_type().get_toplevel_type_params()
}
/// Returns a string containing just the params and rettype bits of the sig
@@ 122,41 125,13 @@ impl Signature {
.collect();
let args = names.join(", ");
- let typenames: Vec<_> = self.typeparams.iter().map(|t| t.get_name()).collect();
+ let typenames: Vec<_> = self
+ .typeparams
+ .iter()
+ .map(|t| (t.val().as_str()).to_string())
+ .collect();
let typeargs = typenames.join(", ");
- format!("({} | {}) {}", args, typeargs, self.rettype.get_name())
- }
-
- /// Transforms this signature into a new type.
- ///
- /// Panics if the types are not compatible,
- /// ie, the given type is not a function with the
- /// same number of params.
- fn _map_type(&self, new_type: &Type) -> Self {
- match new_type {
- Type::Func(params, rettype, typeparams) => {
- if self.params.len() != params.len() {
- panic!("given type has wrong number of params");
- }
- if self.typeparams.len() != typeparams.len() {
- panic!("Given type has wrong number of type params");
- }
- let new_params = self
- .params
- .iter()
- .zip(params)
- .map(|((nm, _t1), t2)| (*nm, t2.clone()))
- .collect();
- let new_rettype = rettype.clone();
- let new_type_params = typeparams.clone();
- Self {
- params: new_params,
- rettype: *new_rettype,
- typeparams: new_type_params,
- }
- }
- _ => panic!("Not a function type: {:?}", new_type),
- }
+ format!("(|{}| {}) {}", typeargs, args, self.rettype.get_name())
}
}
@@ 196,6 171,10 @@ pub enum Expr {
Loop {
body: Vec<Expr>,
},
+ While {
+ cond: Box<Expr>,
+ body: Vec<Expr>,
+ },
Lambda {
signature: Signature,
body: Vec<Expr>,
@@ 266,9 245,13 @@ impl Expr {
}
/// Shortcut function for making literal integers of a known size
- pub const fn sized_int(i: i128, bytes: u8) -> Expr {
+ pub const fn sized_int(i: i128, bytes: u8, signed: bool) -> Expr {
Expr::Lit {
- val: Literal::SizedInteger { vl: i, bytes },
+ val: Literal::SizedInteger {
+ vl: i,
+ bytes,
+ signed,
+ },
}
}
M src/backend/rust.rs +65 -44
@@ 1,8 1,7 @@
//! Compile our HIR to Rust.
//! This is kinda silly, but hey why not.
//!
-//!
-//! Potential improvements:
+//! Potential optimizations:
//! * Use something smarter than strings to collect output -- could just
//! output to a stream like the formatter does. This is a little weird though 'cause
//! we often want to build pieces of code, and then combine them together at the end.
@@ 22,32 21,37 @@ use crate::*;
fn compile_typename(t: &Type) -> Cow<'static, str> {
use crate::Type::*;
match t {
- Prim(PrimType::SInt(16)) => "i128".into(),
- Prim(PrimType::SInt(8)) => "i64".into(),
- Prim(PrimType::SInt(4)) => "i32".into(),
- Prim(PrimType::SInt(2)) => "i16".into(),
- Prim(PrimType::SInt(1)) => "i8".into(),
- Prim(PrimType::SInt(e)) => {
- unreachable!("Invalid integer size: {}", e)
+ Prim(PrimType::Int(16, true)) => "i128".into(),
+ Prim(PrimType::Int(8, true)) => "i64".into(),
+ Prim(PrimType::Int(4, true)) => "i32".into(),
+ Prim(PrimType::Int(2, true)) => "i16".into(),
+ Prim(PrimType::Int(1, true)) => "i8".into(),
+ Prim(PrimType::Int(16, false)) => "u128".into(),
+ Prim(PrimType::Int(8, false)) => "u64".into(),
+ Prim(PrimType::Int(4, false)) => "u32".into(),
+ Prim(PrimType::Int(2, false)) => "u16".into(),
+ Prim(PrimType::Int(1, false)) => "u8".into(),
+ Prim(PrimType::Int(e, is_signed)) => {
+ unreachable!("Invalid integer size: {} (is_signed: {})", e, is_signed)
}
Prim(PrimType::UnknownInt) => {
unreachable!("Backend got an integer of unknown size, should never happen!")
}
Prim(PrimType::Bool) => "bool".into(),
- Prim(PrimType::AnyPtr) => format!("*const u8").into(),
+ Prim(PrimType::AnyPtr) => "*const u8".to_string().into(),
Never => unreachable!(),
Named(s, types) if s == &Sym::new("Tuple") => {
trace!("Compiling tuple {:?}...", t);
let mut accm = String::from("(");
for typ in types {
- accm += &compile_typename(&*typ);
+ accm += &compile_typename(typ);
accm += ", ";
}
accm += ")";
accm.into()
}
Func(params, rettype, typeparams) => {
- if typeparams.len() > 0 {
+ if !typeparams.is_empty() {
todo!("Function pointer types containing generics need monomorph to work");
}
let mut accm = String::from("fn ");
@@ 64,11 68,11 @@ fn compile_typename(t: &Type) -> Cow<'st
*/
accm += "(";
for p in params {
- accm += &compile_typename(&*p);
+ accm += &compile_typename(p);
accm += ", ";
}
accm += ") -> ";
- accm += &compile_typename(&*rettype);
+ accm += &compile_typename(rettype);
accm.into()
}
Struct(_fields, _generics) => {
@@ 91,7 95,7 @@ fn compile_typename(t: &Type) -> Cow<'st
// }
// accm += ")";
// accm.into()
- passes::generate_type_name(t).into()
+ passes::mangled_type_name(t).into()
}
Enum(_things) => {
// Construct names for anonymous enums by concat'ing the member
@@ 104,16 108,14 @@ fn compile_typename(t: &Type) -> Cow<'st
// accm += &*nm.val();
// }
// accm.into()
- passes::generate_type_name(t).into()
+ passes::mangled_type_name(t).into()
}
- Generic(s) => mangle_name(&*s.val()).into(),
Array(t, len) => format!("[{};{}]", compile_typename(t), len).into(),
Named(sym, generics) => {
- if generics.len() == 0 {
+ if generics.is_empty() {
format!("{}", sym).into()
} else {
- let generic_strings: Vec<_> =
- generics.iter().map(|t| compile_typename(t)).collect();
+ let generic_strings: Vec<_> = generics.iter().map(compile_typename).collect();
let args = generic_strings.join(", ");
format!("{}<{}>", sym, args).into()
}
@@ 135,6 137,10 @@ fn compile_typename(t: &Type) -> Cow<'st
//format!("SomeSum").into()
unimplemented!()
}
+ Uniq(t) => {
+ let res = compile_typename(t);
+ format!("&mut {}", res).into()
+ }
}
}
@@ 156,7 162,7 @@ pub(super) fn output(lir: &hir::Ir, tck:
/// TODO: There might be a better way to make lambda's un-nameable.
/// Probably, really.
fn mangle_name(s: &str) -> String {
- s.replace("!", "__")
+ s.replace('!', "__")
}
/// Compile a single decl
@@ 167,7 173,7 @@ fn compile_decl(w: &mut impl Write, decl
signature,
body,
} => {
- let nstr = mangle_name(&*INT.fetch(*name));
+ let nstr = mangle_name(&INT.fetch(*name));
let sstr = compile_fn_signature(signature);
let bstr = compile_exprs(body, ";\n", tck);
if body.iter().all(|expr| expr.is_const) {
@@ 184,7 190,7 @@ fn compile_decl(w: &mut impl Write, decl
init,
} => {
let nstr = mangle_name(&INT.fetch(*name));
- let tstr = compile_typename(&*typename);
+ let tstr = compile_typename(typename);
let istr = compile_expr(init, tck);
writeln!(w, "const {}: {} = {};", nstr, tstr, istr)
}
@@ 206,13 212,13 @@ fn compile_decl(w: &mut impl Write, decl
params.is_empty(),
"Bruh you have generic params on an enum type"
);
- writeln!(w, "pub type {} = i32;", mangle_name(&*name.val()))?;
+ writeln!(w, "pub type {} = i32;", mangle_name(&name.val()))?;
Ok(())
}
Type::Sum(body, _generics) => {
- let nstr = mangle_name(&*name.val());
+ let nstr = mangle_name(&name.val());
let param_strings: Vec<_> =
- params.iter().map(|sym| (&*sym.val()).clone()).collect();
+ params.iter().map(|sym| (*sym.val()).clone()).collect();
let args = param_strings.join(", ");
writeln!(w, "pub enum {}<{}> {{ ", nstr, args)?;
for (nm, ty) in body {
@@ 223,17 229,17 @@ fn compile_decl(w: &mut impl Write, decl
}
// For everything else we just make a fairly literal alias to the existing type.
_other => {
- let nstr = mangle_name(&*name.val());
+ let nstr = mangle_name(&name.val());
let tstr = compile_typename(typedecl);
//writeln!(w, "pub struct {}({});", nstr, tstr)
// TODO: <> is a valid generic param decl in Rust
- if params.len() == 0 {
+ if params.is_empty() {
writeln!(w, "pub type {} = {};", nstr, tstr)
} else {
let param_strings: Vec<_> =
- params.iter().map(|sym| (&*sym.val()).clone()).collect();
+ params.iter().map(|sym| (*sym.val()).clone()).collect();
let args = param_strings.join(", ");
- writeln!(w, "pub type {}<{}> = {};", nstr, args, tstr).into()
+ writeln!(w, "pub type {}<{}> = {};", nstr, args, tstr)
}
}
}
@@ 245,12 251,12 @@ fn compile_decl(w: &mut impl Write, decl
/// Compile a function signature
fn compile_fn_signature(sig: &ast::Signature) -> String {
let mut accm = String::from("");
- let generics = sig.generic_type_names();
+ let generics = sig.type_params();
- if generics.len() > 0 {
+ if !generics.is_empty() {
accm += "<";
for generic in generics.iter() {
- accm += &mangle_name(&*generic.val());
+ accm += &mangle_name(&generic.val());
accm += ", ";
}
accm += ">";
@@ 259,15 265,15 @@ fn compile_fn_signature(sig: &ast::Signa
for (varsym, typesym) in sig.params.iter() {
accm += &*INT.fetch(*varsym);
accm += ": ";
- accm += &compile_typename(&typesym);
+ accm += &compile_typename(typesym);
accm += ", ";
}
accm += ") -> ";
accm += &compile_typename(&sig.rettype);
- if generics.len() > 0 {
+ if !generics.is_empty() {
accm += " where ";
for generic in generics.iter() {
- accm += &mangle_name(&*generic.val());
+ accm += &mangle_name(&generic.val());
accm += ": Copy,";
}
}
@@ 328,7 334,6 @@ fn contains_anyptr(t: &Type) -> bool {
Struct(_body, _generics) => todo!(),
Sum(_body, _generics) => todo!(),
Array(t, _) => contains_anyptr(t),
- Generic(_) => unreachable!(),
_ => false,
}
}
@@ 344,12 349,28 @@ fn compile_expr(expr: &hir::ExprNode, tc
val: ast::Literal::Bool(b),
} => format!("{}", b),
E::Lit {
- val: ast::Literal::SizedInteger { vl, bytes },
+ val:
+ ast::Literal::SizedInteger {
+ vl,
+ bytes,
+ signed: true,
+ },
} => {
let bits = bytes * 8;
format!("{}i{}", vl, bits)
}
- E::Var { name, .. } => mangle_name(&*INT.fetch(*name)),
+ E::Lit {
+ val:
+ ast::Literal::SizedInteger {
+ vl,
+ bytes,
+ signed: false,
+ },
+ } => {
+ let bits = bytes * 8;
+ format!("{}u{}", vl, bits)
+ }
+ E::Var { name, .. } => mangle_name(&INT.fetch(*name)),
E::BinOp { op, lhs, rhs } => format!(
"({} {} {})",
compile_expr(lhs, tck),
@@ 366,7 387,7 @@ fn compile_expr(expr: &hir::ExprNode, tc
init,
mutable,
} => {
- let vstr = mangle_name(&*INT.fetch(*varname));
+ let vstr = mangle_name(&INT.fetch(*varname));
// typename may be elided, so we get the real type from the tck
// TODO: Someday this should just be filled in by a lowering pass
let type_id = tck.get_expr_type(init);
@@ 392,7 413,7 @@ fn compile_expr(expr: &hir::ExprNode, tc
}
accm += &compile_expr(cond, tck);
accm += " {\n";
- accm += &compile_exprs(&body, ";\n", tck);
+ accm += &compile_exprs(body, ";\n", tck);
accm += "} \n";
}
accm += "else {\n";
@@ 433,7 454,7 @@ fn compile_expr(expr: &hir::ExprNode, tc
format!("return {};", compile_expr(retval, tck))
}
// Unit type
- E::TupleCtor { body } if body.len() == 0 => String::from(" ()\n"),
+ E::TupleCtor { body } if body.is_empty() => String::from(" ()\n"),
E::TupleCtor { body } => {
let contents = compile_exprs(body, ",", tck);
format!("({},)", contents)
@@ 445,9 466,8 @@ fn compile_expr(expr: &hir::ExprNode, tc
body,
type_params: _,
} => {
- let contents = compile_expr(body, tck);
//format!("{}({})", &*name.val(), contents)
- format!("{}", contents)
+ compile_expr(body, tck)
}
E::StructCtor { body } => {
/*
@@ 526,6 546,7 @@ fn compile_expr(expr: &hir::ExprNode, tc
} => {
format!("{}", value)
}
+ E::Ref { expr } => format!("&mut {}", compile_expr(expr, tck)),
other => todo!("{:?}", other),
};
expr_str
M src/bin/garnetc.rs +5 -5
@@ 4,7 4,7 @@ use std::io;
use std::path::{Path, PathBuf};
use argh::FromArgs;
-use pretty_env_logger;
+
use garnet::backend::Backend;
@@ 47,11 47,11 @@ fn compile_rust(input_file: &Path, exe_n
let mut rust_file;
// Output to file
{
- let src = std::fs::read_to_string(&input_file)?;
- let output = garnet::compile(&input_file.to_str().unwrap(), &src, Backend::Rust);
+ let src = std::fs::read_to_string(input_file)?;
+ let output = garnet::compile(input_file.to_str().unwrap(), &src, Backend::Rust);
rust_file = input_file.to_owned();
rust_file.set_extension("rs");
- std::fs::write(&rust_file, &output)?;
+ std::fs::write(&rust_file, output)?;
}
use std::process::{Command, Stdio};
// Invoke rustc
@@ 61,7 61,7 @@ fn compile_rust(input_file: &Path, exe_n
.arg("-C")
.arg("opt-level=3")
.arg("-o")
- .arg(&exe_name)
+ .arg(exe_name)
.arg(&rust_file)
.output()
.expect("Failed to execute rustc");
M src/bin/garnetfmt.rs +4 -4
@@ 1,10 1,10 @@
//! A code formatter.
+//! Very simple and dumb.
use std::io::{self, Cursor};
use std::path::PathBuf;
use argh::FromArgs;
-use pretty_env_logger;
/// Garnet formatter
#[derive(Debug, FromArgs)]
@@ 28,7 28,7 @@ fn main() -> io::Result<()> {
let src = std::fs::read_to_string(&opt.file)?;
let filename = &opt.file.to_string_lossy();
let ast = {
- let mut parser = parser::Parser::new(&filename, &src);
+ let mut parser = parser::Parser::new(filename, &src);
parser.parse()
};
@@ 42,11 42,11 @@ fn main() -> io::Result<()> {
let formatted_str = String::from_utf8_lossy(formatted_data);
println!("{}", formatted_str);
let formatted_ast = {
- let mut parser = parser::Parser::new(&filename, &formatted_str);
+ let mut parser = parser::Parser::new(filename, &formatted_str);
parser.parse()
};
if &ast != &formatted_ast {
- // we want more info here
+ // we want more info here
eprintln!("Error, reformatted AST parses differently from original");
eprintln!("BEFORE:\n{}", src);
eprintln!("AST: {:#?}", &ast);
A => src/borrowck.rs +7 -0
@@ 0,0 1,7 @@
+use crate::hir;
+use crate::typeck;
+
+pub fn borrowck(_ir: &hir::Ir, _tck: &typeck::Tck) -> Result<(), String> {
+ // Everything's fine, trust me.
+ Ok(())
+}
M src/builtins.rs +158 -100
@@ 16,12 16,150 @@ pub struct Builtin {
pub sig: Type,
/// The implmentation of the builtin as raw code for each particular
/// backend.
- pub code: BTreeMap<Backend, &'static str>,
+ pub code: BTreeMap<Backend, String>,
}
pub static BUILTINS: Lazy<Vec<Builtin>> = Lazy::new(Builtin::all);
impl Builtin {
+ /// Generate all appropriate methods for the given numeric type.
+ /// Right now we just stick em in the toplevel with constructed names,
+ /// but eventually they'll have to go into their own module/package/whatever.
+ /// That package will still contain these generated strings though, so oh well.
+ ///
+ /// I guess this is the start of Garnet's macro system, huh?
+ ///
+ /// TODO: rotates, arithmetic shift, wrapping stuff, other?
+ fn generate_numerics_for(name: &str, ty: Type) -> Vec<Builtin> {
+ // names come from luajit I suppose
+ // I'm not in love wtih 'em but also don't want to think about em.
+ let println = format!(
+ r#"
+fn __println_{name}(x: {name}) {{
+ println!("{{}}", x);
+}}"#
+ );
+ let band = format!(
+ r#"
+fn __band_{name}(x: {name}, y: {name}) -> {name} {{
+ x & y
+}}"#
+ );
+ let bor = format!(
+ r#"
+fn __bor_{name}(x: {name}, y: {name}) -> {name} {{
+ x | y
+}}"#
+ );
+ let bxor = format!(
+ r#"
+fn __bxor_{name}(x: {name}, y: {name}) -> {name} {{
+ x ^ y
+}}"#
+ );
+ let bnot = format!(
+ r#"
+fn __bnot_{name}(x: {name}) -> {name} {{
+ !x
+}}"#
+ );
+
+ let rshift = format!(
+ r#"
+fn __rshift_{name}(x: {name}, i: {name}) -> {name} {{
+ x >> i
+}}"#
+ );
+ let lshift = format!(
+ r#"
+fn __lshift_{name}(x: {name}, i: {name}) -> {name} {{
+ x << i
+}}"#
+ );
+ let rol = format!(
+ r#"
+fn __rol_{name}(x: {name}, i: i32) -> {name} {{
+ x.rotate_left(i as u32)
+}}"#
+ );
+ let ror = format!(
+ r#"
+fn __ror_{name}(x: {name}, i: i32) -> {name} {{
+ x.rotate_right(i as u32)
+}}"#
+ );
+
+ let cast_i32 = format!(
+ r#"
+fn __{name}_to_i32(x: {name}) -> i32 {{
+ x as i32
+}}"#
+ );
+ let cast_u32 = format!(
+ r#"
+fn __{name}_to_u32(x: {name}) -> u32 {{
+ x as u32
+}}"#
+ );
+
+ vec![
+ Builtin {
+ name: Sym::new(format!("__println_{name}")),
+ sig: Type::Func(vec![ty.clone()], Box::new(Type::unit()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, println)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__band_{name}")),
+ sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, band)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__bor_{name}")),
+ sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bor)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__bxor_{name}")),
+ sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bxor)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__bnot_{name}")),
+ sig: Type::Func(vec![ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, bnot)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__rshift_{name}")),
+ sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, rshift)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__lshift_{name}")),
+ sig: Type::Func(vec![ty.clone(), ty.clone()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, lshift)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__rol_{name}")),
+ sig: Type::Func(vec![ty.clone(), Type::i32()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, rol)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__ror_{name}")),
+ sig: Type::Func(vec![ty.clone(), Type::i32()], Box::new(ty.clone()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, ror)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__{name}_to_i32")),
+ sig: Type::Func(vec![ty.clone()], Box::new(Type::i32()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, cast_i32)]),
+ },
+ Builtin {
+ name: Sym::new(format!("__{name}_to_u32")),
+ sig: Type::Func(vec![ty.clone()], Box::new(Type::u32()), vec![]),
+ code: BTreeMap::from([(Backend::Null, "".into()), (Backend::Rust, cast_u32)]),
+ },
+ ]
+ }
/// A function that returns all the compiler builtin info. Just
/// use the `BUILTINS` global instead, this is basically here
/// to initialize it.
@@ 34,112 172,32 @@ fn __println_bool(x: bool) {
println!("{}", x);
}"#;
- let rust_println_i64 = r#"
-fn __println_i64(x: i64) {
- println!("{}", x);
-}"#;
- let rust_println_i16 = r#"
-fn __println_i16(x: i16) {
- println!("{}", x);
-}"#;
-
- let band = r#"
-fn __band(x: i32, y: i32) -> i32 {
- x & y
-}"#;
- let bor = r#"
-fn __bor(x: i32, y: i32) -> i32 {
- x | y
-}"#;
- let bxor = r#"
-fn __bxor(x: i32, y: i32) -> i32 {
- x ^ y
-}"#;
- let bnot = r#"
-fn __bnot(x: i32) -> i32 {
- !x
-}"#;
-
- let rshift = r#"
-fn __rshift(x: i32, i: i32) -> i32 {
- x >> i
-}"#;
- let lshift = r#"
-fn __lshift(x: i32, i: i32) -> i32 {
- x << i
-}"#;
- vec![
+ let mut funcs = vec![
Builtin {
name: Sym::new("__println"),
sig: Type::Func(vec![Type::i32()], Box::new(Type::unit()), vec![]),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println)]),
+ code: BTreeMap::from([
+ (Backend::Null, "".into()),
+ (Backend::Rust, rust_println.into()),
+ ]),
},
Builtin {
name: Sym::new("__println_bool"),
sig: Type::Func(vec![Type::bool()], Box::new(Type::unit()), vec![]),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_bool)]),
- },
- Builtin {
- name: Sym::new("__println_i64"),
- sig: Type::Func(vec![Type::i64()], Box::new(Type::unit()), vec![]),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_i64)]),
- },
- Builtin {
- name: Sym::new("__println_i16"),
- sig: Type::Func(vec![Type::i16()], Box::new(Type::unit()), vec![]),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rust_println_i16)]),
- },
- // names come from luajit I suppose
- Builtin {
- name: Sym::new("__band"),
- sig: Type::Func(
- vec![Type::i32(), Type::i32()],
- Box::new(Type::i32()),
- vec![],
- ),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, band)]),
- },
- Builtin {
- name: Sym::new("__bor"),
- sig: Type::Func(
- vec![Type::i32(), Type::i32()],
- Box::new(Type::i32()),
- vec![],
- ),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bor)]),
+ code: BTreeMap::from([
+ (Backend::Null, "".into()),
+ (Backend::Rust, rust_println_bool.into()),
+ ]),
},
- Builtin {
- name: Sym::new("__bxor"),
- sig: Type::Func(
- vec![Type::i32(), Type::i32()],
- Box::new(Type::i32()),
- vec![],
- ),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bxor)]),
- },
- Builtin {
- name: Sym::new("__bnot"),
- sig: Type::Func(vec![Type::i32()], Box::new(Type::i32()), vec![]),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, bnot)]),
- },
- Builtin {
- name: Sym::new("__rshift"),
- sig: Type::Func(
- vec![Type::i32(), Type::i32()],
- Box::new(Type::i32()),
- vec![],
- ),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, rshift)]),
- },
- Builtin {
- name: Sym::new("__lshift"),
- sig: Type::Func(
- vec![Type::i32(), Type::i32()],
- Box::new(Type::i32()),
- vec![],
- ),
- code: BTreeMap::from([(Backend::Null, ""), (Backend::Rust, lshift)]),
- },
- ]
+ ];
+ funcs.extend(Self::generate_numerics_for("i8", Type::i8()));
+ funcs.extend(Self::generate_numerics_for("i16", Type::i16()));
+ funcs.extend(Self::generate_numerics_for("i32", Type::i32()));
+ funcs.extend(Self::generate_numerics_for("i64", Type::i64()));
+ funcs.extend(Self::generate_numerics_for("u8", Type::u8()));
+ funcs.extend(Self::generate_numerics_for("u16", Type::u16()));
+ funcs.extend(Self::generate_numerics_for("u32", Type::u32()));
+ funcs.extend(Self::generate_numerics_for("u64", Type::u64()));
+ funcs
}
}
M src/format.rs +32 -22
@@ 33,20 33,25 @@ fn unparse_decl(d: &Decl, out: &mut dyn
doc_comment,
} => {
for line in doc_comment.iter() {
- write!(out, "--- {}", line)?;
+ write!(out, "---{}", line)?;
}
let name = name.val();
let tname = typename.get_name();
write!(out, "const {} {} = ", name, tname)?;
unparse_expr(init, 0, out)
}
- Decl::TypeDef { name, params, typedecl, doc_comment } => {
+ Decl::TypeDef {
+ name,
+ params,
+ typedecl,
+ doc_comment,
+ } => {
for line in doc_comment.iter() {
- write!(out, "--- {}", line)?;
+ write!(out, "---{}", line)?;
}
let name = name.val();
let tname = typedecl.get_name();
- if params.len() == 0 {
+ if params.is_empty() {
writeln!(out, "type {} = {}", name, tname)?;
} else {
let mut paramstr = String::from("");
@@ 57,13 62,11 @@ fn unparse_decl(d: &Decl, out: &mut dyn
} else {
first = false;
}
- paramstr += "@";
paramstr += &*t.val();
}
writeln!(out, "type {}({}) = {}", name, paramstr, tname)?;
}
writeln!(out)
-
}
Decl::Import { name, rename } => {
if let Some(re) = rename {
@@ 87,11 90,11 @@ fn unparse_sig(sig: &Signature, out: &mu
} else {
first = false;
}
- write!(out, "{}", name.get_name())?;
+ write!(out, "{}", name)?;
}
- write!(out, "| ")?;
+ write!(out, "| ")?;
}
-
+
// Write (foo I32, bar I16)
// not (foo I32, bar I16, )
let mut first = true;
@@ 197,10 200,10 @@ fn unparse_expr(e: &Expr, indent: usize,
writeln!(out)
}
E::If { cases, falseblock } => {
- assert!(cases.len() >= 1);
+ assert!(!cases.is_empty());
let first_case = &cases[0];
write!(out, "if ")?;
- unparse_expr(&*first_case.condition, 0, out)?;
+ unparse_expr(&first_case.condition, 0, out)?;
writeln!(out, " then")?;
unparse_exprs(&first_case.body, indent + 1, out)?;
@@ 224,6 227,13 @@ fn unparse_expr(e: &Expr, indent: usize,
unparse_exprs(body, indent + 1, out)?;
writeln!(out, "end")
}
+ E::While { cond, body } => {
+ write!(out, "while ")?;
+ unparse_expr(cond, 0, out)?;
+ writeln!(out, " do")?;
+ unparse_exprs(body, indent + 1, out)?;
+ writeln!(out, "end")
+ }
E::Lambda { signature, body } => {
write!(out, "fn")?;
unparse_sig(signature, out)?;
@@ 240,7 250,7 @@ fn unparse_expr(e: &Expr, indent: usize,
} => {
unparse_expr(func, 0, out)?;
write!(out, "(")?;
- if typeparams.len() > 0 {
+ if !typeparams.is_empty() {
write!(out, "|")?;
let mut first = true;
for t in typeparams {
@@ 293,35 303,35 @@ fn unparse_expr(e: &Expr, indent: usize,
write!(out, "}}")
}
E::ArrayRef { expr, idx } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, "[")?;
- unparse_expr(&*idx, 0, out)?;
+ unparse_expr(idx, 0, out)?;
write!(out, "]")
}
E::TupleRef { expr, elt } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, ".{}", elt)
}
E::StructRef { expr, elt } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, ".{}", elt.val())
}
E::TypeUnwrap { expr } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, "$")
}
E::Ref { expr } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, "&")
}
E::Deref { expr } => {
- unparse_expr(&*expr, indent, out)?;
+ unparse_expr(expr, indent, out)?;
write!(out, "^")
}
E::Assign { lhs, rhs } => {
- unparse_expr(&*lhs, 0, out)?;
+ unparse_expr(lhs, 0, out)?;
write!(out, " = ")?;
- unparse_expr(&*rhs, 0, out)
+ unparse_expr(rhs, 0, out)
}
E::ArrayCtor { body } => {
writeln!(out, "[")?;
@@ 337,7 347,7 @@ fn unparse_expr(e: &Expr, indent: usize,
/// Take the AST and produce a formatted string of source code.
pub fn unparse(ast: &Ast, out: &mut dyn io::Write) -> io::Result<()> {
for decl in ast.decls.iter() {
- if ast.module_docstring.len() > 0 {
+ if !ast.module_docstring.is_empty() {
writeln!(out, "--- {}", &ast.module_docstring)?;
}
writeln!(out)?;
M src/hir.rs +90 -44
@@ 99,7 99,7 @@ impl fmt::Display for Decl {
typ: typename,
init,
} => {
- write!(f, "const {}: {} = ", name, typename.get_name())?;
+ write!(f, "const {} {} = ", name, typename.get_name())?;
init.write(1, f)?;
writeln!(f)?;
}
@@ 108,7 108,9 @@ impl fmt::Display for Decl {
typedecl,
params,
} => {
- writeln!(f, "type {}({:?}) = {}", name, params, typedecl.get_name())?;
+ let param_str: Vec<String> = params.iter().map(|t| t.to_string()).collect();
+ let param_str = param_str.join(", ");
+ writeln!(f, "type {}({}) = {}", name, param_str, typedecl.get_name())?;
}
D::Import { name, localname } => {
writeln!(f, "import {} as {}", name, localname)?;
@@ 118,6 120,12 @@ impl fmt::Display for Decl {
}
}
+impl fmt::Display for Expr {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.write(0, f)
+ }
+}
+
impl ExprNode {
pub fn new(e: Expr) -> Self {
ExprNode {
@@ 255,14 263,12 @@ pub enum Expr {
e: ExprNode,
to: Type,
},
- /*
Deref {
expr: ExprNode,
},
Ref {
expr: ExprNode,
},
- */
}
impl Expr {
@@ 285,6 291,8 @@ impl Expr {
Self::TupleCtor { body: vec![] }
}
+ /// A fairly janky pretty-printer for IR, that is at least better
+ /// than using Debug on the raw structures.
pub fn write(&self, indent: usize, f: &mut dyn fmt::Write) -> fmt::Result {
use Expr::*;
for _ in 0..(indent * 2) {
@@ 322,7 330,7 @@ impl Expr {
let m = if *mutable { " mut" } else { "" };
let type_str = typename
.as_ref()
- .map(|inner| Cow::from(inner.get_name()))
+ .map(|inner| inner.get_name())
.unwrap_or(Cow::from(""));
write!(f, "(let {}{} {} = ", &*varname.val(), m, type_str)?;
init.write(0, f)?;
@@ 359,14 367,15 @@ impl Expr {
} => {
write!(f, "(funcall ")?;
func.write(0, f)?;
+ write!(f, " |")?;
+ for ty in type_params {
+ write!(f, "{}, ", ty.get_name())?;
+ }
+ write!(f, "|")?;
for b in params {
b.write(indent + 1, f)?;
write!(f, " ")?;
}
- write!(f, "|")?;
- for ty in type_params {
- write!(f, "{},", ty.get_name())?;
- }
write!(f, ")")?;
}
Break => {
@@ 382,7 391,7 @@ impl Expr {
variant,
value,
} => {
- write!(f, "(enumval {} {} {})", name, &*variant.val(), value)?;
+ write!(f, "(enumctor {} {} {})", name, &*variant.val(), value)?;
}
TupleCtor { body } => {
write!(f, "(tuple ")?;
@@ 419,11 428,15 @@ impl Expr {
}
TypeCtor {
name,
- type_params: _,
+ type_params,
body,
} => {
// TODO: type_params
- write!(f, "(typector {} ", name)?;
+ write!(f, "(typector |")?;
+ for ty in type_params {
+ write!(f, "{} ", ty.get_name())?;
+ }
+ write!(f, "| {} ", name)?;
body.write(0, f)?;
write!(f, ")")?;
}
@@ 461,6 474,16 @@ impl Expr {
e.write(0, f)?;
write!(f, ")")?;
}
+ Deref { expr } => {
+ write!(f, "(ref ")?;
+ expr.write(0, f)?;
+ write!(f, ")")?;
+ }
+ Ref { expr } => {
+ write!(f, "(ref ")?;
+ expr.write(0, f)?;
+ write!(f, ")")?;
+ }
}
Ok(())
}
@@ 469,23 492,20 @@ impl Expr {
impl ExprNode {
/// Shortcut function for making literal bools
pub fn bool(b: bool) -> Self {
- Self::new(Expr::Lit {
- val: Literal::Bool(b),
- })
+ Self::new(Expr::bool(b))
}
/// Shortcut function for making literal integers
pub fn int(i: i128) -> Self {
- Self::new(Expr::Lit {
- val: Literal::Integer(i),
- })
+ Self::new(Expr::int(i))
}
/// Shortcut function for making literal unit
pub fn unit() -> Self {
- Self::new(Expr::TupleCtor { body: vec![] })
+ Self::new(Expr::unit())
}
}
+
/// A top-level declaration in the source file.
/// Like ExprNode, contains a type annotation.
#[derive(Debug, Clone, PartialEq)]
@@ 537,12 557,11 @@ pub fn lower(ast: &ast::Ast) -> Ir {
lower_decl(&mut accm, d)
}
- let i = Ir {
+ Ir {
decls: accm,
filename: ast.filename.clone(),
modulename: ast.modulename.clone(),
- };
- i
+ }
}
fn lower_lit(lit: &ast::Literal) -> Literal {
@@ 562,7 581,7 @@ fn lower_signature(sig: &ast::Signature)
}
/// This is the biggie currently
-fn lower_expr(expr: &ast::Expr) -> ExprNode {
+pub fn lower_expr(expr: &ast::Expr) -> ExprNode {
use ast::Expr as E;
use Expr::*;
let new_exp = match expr {
@@ 586,7 605,7 @@ fn lower_expr(expr: &ast::Expr) -> ExprN
UniOp { op: nop, rhs: nrhs }
}
E::Block { body } => {
- let nbody = body.iter().map(|e| lower_expr(e)).collect();
+ let nbody = body.iter().map(lower_expr).collect();
Block { body: nbody }
}
E::Let {
@@ 605,20 624,20 @@ fn lower_expr(expr: &ast::Expr) -> ExprN
}
E::If { cases, falseblock } => {
// One of the actual transformations, this makes all if/else statements
- // into essentially a switch: `if ... else if ... else if ... else if true ... end`
+ // into essentially a cond: `if ... else if ... else if ... else if true ... end`
// This is more consistent and easier to handle for typechecking.
assert!(!cases.is_empty(), "Should never happen");
let mut cases: Vec<_> = cases
.iter()
- .map(|case| (lower_expr(&*case.condition), lower_exprs(&case.body)))
+ .map(|case| (lower_expr(&case.condition), lower_exprs(&case.body)))
.collect();
// Add the "else" case, which we can just make `else if true then...`
// No idea if this is a good idea, but it makes life easier right
// this instant, so. Hasn't bit me yet, so it's not a *bad* idea.
let nelse_case = ExprNode::bool(true);
// Empty false block becomes a false block that returns unit
- let false_exprs = if falseblock.len() == 0 {
- lower_exprs(&vec![ast::Expr::TupleCtor { body: vec![] }])
+ let false_exprs = if falseblock.is_empty() {
+ lower_exprs(&[ast::Expr::TupleCtor { body: vec![] }])
} else {
lower_exprs(falseblock)
};
@@ 629,6 648,25 @@ fn lower_expr(expr: &ast::Expr) -> ExprN
let nbody = lower_exprs(body);
Loop { body: nbody }
}
+ E::While { cond, body } => {
+ // While loops just get turned into a Loop containing
+ // `if not cond then break end`
+ let inverted_cond = E::UniOp {
+ op: UOp::Not,
+ rhs: cond.clone(),
+ };
+ let test = lower_expr(&inverted_cond);
+ let brk = vec![ExprNode::new(Break)];
+ // As per above, we need to always have an "else" end case
+ let else_case = ExprNode::bool(true);
+ let else_exprs = lower_exprs(&[ast::Expr::TupleCtor { body: vec![] }]);
+ let if_expr = ExprNode::new(If {
+ cases: vec![(test, brk), (else_case, else_exprs)],
+ });
+ let mut nbody = lower_exprs(body);
+ nbody.insert(0, if_expr);
+ Loop { body: nbody }
+ }
E::Lambda { signature, body } => {
let nsig = lower_signature(signature);
let nbody = lower_exprs(body);
@@ 689,8 727,12 @@ fn lower_expr(expr: &ast::Expr) -> ExprN
E::TypeUnwrap { expr } => Expr::TypeUnwrap {
expr: lower_expr(expr),
},
- E::Deref { expr: _ } => todo!(),
- E::Ref { expr: _ } => todo!(),
+ E::Deref { expr } => Expr::Deref {
+ expr: lower_expr(expr),
+ },
+ E::Ref { expr } => Expr::Ref {
+ expr: lower_expr(expr),
+ },
E::Assign { lhs, rhs } => Expr::Assign {
lhs: lower_expr(lhs),
rhs: lower_expr(rhs),
@@ 703,9 745,10 @@ fn lower_expr(expr: &ast::Expr) -> ExprN
}
/// handy shortcut to lower Vec<ast::Expr>
-fn lower_exprs(exprs: &[ast::Expr]) -> Vec<ExprNode> {
- exprs.iter().map(|e| lower_expr(e)).collect()
+pub fn lower_exprs(exprs: &[ast::Expr]) -> Vec<ExprNode> {
+ exprs.iter().map(lower_expr).collect()
}
+
fn lower_typedef(accm: &mut Vec<Decl>, name: Sym, ty: &Type, params: &[Sym]) {
use Decl::*;
match ty {
@@ 749,6 792,7 @@ fn lower_typedef(accm: &mut Vec<Decl>, n
};
accm.push(new_constdef);
}
+
// For `type Foo = sum X {}, Y Thing end`
// synthesize
// const Foo = {
@@ 761,27 805,29 @@ fn lower_typedef(accm: &mut Vec<Decl>, n
// type Y = Thing
// TODO: What do we do with the generics... Right now they just
// get stuffed into the constructor functions verbatim.
- Type::Sum(body, generics) => {
+ Type::Sum(body, type_params) => {
trace!("Lowering sum type {}", name);
+ let new_type_params = Type::detype_names(type_params);
+
let struct_body: Vec<_> = body
.iter()
.map(|(variant_name, variant_type)| {
let paramname = Sym::new("x");
let signature = ast::Signature {
params: vec![(paramname, variant_type.clone())],
- rettype: Type::Named(name, generics.clone()),
- typeparams: generics.clone(),
+ rettype: Type::Named(name, type_params.clone()),
+ typeparams: new_type_params.clone(),
};
// Just return the value passed to it wrapped
// in a constructor of some kind...?
let body = vec![ExprNode::new(Expr::SumCtor {
- name: name,
+ name,
variant: *variant_name,
body: ExprNode::new(Expr::Var { name: paramname }),
})];
let e = ExprNode::new(Expr::Lambda { signature, body });
//println!("{} is {:#?}", variant_name, e);
- (variant_name.clone(), e)
+ (*variant_name, e)
})
.collect();
let init_val = ExprNode::new(Expr::StructCtor { body: struct_body });
@@ 794,13 840,13 @@ fn lower_typedef(accm: &mut Vec<Decl>, n
*variant_name,
Type::Func(
vec![variant_type.clone()],
- Box::new(Type::Named(name, generics.clone())),
- generics.clone(),
+ Box::new(Type::Named(name, type_params.clone())),
+ type_params.clone(),
),
)
})
.collect();
- let struct_type = Type::Struct(struct_typebody, generics.clone());
+ let struct_type = Type::Struct(struct_typebody, type_params.clone());
let new_constdef = Const {
name: name.to_owned(),
typ: struct_type,
@@ 813,16 859,16 @@ fn lower_typedef(accm: &mut Vec<Decl>, n
other => {
let s = Sym::new("x");
trace!("Lowering params {:?}", params);
- let type_params: Vec<_> = params.iter().map(|s| Type::Generic(*s)).collect();
+ let type_params: Vec<_> = params.iter().map(|s| Type::named0(*s)).collect();
let signature = ast::Signature {
params: vec![(s, other.clone())],
rettype: Type::Named(name.to_owned(), type_params.clone()),
- typeparams: type_params.clone(),
+ typeparams: params.to_owned(),
};
// The generated function just returns the value passed to it wrapped
// in a type constructor
let body = vec![ExprNode::new(Expr::TypeCtor {
- name: name.into(),
+ name,
type_params,
body: ExprNode::new(Expr::Var { name: s }),
})];
@@ 872,7 918,7 @@ fn lower_decl(accm: &mut Vec<Decl>, decl
doc_comment: _,
params,
} => {
- lower_typedef(accm, *name, typedecl, ¶ms);
+ lower_typedef(accm, *name, typedecl, params);
accm.push(Decl::TypeDef {
name: *name,
params: params.clone(),
M src/intern.rs +1 -1
@@ 38,7 38,7 @@ where
// Apparently I'm not smart enough to use entry() currently.
let mut data = self.data.write().unwrap();
let mut map = self.map.write().unwrap();
- if let Some(sym) = map.get(&*s) {
+ if let Some(sym) = map.get(s) {
// We have it, great
*sym
} else {
M src/lib.rs +160 -192
@@ 1,4 1,4 @@
-//! Garnet compiler guts.
+//! Garnet compiler driver functions and utility funcs.
//#![deny(missing_docs)]
@@ 12,12 12,14 @@ use once_cell::sync::Lazy;
mod ast;
pub mod backend;
+pub mod borrowck;
mod builtins;
pub mod format;
pub mod hir;
mod intern;
pub mod parser;
pub mod passes;
+pub mod symtbl;
pub mod typeck;
/// The interner. It's the ONLY part we have to actually
@@ 29,7 31,8 @@ static INT: Lazy<Cx> = Lazy::new(Cx::new
/// and can't contain other types, so.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum PrimType {
- SInt(u8),
+ /// size in bytes, is-signed
+ Int(u8, bool),
UnknownInt,
Bool,
/// erased type, currently unused
@@ 39,12 42,20 @@ pub enum PrimType {
impl PrimType {
fn get_name(&self) -> Cow<'static, str> {
match self {
- PrimType::SInt(16) => Cow::Borrowed("I128"),
- PrimType::SInt(8) => Cow::Borrowed("I64"),
- PrimType::SInt(4) => Cow::Borrowed("I32"),
- PrimType::SInt(2) => Cow::Borrowed("I16"),
- PrimType::SInt(1) => Cow::Borrowed("I8"),
- PrimType::SInt(s) => panic!("Undefined integer size {}!", s),
+ PrimType::Int(16, true) => Cow::Borrowed("I128"),
+ PrimType::Int(8, true) => Cow::Borrowed("I64"),
+ PrimType::Int(4, true) => Cow::Borrowed("I32"),
+ PrimType::Int(2, true) => Cow::Borrowed("I16"),
+ PrimType::Int(1, true) => Cow::Borrowed("I8"),
+ PrimType::Int(16, false) => Cow::Borrowed("U128"),
+ PrimType::Int(8, false) => Cow::Borrowed("U64"),
+ PrimType::Int(4, false) => Cow::Borrowed("U32"),
+ PrimType::Int(2, false) => Cow::Borrowed("U16"),
+ PrimType::Int(1, false) => Cow::Borrowed("U8"),
+ PrimType::Int(s, is_signed) => {
+ let prefix = if *is_signed { "I" } else { "U" };
+ panic!("Undefined integer size {}{}!", prefix, s);
+ }
PrimType::UnknownInt => Cow::Borrowed("{number}"),
PrimType::Bool => Cow::Borrowed("Bool"),
PrimType::AnyPtr => Cow::Borrowed("AnyPtr"),
@@ 56,7 67,7 @@ impl PrimType {
///
/// TODO someday: We should make a consistent and very good
/// name-mangling scheme for types, will make some backend stuff
-/// simpler.
+/// simpler. Also see passes::generate_type_name.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Type {
/// Primitive type with no subtypes
@@ 80,8 91,8 @@ pub enum Type {
Sum(BTreeMap<Sym, Type>, Vec<Type>),
/// Arrays are just a type and a number.
Array(Box<Type>, usize),
- /// A generic type parameter that has been given an explicit name.
- Generic(Sym),
+ /// Unique borrow
+ Uniq(Box<Type>),
}
impl Type {
@@ 93,62 104,39 @@ impl Type {
/// and it's actually really nice. And also fiddly af so touching it
/// breaks lots of things that currently work.
/// plz unfuck this, it's not THAT hard.
- fn get_type_params(&self) -> Vec<Sym> {
- fn helper(t: &Type, accm: &mut Vec<Sym>) {
- match t {
- Type::Prim(_) => (),
- Type::Never => (),
- Type::Enum(_ts) => (),
- Type::Named(_, generics) => {
- for g in generics {
- helper(g, accm);
- }
- }
- Type::Func(args, rettype, typeparams) => {
- for t in args {
- helper(t, accm);
- }
- for t in typeparams {
- helper(t, accm);
- }
- helper(rettype, accm)
- }
- Type::Struct(body, generics) => {
- for (_, ty) in body {
- helper(ty, accm);
- }
- for g in generics {
- helper(g, accm);
+ fn get_toplevel_type_params(&self) -> Vec<Sym> {
+ fn get_toplevel_names(t: &[Type]) -> Vec<Sym> {
+ t.iter()
+ .flat_map(|t| match t {
+ Type::Named(nm, generics) => {
+ assert!(generics.len() == 0);
+ Some(*nm)
}
- }
- Type::Sum(body, generics) => {
- for (_, ty) in body {
- helper(ty, accm);
- }
- for g in generics {
- helper(g, accm);
- }
- }
- Type::Array(ty, _size) => {
- helper(ty, accm);
- }
- Type::Generic(s) => {
- // Deduplicating these things while maintaining ordering
- // is kinda screwy.
- // This works, it's just, yanno, also O(n^2)
- // Could use a set to check membership , but fuckit for now.
- if accm.contains(s) {
- //todo!("This is probably always wrong 'cause type param names will shadow each other, but, uh...")
- } else {
- accm.push(*s)
- }
- }
+ _ => None,
+ })
+ .collect()
+ }
+
+ let params = match self {
+ Type::Prim(_) => vec![],
+ Type::Never => vec![],
+ Type::Enum(_ts) => vec![],
+ Type::Named(_, typeparams) => get_toplevel_names(typeparams),
+ Type::Func(_args, _rettype, typeparams) => get_toplevel_names(typeparams),
+ Type::Struct(_body, typeparams) => get_toplevel_names(typeparams),
+ Type::Sum(_body, typeparams) => get_toplevel_names(typeparams),
+ Type::Array(_ty, _size) => {
+ // BUGGO: What to do here?????
+ // Arrays and ptrs kiiiiinda have type params, but only
+ // one???
+ vec![]
}
- }
- let mut accm = vec![];
- helper(self, &mut accm);
- //trace!("Found type params for {:?}: {:?}", self, accm);
- accm
+ Type::Uniq(_ty) => {
+ // BUGGO: What to do here?????
+ vec![]
+ }
+ };
+ params
}
/// Shortcut for getting the type for an unknown int
@@ 156,9 144,14 @@ impl Type {
Self::Prim(PrimType::UnknownInt)
}
- /// Shortcut for getting the Type for an int of a particular size
+ /// Shortcut for getting the Type for a signed int of a particular size
pub fn isize(size: u8) -> Self {
- Self::Prim(PrimType::SInt(size))
+ Self::Prim(PrimType::Int(size, true))
+ }
+
+ /// Shortcut for getting the Type for an unsigned signed int of a particular size
+ pub fn usize(size: u8) -> Self {
+ Self::Prim(PrimType::Int(size, false))
}
/// Shortcut for getting the Type for I128
@@ 186,6 179,31 @@ impl Type {
Self::isize(1)
}
+ /// Shortcut for getting the Type for U128
+ pub fn u128() -> Self {
+ Self::usize(16)
+ }
+
+ /// Shortcut for getting the Type for U64
+ pub fn u64() -> Self {
+ Self::usize(8)
+ }
+
+ /// Shortcut for getting the Type for U32
+ pub fn u32() -> Self {
+ Self::usize(4)
+ }
+
+ /// Shortcut for getting the type for U16
+ pub fn u16() -> Self {
+ Self::usize(2)
+ }
+
+ /// Shortcut for getting the type for U8
+ pub fn u8() -> Self {
+ Self::usize(1)
+ }
+
/// Shortcut for getting the type for Bool
pub fn bool() -> Self {
Self::Prim(PrimType::Bool)
@@ 211,9 229,41 @@ impl Type {
Self::Array(Box::new(t.clone()), len)
}
+ /// Shortcut for a named type with no type params
+ // pub fn named0(s: impl AsRef<str>) -> Self {
+ pub fn named0(s: Sym) -> Self {
+ Type::Named(s, vec![])
+ }
+
+ /// Turns a bunch of Named types into a list of symbols.
+ /// Panics if it encounters a different type of type.
+ pub fn detype_names(ts: &[Type]) -> Vec<Sym> {
+ fn f(t: &Type) -> Sym {
+ match t {
+ Type::Named(nm, generics) => {
+ assert!(generics.len() == 0);
+ *nm
+ }
+ _ => panic!(
+ "Tried to get a Named type out of a {:?}, should never happen",
+ t
+ ),
+ }
+ }
+ ts.iter().map(f).collect()
+ }
+
+ fn function(params: &[Type], rettype: &Type, generics: &[Type]) -> Self {
+ Type::Func(
+ Vec::from(params),
+ Box::new(rettype.clone()),
+ Vec::from(generics),
+ )
+ }
+
fn _is_integer(&self) -> bool {
match self {
- Self::Prim(PrimType::SInt(_)) => true,
+ Self::Prim(PrimType::Int(_, _)) => true,
Self::Prim(PrimType::UnknownInt) => true,
_ => false,
}
@@ 235,6 285,11 @@ impl Type {
"I32" => Some(Type::i32()),
"I64" => Some(Type::i64()),
"I128" => Some(Type::i128()),
+ "U8" => Some(Type::u8()),
+ "U16" => Some(Type::u16()),
+ "U32" => Some(Type::u32()),
+ "U64" => Some(Type::u64()),
+ "U128" => Some(Type::u128()),
"Bool" => Some(Type::bool()),
"Never" => Some(Type::never()),
_ => None,
@@ 328,127 383,10 @@ impl Type {
let inner_name = body.get_name();
Cow::Owned(format!("[{}]{}", len, inner_name))
}
- Type::Generic(name) => Cow::Owned(format!("@{}", name)),
- }
- }
-
- /// Takes two types and creates/adds to a map of substitutions
- /// from generics in the first type to the corresponding concrete
- /// types in the second types.
- ///
- /// Panics if non-generic types don't match.
- ///
- /// TODO someday: refactor with passes::type_map()? Not sure how to make
- /// that walk over two types.
- fn _find_substs(&self, other: &Type, substitutions: &mut BTreeMap<Sym, Type>) {
- match (self, other) {
- // Types are identical, noop.
- (s, o) if s == o => (),
- (Type::Named(n1, args1), Type::Named(n2, args2)) if n1 == n2 => {
- for (p1, p2) in args1.iter().zip(args2) {
- p1._find_substs(p2, substitutions);
- }
- }
- (
- Type::Func(params1, rettype1, typeparams1),
- Type::Func(params2, rettype2, typeparams2),
- ) => {
- if params1.len() != params2.len() {
- panic!("subst for function had incorrect param length")
- }
- if typeparams1.len() != typeparams2.len() {
- panic!("subst for function had incorrect typeparam length")
- }
- if typeparams1.len() > 0 {
- todo!()
- }
- for (p1, p2) in params1.iter().zip(params2) {
- p1._find_substs(p2, substitutions);
- }
- rettype1._find_substs(rettype2, substitutions);
- }
- (Type::Struct(_, _), Type::Struct(_, _)) => {
- unreachable!("Actually can't happen I think, 'cause we tuple-ify everything first?")
- } /*
- (Type::Struct(body1, generics1), Type::Struct(body2, generics2)) => {
- if body1.len() != body2.len() || generics1.len() != generics2.len() {
- panic!("subst for function had incorrect body or generics length")
- }
- if
+ Type::Uniq(ty) => {
+ let inner = ty.get_name();
+ Cow::Owned(format!("&{}", inner))
}
- */
- (Type::Sum(body1, generics1), Type::Sum(body2, generics2)) => {
- if body1.len() != body2.len() || generics1.len() != generics2.len() {
- panic!("subst for sum type had non-matching body or generics length")
- }
- if !body1.keys().eq(body2.keys()) {
- panic!("subst for sum type had non-matching keys")
- }
- for ((_nm1, t1), (_nm2, t2)) in body1.iter().zip(body2) {
- t1._find_substs(t2, substitutions);
- }
-
- for (p1, p2) in generics1.iter().zip(generics2) {
- p1._find_substs(p2, substitutions);
- }
- }
- (Type::Array(t1, len1), Type::Array(t2, len2)) if len1 == len2 => {
- t1._find_substs(t2, substitutions);
- }
- (Type::Generic(nm), p2) => {
- // If we have an existing substitution, does it conflict?
- // Not 100% sure this handles generics right, but should work
- // for now.
- if let Some(other_ty) = substitutions.get(nm) {
- if other_ty != p2 {
- panic!("Conflicting subtitution");
- }
- } else {
- substitutions.insert(*nm, p2.clone());
- }
- }
- // Types are not identical, panic
- _ => panic!("Cannot substitute {:?} into {:?}", other, self),
- }
- }
-
- /// Takes a type and a map of substitutions and swaps out any generics
- /// with the substituted types.
- ///
- /// Panics if a generic type has no substitution.
- ///
- /// TODO someday: refactor with passes::type_map()?
- fn _apply_substs(&self, substs: &BTreeMap<Sym, Type>) -> Type {
- match self {
- Type::Func(params1, rettype1, typeparams1) => {
- let new_params = params1.iter().map(|p1| p1._apply_substs(substs)).collect();
- let new_rettype = rettype1._apply_substs(substs);
- if typeparams1.len() > 0 {
- todo!("Hsfjkdslfjs");
- }
- Type::Func(new_params, Box::new(new_rettype), vec![])
- }
- Type::Named(n1, args1) => {
- let new_args = args1.iter().map(|p1| p1._apply_substs(substs)).collect();
- Type::Named(*n1, new_args)
- }
- Type::Struct(_, _) => unreachable!("see other unreachable in substitute()"),
- Type::Sum(body, generics) => {
- let new_body = body
- .iter()
- .map(|(nm, ty)| (*nm, ty._apply_substs(substs)))
- .collect();
- let new_generics = generics.iter().map(|p1| p1._apply_substs(substs)).collect();
- Type::Sum(new_body, new_generics)
- }
- Type::Array(body, len) => Type::Array(Box::new(body._apply_substs(substs)), *len),
- Type::Generic(nm) => substs
- .get(&nm)
- .unwrap_or_else(|| panic!("No substitution found for generic named {}!", nm))
- .to_owned(),
- Type::Prim(_) => self.clone(),
- Type::Enum(_) => self.clone(),
- Type::Never => self.clone(),
}
}
}
@@ 535,7 473,7 @@ impl Cx {
/// Main driver function.
/// Compile a given source string to Rust source code, or return an error.
-/// TODO: Better parser errors with locations
+/// TODO: Better errors with locations
///
/// Parse -> lower to IR -> run transformation passes
/// -> typecheck -> more passes -> codegen
@@ 550,9 488,18 @@ pub fn try_compile(
};
let hir = hir::lower(&ast);
info!("HIR from AST lowering:\n{}", &hir);
+ let (hir, _symtbl) = symtbl::resolve_symbols(hir);
+ info!("HIR from symtbl renaming 1:\n{}", &hir);
let hir = passes::run_passes(hir);
- let tck = &mut typeck::typecheck(&hir)?;
- let hir = passes::run_typechecked_passes(hir, tck);
+ info!("HIR from first passes:\n{}", &hir);
+ // Symbol resolution has to happen (again???) after passes 'cause
+ // the passes may generate new code.
+ let (hir, mut symtbl) = symtbl::resolve_symbols(hir);
+ info!("HIR from symtbl renaming:\n{}", &hir);
+ // info!("Symtbl from AST:\n{:#?}", &symtbl);
+ let tck = &mut typeck::typecheck(&hir, &mut symtbl)?;
+ borrowck::borrowck(&hir, tck).unwrap();
+ let hir = passes::run_typechecked_passes(hir, &mut symtbl, tck);
info!("HIR after transform passes:\n{}", &hir);
Ok(backend::output(backend, &hir, tck))
}
@@ 562,6 509,27 @@ pub fn compile(filename: &str, src: &str
try_compile(filename, src, backend).unwrap_or_else(|e| panic!("Type check error: {}", e))
}
+/// Turns source code into HIR, panicking on any error.
+/// Useful for unit tests.
+#[cfg(test)]
+fn _compile_to_hir_expr(src: &str) -> hir::ExprNode {
+ let ast = {
+ let mut parser = parser::Parser::new("__None__", src);
+ let res = parser.parse_expr(0);
+ res.expect("input to compile_to_hir_expr had a syntax error!")
+ };
+ hir::lower_expr(&ast)
+}
+
+#[cfg(test)]
+fn _compile_to_hir_exprs(src: &str) -> Vec<hir::ExprNode> {
+ let ast = {
+ let mut parser = parser::Parser::new("__None__", src);
+ parser.parse_exprs()
+ };
+ hir::lower_exprs(&ast)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ 569,7 537,7 @@ mod tests {
#[test]
fn check_name_format() {
let args = vec![Type::i32(), Type::bool()];
- let def = Type::Func(args, Box::new(Type::i32()), vec![]);
+ let def = Type::function(&args, &Type::i32(), &vec![]);
let gotten_name = def.get_name();
let desired_name = "fn(I32, Bool) I32";
assert_eq!(&gotten_name, desired_name);
M src/parser.rs +159 -85
@@ 20,7 20,7 @@ use crate::*;
/// Checks whether the given value can fit in `int_size` number
/// of bits.
-fn bounds_check(val: i128, int_size: u8) -> Option<(i128, u8)> {
+fn bounds_check_signed(val: i128, int_size: u8) -> Option<(i128, u8)> {
let bound = 2_i128.pow(int_size as u32 * 8);
if val > (bound / 2) - 1 || val <= -(bound / 2) {
None
@@ 29,6 29,15 @@ fn bounds_check(val: i128, int_size: u8)
}
}
+fn bounds_check_unsigned(val: i128, int_size: u8) -> Option<(i128, u8)> {
+ let bound = 2_i128.pow(int_size as u32 * 8);
+ if val > bound || val <= 0 {
+ None
+ } else {
+ Some((val, int_size))
+ }
+}
+
/// Turn a valid number string into something Rust's `str::parse()` can parse,
/// so,
/// 123_456_I32 becomes 123456.
@@ 48,36 57,15 @@ fn extract_digits(s: &str) -> String {
.collect()
}
-fn make_i8(lex: &mut Lexer<TokenKind>) -> Option<(i128, u8)> {
- let digits = extract_digits(lex.slice());
- let m = digits.parse().ok()?;
- bounds_check(m, 1)
-}
-
-fn make_i16(lex: &mut Lexer<TokenKind>) -> Option<(i128, u8)> {
- let digits = extract_digits(lex.slice());
- let m = digits.parse().ok()?;
- bounds_check(m, 2)
-}
-
-fn make_i32(lex: &mut Lexer<TokenKind>) -> Option<(i128, u8)> {
+fn make_int(lex: &mut Lexer<TokenKind>, size: u8, signed: bool) -> Option<(i128, u8, bool)> {
let digits = extract_digits(lex.slice());
let m = digits.parse().ok()?;
- bounds_check(m, 4)
-}
-
-fn make_i64(lex: &mut Lexer<TokenKind>) -> Option<(i128, u8)> {
- let digits = extract_digits(lex.slice());
- let m = digits.parse().ok()?;
- bounds_check(m, 8)
-}
-
-fn make_i128(lex: &mut Lexer<TokenKind>) -> Option<(i128, u8)> {
- let digits = extract_digits(lex.slice());
- let m = digits.parse().ok()?;
- // No bounds check, since our internal type is i128 anyway.
- //bounds_check(m, 16)
- Some((m, 16))
+ let (val, size) = if signed {
+ bounds_check_signed(m, size)?
+ } else {
+ bounds_check_unsigned(m, size)?
+ };
+ Some((val, size, signed))
}
fn eat_block_comment(lex: &mut Lexer<TokenKind>) -> String {
@@ 114,16 102,17 @@ fn eat_block_comment(lex: &mut Lexer<Tok
pub enum TokenKind {
#[regex("[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().to_owned())]
Ident(String),
- //#[regex("@[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().to_owned())]
- //TypeIdent(String),
#[regex("true|false", |lex| lex.slice().parse())]
Bool(bool),
- #[regex("[0-9][0-9_]*I8", make_i8)]
- #[regex("[0-9][0-9_]*I16", make_i16)]
- #[regex("[0-9][0-9_]*I32", make_i32)]
- #[regex("[0-9][0-9_]*I64", make_i64)]
- #[regex("[0-9][0-9_]*I128", make_i128)]
- IntegerSize((i128, u8)),
+ #[regex("[0-9][0-9_]*I8", |lex| make_int(lex, 1, true))]
+ #[regex("[0-9][0-9_]*I16", |lex| make_int(lex, 2, true))]
+ #[regex("[0-9][0-9_]*I32", |lex| make_int(lex, 4, true))]
+ #[regex("[0-9][0-9_]*I64", |lex| make_int(lex, 8, true))]
+ #[regex("[0-9][0-9_]*U8", |lex| make_int(lex, 1, false))]
+ #[regex("[0-9][0-9_]*U16", |lex| make_int(lex, 2, false))]
+ #[regex("[0-9][0-9_]*U32", |lex| make_int(lex, 4, false))]
+ #[regex("[0-9][0-9_]*U64", |lex| make_int(lex, 8, false))]
+ IntegerSize((i128, u8, bool)),
#[regex("[0-9][0-9_]*", |lex| lex.slice().parse())]
Integer(i128),
@@ 150,6 139,8 @@ pub enum TokenKind {
Else,
#[regex("loop[ \n]*")]
Loop,
+ #[regex("while[ \n]*")]
+ While,
#[regex("do[ \n]*")]
Do,
#[token("return")]
@@ 336,10 327,13 @@ impl ErrorReporter {
// and the default test runner truncates 'em anyway. Leave this
// the way it is 'cause the lang_tester tests are set up to look
// for specific strings on failure.
+ //
+ // TODO: It really would be nice to have the test harness capture things
+ // properly. Why isn't it?
#[cfg(not(test))]
{
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
- let writer = StandardStream::stderr(ColorChoice::Always);
+ let writer = StandardStream::stdout(ColorChoice::Always);
cs::term::emit(&mut writer.lock(), &self.config, &self.files, _diag)
.expect("Could not print error message");
}
@@ 371,9 365,10 @@ macro_rules! parse_delimited {
/// The core parser struct. It provides basic methods for
/// manipulating the input stream, and the `parse()` method to
/// try to drive the given input to completion.
+#[derive(Clone)]
pub struct Parser<'input> {
+ // Honestly, just cloning the lexer is easier than dealing with Peekable
lex: logos::Lexer<'input, TokenKind>,
- //lex: std::iter::Peekable<logos::SpannedIter<'input, TokenKind>>,
source: &'input str,
err: ErrorReporter,
}
@@ 401,7 396,7 @@ impl<'input> Parser<'input> {
}) => {
self.drop();
s
- },
+ }
_ => String::new(),
};
self.eat_delimiters();
@@ 587,7 582,7 @@ impl<'input> Parser<'input> {
..
}) => s,
Some(Token {
- kind: T::IntegerSize((s, _)),
+ kind: T::IntegerSize((s, _, _)),
..
}) => s,
Some(Token { kind, span }) => {
@@ 673,7 668,6 @@ impl<'input> Parser<'input> {
if self.peek_is(T::RParen.discr()) {
break;
} else {
- self.expect(T::At);
let name = self.expect_ident();
params.push(name);
}
@@ 704,11 698,11 @@ impl<'input> Parser<'input> {
ast::Decl::Import { name, rename }
}
- /// signature = fn_args [":" typename]
+ /// signature = fn_args [typename]
fn parse_fn_signature(&mut self) -> ast::Signature {
- // TODO: Elide trailing unit type?
let (params, typeparams) = self.parse_fn_args();
- let rettype = self.parse_type();
+ let rettype = self.try_parse_type().unwrap_or(Type::unit());
+ let typeparams = Type::detype_names(&typeparams);
ast::Signature {
params,
rettype,
@@ 716,18 710,8 @@ impl<'input> Parser<'input> {
}
}
- /// Ok, this is where the grammar gets slightly cursed, but I
- /// think I can isolate the curse here. Basically this parses
- /// type args like `type, type, type |` up until the closing
- /// `|`, or if there IS no closing `|` and it reads something
- /// not a type then it *backtracks* and returns an empty list.
- fn _parse_type_args(&mut self) -> Vec<Type> {
- //let new_lexer = self.lex.into_inner().deref().clone();
- vec![]
- }
-
/// sig = ident typename
- /// fn_args = "(" [sig {"," sig} [","]] ["|" types...] ")"
+ /// fn_args = "(" ["|" types... "|"] [sig {"," sig} [","]] ["|" types...] ")"
fn parse_fn_args(&mut self) -> (Vec<(Sym, Type)>, Vec<Type>) {
let mut args = vec![];
let mut typeparams = vec![];
@@ 781,7 765,10 @@ impl<'input> Parser<'input> {
self.expect(T::LParen);
if self.peek_expect(T::Bar.discr()) {
parse_delimited!(self, T::Comma, {
- if !self.peek_is(T::RParen.discr()) {
+ if self.peek_is(T::Bar.discr()) {
+ // Bit of a hack, but (||) is a valid sig
+ break;
+ } else if !self.peek_is(T::RParen.discr()) {
let tname = self.parse_type();
typeparams.push(tname);
} else {
@@ 805,8 792,8 @@ impl<'input> Parser<'input> {
fn parse_fn_type(&mut self) -> Type {
let (params, typeparams) = self.parse_type_list_with_typeparams();
- let rettype = self.parse_type();
- Type::Func(params, Box::new(rettype), typeparams)
+ let rettype = self.try_parse_type().unwrap_or(Type::unit());
+ Type::function(¶ms, &rettype, &typeparams)
}
/// Parse the fields for a struct *type decl*
@@ 955,7 942,7 @@ impl<'input> Parser<'input> {
Type::Sum(fields, generics)
}
- fn parse_exprs(&mut self) -> Vec<ast::Expr> {
+ pub fn parse_exprs(&mut self) -> Vec<ast::Expr> {
let mut exprs = vec![];
let tok = self.peek();
while let Some(e) = self.parse_expr(0) {
@@ 983,7 970,10 @@ impl<'input> Parser<'input> {
/// parser to parse math expressions and such. It's a big chonky
/// boi of a function, but really just tries a bunch of matches
/// in sequence.
- fn parse_expr(&mut self, min_bp: usize) -> Option<ast::Expr> {
+ ///
+ /// The min_bp is the binding power used in the pratt parser; if
+ /// you are calling this standalone the min_bp should be 0.
+ pub fn parse_expr(&mut self, min_bp: usize) -> Option<ast::Expr> {
let t = self.peek()?;
let token = &t.kind;
let mut lhs = match token {
@@ 992,7 982,9 @@ impl<'input> Parser<'input> {
ast::Expr::bool(*b)
}
T::Integer(_) => ast::Expr::int(self.expect_int() as i128),
- T::IntegerSize((_str, size)) => ast::Expr::sized_int(self.expect_int() as i128, *size),
+ T::IntegerSize((_str, size, signed)) => {
+ ast::Expr::sized_int(self.expect_int() as i128, *size, *signed)
+ }
// Tuple/struct literal
T::LBrace => self.parse_constructor(),
// Array literal
@@ 1010,6 1002,7 @@ impl<'input> Parser<'input> {
T::Let => self.parse_let(),
T::If => self.parse_if(),
T::Loop => self.parse_loop(),
+ T::While => self.parse_while_loop(),
T::Do => self.parse_block(),
T::Fn => self.parse_lambda(),
T::Return => self.parse_return(),
@@ 1290,6 1283,21 @@ impl<'input> Parser<'input> {
ast::Expr::Loop { body }
}
+ /// while = "while" expr "do" {expr} "end"
+ fn parse_while_loop(&mut self) -> ast::Expr {
+ self.expect(T::While);
+ let cond = self
+ .parse_expr(0)
+ .expect("While loop condition was not an expression?");
+ self.expect(T::Do);
+ let body = self.parse_exprs();
+ self.expect(T::End);
+ ast::Expr::While {
+ cond: Box::new(cond),
+ body,
+ }
+ }
+
/// block = "do" {expr} "end"
fn parse_block(&mut self) -> ast::Expr {
self.expect(T::Do);
@@ 1298,7 1306,7 @@ impl<'input> Parser<'input> {
ast::Expr::Block { body }
}
- /// lambda = "fn" "(" ...args... ")" [":" typename] = {exprs} "end"
+ /// lambda = "fn" "(" ...args... ")" [typename] = {exprs} "end"
fn parse_lambda(&mut self) -> ast::Expr {
self.expect(T::Fn);
let signature = self.parse_fn_signature();
@@ 1361,7 1369,11 @@ impl<'input> Parser<'input> {
self.try_parse_type().expect("Type expected")
}
+ /// If this can't parse a valid type, it will rewind
+ /// back to where it started. Magic!
+ /// Also, you know, arbitrary lookahead/backtracking, but that's ok.
fn try_parse_type(&mut self) -> Option<Type> {
+ let old_lexer = self.lex.clone();
let t = self.next()?;
let x = match t.kind {
T::Ident(ref s) => {
@@ 1376,10 1388,12 @@ impl<'input> Parser<'input> {
Type::Named(Sym::new(s), type_params)
}
}
- T::At => {
- let s = self.expect_ident();
- Type::Generic(s)
- }
+ /*
+ T::At => {
+ let s = self.expect_ident();
+ Type::Generic(s)
+ }
+ */
T::LBrace => self.try_parse_tuple_type()?,
T::Fn => self.parse_fn_type(),
T::Struct => self.try_parse_struct_type()?,
@@ 1392,7 1406,17 @@ impl<'input> Parser<'input> {
let inner = self.parse_type();
Type::Array(Box::new(inner), len as usize)
}
- _ => return None,
+ T::Ampersand => {
+ let next = self.try_parse_type()?;
+ Type::Uniq(Box::new(next))
+ }
+ _ => {
+ // Wind the parse stream back to wherever we started
+ // TODO LATER: We should maybe think about making a better way
+ // of doing this, but so far this is the only place it happens.
+ self.lex = old_lexer;
+ return None;
+ }
};
Some(x)
}
@@ 1555,9 1579,9 @@ mod tests {
#[test]
fn test_typedef_generics() {
- test_decl_is("type bop(@T) = @T", || ast::Decl::TypeDef {
+ test_decl_is("type bop(T) = T", || ast::Decl::TypeDef {
name: Sym::new("bop"),
- typedecl: Type::Generic(Sym::new("T")),
+ typedecl: Type::Named(Sym::new("T"), vec![]),
doc_comment: vec![],
params: vec![Sym::new("T")],
});
@@ 1627,7 1651,7 @@ type blar = I8
"(x Bool)",
"(x Bool,)",
"(x I32, y Bool)",
- "(x @X, y @Y)",
+ "(x X, y Y)",
"(x I32, y Bool,)",
];
test_parse_with(|p| p.parse_fn_args(), &valid_args)
@@ 1636,13 1660,28 @@ type blar = I8
fn parse_fn_signature() {
let valid_args = vec![
"() {}",
- "(x Bool) I32",
- "(x Bool) {}",
- "(x I16, y Bool) {}",
- "(x I64, y Bool) Bool",
- "(x I8, y Bool,) {}",
- "(x I32, y Bool,) Bool",
- "(f fn(I32) I128, x I32) Bool",
+ "(x Bool) I32",
+ "(x Bool) {}",
+ "(x I16, y Bool) {}",
+ "(x I64, y Bool) Bool",
+ "(x I8, y Bool,) {}",
+ "(x I32, y Bool,) Bool",
+ "(f fn(I32) I64, x I32) Bool",
+ "(f fn(|| I32) I64, x I32) Bool",
+ "(f fn(||) I64, x I32) Bool",
+ "(f fn(|T| I32) I64, x I32) Bool",
+ // now without explicit return types
+ "()",
+ "(x Bool)",
+ "(x Bool)",
+ "(x I16, y Bool)",
+ "(x I64, y Bool)",
+ "(x I8, y Bool,)",
+ "(x I32, y Bool,)",
+ "(f fn(I32), x I32)",
+ "(f fn(|| I32), x I32)",
+ "(f fn(||), x I32)",
+ "(f fn(|T| I32), x I32)",
];
test_parse_with(|p| p.parse_fn_signature(), &valid_args)
}
@@ 1714,6 1753,12 @@ type blar = I8
"fn(x I32, i Bool) I32 = x end",
"fn(f fn(I32) I32, x I32) {} = f(x) end",
"fn() {} = {} end",
+ // for parse_expr there must be no leading newlines
+ // but there can be trailing ones.
+ r#"fn() {} =
+ {}
+end
+"#,
];
test_parse_with(|p| p.parse_lambda(), &valid_args);
test_parse_with(|p| p.parse_expr(0), &valid_args);
@@ 1742,12 1787,12 @@ type blar = I8
fn parse_fn_decls() {
let valid_args = vec![
"fn foo1(f I32) I32 = f end",
- "fn foo2(|@T| f I32 ) I32 = f end",
- "fn foo3(|@T|) {} = f end",
+ "fn foo2(|T| f I32 ) I32 = f end",
+ "fn foo3(|T|) {} = f end",
"fn foo4(||) {} = f end",
"fn foo5() {} = f end",
- "fn foo6(f @T) @T = f end",
- "fn foo7(|@T1, @T2, | f I32, g Bool, ) I32 = f end",
+ "fn foo6(f T) T = f end",
+ "fn foo7(|T1, T2, | f I32, g Bool, ) I32 = f end",
];
test_parse_with(|p| p.parse_decl().unwrap(), &valid_args);
}
@@ 1919,13 1964,12 @@ type blar = I8
("22_I16", 22, 2),
("33_I32", 33, 4),
("91_I64", 91, 8),
- ("9_I128", 9, 16),
];
for (s, expected_int, expected_bytes) in tests {
let mut p = Parser::new("unittest.gt", s);
assert_eq!(
p.next().unwrap().kind,
- TokenKind::IntegerSize((*expected_int, *expected_bytes))
+ TokenKind::IntegerSize((*expected_int, *expected_bytes, true))
);
// Make sure we don't lex the "i128" or whatever as the start of
// another token
@@ 1956,7 2000,7 @@ type blar = I8
#[test]
fn parse_integer_values() {
- test_expr_is("43_I8", || Expr::sized_int(43, 1));
+ test_expr_is("43_I8", || Expr::sized_int(43, 1, true));
/*
test_expr_is("{1,2,3}", |_cx| Expr::TupleCtor {
body: vec![Expr::int(1), Expr::int(2), Expr::int(3)],
@@ 2085,4 2129,34 @@ fn foo() {} = {} end
let res = p.parse();
assert_eq!(&res.module_docstring, "");
}
+
+ /// This tests a kinda horrible edge case in mixing line and block comments,
+ /// but it's a rare enough one that I don't care about it right now.
+ #[test]
+ #[should_panic]
+ fn parse_evil_nested_comment() {
+ let thing1 = r#"
+
+/- Block comments work fine
+-/
+
+/- Block comments work fine
+/- And nested block comments work fine
+-/
+-/
+
+-- Line comments work fine with a -/ in them
+-- Line comments work fine with a /- in them
+-- and no closing delimiter ever
+
+/-
+-- But if a line comment is commented out by a block comment and contains a
+-- surprising end delimiter like "-/" then the block comment is closed
+
+"#;
+
+ let mut p = Parser::new("unittest.gt", thing1);
+ let _res = p.parse();
+ }
+
}
M src/passes.rs +203 -57
@@ 26,7 26,10 @@
// oh well.
//mod enum_to_int;
+//mod closure_convert;
mod constinfer;
+mod double_typeck;
+// mod generic_infer;
mod handle_imports;
mod lambda_lift;
//mod monomorphization;
@@ 39,7 42,7 @@ type Pass = fn(Ir) -> Ir;
/// Tck has to be mutable because we may change the return type
/// of expr nodes.
-type TckPass = fn(Ir, &mut typeck::Tck) -> Ir;
+type TckPass = fn(Ir, &symtbl::Symtbl, &mut typeck::Tck) -> Ir;
pub fn run_passes(ir: Ir) -> Ir {
// TODO: It may be more efficient to compose passes rather than fold them?
@@ 48,20 51,29 @@ pub fn run_passes(ir: Ir) -> Ir {
// That will take some nontrivial restructuring of expr_map though, will also need
// a decl_map or something that can compose multiple passes together.
// Probably not *difficult*, but tricksy.
- let passes: &[Pass] = &[handle_imports::handle_imports, lambda_lift::lambda_lift];
+ let passes: &[Pass] = &[
+ handle_imports::handle_imports,
+ //generic_infer::generic_infer
+ ];
passes.iter().fold(ir, |prev_ir, f| f(prev_ir))
}
-pub fn run_typechecked_passes(ir: Ir, tck: &mut typeck::Tck) -> Ir {
- // let passes: &[TckPass] = &[nameify, enum_to_int];
- //let passes: &[TckPass] = &[nameify, struct_to_tuple];
+pub fn run_typechecked_passes(ir: Ir, symtbl: &symtbl::Symtbl, tck: &mut typeck::Tck) -> Ir {
+ // If we want to do closure conversion of some kind we need to know types of things,
+ // so for now we just stub this out
+ fn ll(ir: Ir, _: &symtbl::Symtbl, _tck: &mut typeck::Tck) -> Ir {
+ lambda_lift::lambda_lift(ir)
+ }
let passes: &[TckPass] = &[
+ double_typeck::double_typeck,
+ // closure_convert::closure_convert,
+ ll,
constinfer::constinfer,
struct_to_tuple::struct_to_tuple,
//monomorphization::monomorphize,
//type_erasure::type_erasure,
];
- let res = passes.iter().fold(ir, |prev_ir, f| f(prev_ir, tck));
+ let res = passes.iter().fold(ir, |prev_ir, f| f(prev_ir, symtbl, tck));
res
}
@@ 79,7 91,7 @@ fn id<T>(thing: T) -> T {
///
/// To handle multiple expressions this will turn more into a fold()
/// than a map(), it will take an accumulator that gets threaded through
-/// everything... That gets *very weird* though and I manage to pour
+/// everything... That gets *very weird* though and I managed to pour
/// my coffee on my cat this morning so let's give that a miss for now.
/// As it is, this can only transform subtrees into other subtrees.
///
@@ 94,10 106,13 @@ fn id<T>(thing: T) -> T {
/// sub-nodes, and one to call after. Use expr_map_pre()
/// and expr_map_post() to just do a pre-traversal or a post-traversal
/// specifically.
+///
+/// ...must resist the urge to rewrite all of this in terms of fold()...
fn expr_map(
expr: ExprNode,
f_pre: &mut dyn FnMut(ExprNode) -> ExprNode,
f_post: &mut dyn FnMut(ExprNode) -> ExprNode,
+ ft: &mut dyn FnMut(Type) -> Type,
) -> ExprNode {
let thing = f_pre(expr);
let exprfn = &mut |e| match e {
@@ 110,12 125,12 @@ fn expr_map(
E::Break => e,
E::EnumCtor { .. } => e,
E::TupleCtor { body } => E::TupleCtor {
- body: exprs_map(body, f_pre, f_post),
+ body: exprs_map(body, f_pre, f_post, ft),
},
E::StructCtor { body } => {
let new_body = body
.into_iter()
- .map(|(sym, vl)| (sym, expr_map(vl, f_pre, f_post)))
+ .map(|(sym, vl)| (sym, expr_map(vl, f_pre, f_post, ft)))
.collect();
E::StructCtor { body: new_body }
}
@@ 125,8 140,8 @@ fn expr_map(
body,
} => E::TypeCtor {
name,
- type_params,
- body: expr_map(body, f_pre, f_post),
+ type_params: types_map(type_params, ft),
+ body: expr_map(body, f_pre, f_post, ft),
},
E::SumCtor {
name,
@@ 135,41 150,41 @@ fn expr_map(
} => E::SumCtor {
name,
variant,
- body: expr_map(body, f_pre, f_post),
+ body: expr_map(body, f_pre, f_post, ft),
},
E::ArrayCtor { body } => E::ArrayCtor {
- body: exprs_map(body, f_pre, f_post),
+ body: exprs_map(body, f_pre, f_post, ft),
},
E::TypeUnwrap { expr } => E::TypeUnwrap {
- expr: expr_map(expr, f_pre, f_post),
+ expr: expr_map(expr, f_pre, f_post, ft),
},
E::TupleRef { expr, elt } => E::TupleRef {
- expr: expr_map(expr, f_pre, f_post),
+ expr: expr_map(expr, f_pre, f_post, ft),
elt,
},
E::StructRef { expr, elt } => E::StructRef {
- expr: expr_map(expr, f_pre, f_post),
+ expr: expr_map(expr, f_pre, f_post, ft),
elt,
},
E::ArrayRef { expr, idx } => E::ArrayRef {
- expr: expr_map(expr, f_pre, f_post),
+ expr: expr_map(expr, f_pre, f_post, ft),
idx,
},
E::Assign { lhs, rhs } => E::Assign {
- lhs: expr_map(lhs, f_pre, f_post), // TODO: Think real hard about lvalues
- rhs: expr_map(rhs, f_pre, f_post),
+ lhs: expr_map(lhs, f_pre, f_post, ft), // TODO: Think real hard about lvalues
+ rhs: expr_map(rhs, f_pre, f_post, ft),
},
E::BinOp { op, lhs, rhs } => E::BinOp {
op,
- lhs: expr_map(lhs, f_pre, f_post),
- rhs: expr_map(rhs, f_pre, f_post),
+ lhs: expr_map(lhs, f_pre, f_post, ft),
+ rhs: expr_map(rhs, f_pre, f_post, ft),
},
E::UniOp { op, rhs } => E::UniOp {
op,
- rhs: expr_map(rhs, f_pre, f_post),
+ rhs: expr_map(rhs, f_pre, f_post, ft),
},
E::Block { body } => E::Block {
- body: exprs_map(body, f_pre, f_post),
+ body: exprs_map(body, f_pre, f_post, ft),
},
E::Let {
varname,
@@ 178,47 193,54 @@ fn expr_map(
mutable,
} => E::Let {
varname,
- typename,
- init: expr_map(init, f_pre, f_post),
+ typename: typename.map(|t| type_map(t, ft)),
+ init: expr_map(init, f_pre, f_post, ft),
mutable,
},
E::If { cases } => {
let new_cases = cases
.into_iter()
.map(|(test, case)| {
- let new_test = expr_map(test, f_pre, f_post);
- let new_cases = exprs_map(case, f_pre, f_post);
+ let new_test = expr_map(test, f_pre, f_post, ft);
+ let new_cases = exprs_map(case, f_pre, f_post, ft);
(new_test, new_cases)
})
.collect();
E::If { cases: new_cases }
}
E::Loop { body } => E::Loop {
- body: exprs_map(body, f_pre, f_post),
+ body: exprs_map(body, f_pre, f_post, ft),
},
E::Return { retval } => E::Return {
- retval: expr_map(retval, f_pre, f_post),
+ retval: expr_map(retval, f_pre, f_post, ft),
},
E::Funcall {
func,
params,
type_params,
} => {
- let new_func = expr_map(func, f_pre, f_post);
- let new_params = exprs_map(params, f_pre, f_post);
+ let new_func = expr_map(func, f_pre, f_post, ft);
+ let new_params = exprs_map(params, f_pre, f_post, ft);
+ let new_type_params = types_map(type_params, ft);
E::Funcall {
func: new_func,
params: new_params,
- type_params,
+ type_params: new_type_params,
}
}
E::Lambda { signature, body } => E::Lambda {
signature,
- body: exprs_map(body, f_pre, f_post),
+ body: exprs_map(body, f_pre, f_post, ft),
},
E::Typecast { e, to } => E::Typecast {
- e: expr_map(e, f_pre, f_post),
- to,
+ e: expr_map(e, f_pre, f_post, ft),
+ to: type_map(to, ft),
+ },
+ E::Ref { expr } => E::Ref {
+ expr: expr_map(expr, f_pre, f_post, ft),
+ },
+ E::Deref { expr } => E::Deref {
+ expr: expr_map(expr, f_pre, f_post, ft),
},
};
let post_thing = thing.map(exprfn);
@@ 226,11 248,80 @@ fn expr_map(
}
pub fn expr_map_pre(expr: ExprNode, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> ExprNode {
- expr_map(expr, f, &mut id)
+ expr_map(expr, f, &mut id, &mut id)
}
pub fn expr_map_post(expr: ExprNode, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> ExprNode {
- expr_map(expr, &mut id, f)
+ expr_map(expr, &mut id, f, &mut id)
+}
+
+fn exprs_iter(exprs: &[ExprNode], callback: &mut dyn FnMut(&ExprNode)) {
+ exprs.iter().for_each(|e| expr_iter(e, callback));
+}
+
+/// Recursion scheme for non-mutating iteration.
+///
+/// `expr_map()` except it doesn't alter the nodes, it just takes a callback it
+/// executes on each node for its side-effects
+pub fn expr_iter(expr: &ExprNode, callback: &mut dyn FnMut(&ExprNode)) {
+ // BUGGO: *heck*
+ // This *could* be written in terms of expr_map() but the ownership gets fucky.
+ /*
+ let dirty_ownership_hack = expr.clone();
+ let thonk = &mut |e: ExprNode| {
+ callback(&e);
+ e
+ };
+ expr_map_pre(dirty_ownership_hack, thonk);
+ */
+ use hir::Expr::*;
+ callback(expr);
+ match &*expr.e {
+ Lit { .. } => (),
+ Var { .. } => (),
+ BinOp { lhs, rhs, .. } => {
+ expr_iter(lhs, callback);
+ expr_iter(rhs, callback);
+ }
+ UniOp { rhs, .. } => expr_iter(rhs, callback),
+ Block { body } => exprs_iter(body, callback),
+ Loop { body } => exprs_iter(body, callback),
+ Funcall { func, params, .. } => {
+ expr_iter(func, callback);
+ exprs_iter(params, callback);
+ }
+ Let { init, .. } => expr_iter(init, callback),
+ If { cases } => {
+ for (test, body) in cases {
+ expr_iter(test, callback);
+ exprs_iter(body, callback);
+ }
+ }
+ EnumCtor { .. } => (),
+ TupleCtor { body } => exprs_iter(body, callback),
+ TupleRef { expr, .. } => expr_iter(expr, callback),
+ StructCtor { body } => {
+ for (_name, body) in body {
+ expr_iter(body, callback)
+ }
+ }
+ StructRef { expr, .. } => expr_iter(expr, callback),
+ Assign { rhs, .. } => expr_iter(rhs, callback),
+ Break => (),
+ Lambda { body, .. } => exprs_iter(body, callback),
+ Return { retval } => expr_iter(retval, callback),
+ TypeCtor { body, .. } => expr_iter(body, callback),
+ TypeUnwrap { expr } => expr_iter(expr, callback),
+ SumCtor { body, .. } => expr_iter(body, callback),
+ ArrayCtor { body } => exprs_iter(body, callback),
+ ArrayRef { expr, idx } => {
+ expr_iter(expr, callback);
+ expr_iter(idx, callback);
+ }
+ Typecast { .. } => todo!(),
+ Ref { expr } => expr_iter(expr, callback),
+ Deref { expr } => expr_iter(expr, callback),
+ }
}
/// Map functions over a list of exprs.
@@ 238,19 329,23 @@ fn exprs_map(
exprs: Vec<ExprNode>,
f_pre: &mut dyn FnMut(ExprNode) -> ExprNode,
f_post: &mut dyn FnMut(ExprNode) -> ExprNode,
+ ft: &mut dyn FnMut(Type) -> Type,
) -> Vec<ExprNode> {
exprs
.into_iter()
- .map(|e| expr_map(e, f_pre, f_post))
+ .map(|e| expr_map(e, f_pre, f_post, ft))
.collect()
}
-fn exprs_map_pre(exprs: Vec<ExprNode>, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> Vec<ExprNode> {
- exprs_map(exprs, f, &mut id)
+pub fn exprs_map_pre(
+ exprs: Vec<ExprNode>,
+ f: &mut dyn FnMut(ExprNode) -> ExprNode,
+) -> Vec<ExprNode> {
+ exprs_map(exprs, f, &mut id, &mut id)
}
-fn exprs_map_post(exprs: Vec<ExprNode>, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> Vec<ExprNode> {
- exprs_map(exprs, &mut id, f)
+fn _exprs_map_post(exprs: Vec<ExprNode>, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> Vec<ExprNode> {
+ exprs_map(exprs, &mut id, f, &mut id)
}
fn decl_map_pre(
@@ 288,12 383,12 @@ fn decl_map(
} => D::Function {
name,
signature: signature_map(signature, ft),
- body: exprs_map(body, fe_pre, fe_post),
+ body: exprs_map(body, fe_pre, fe_post, ft),
},
D::Const { name, typ, init } => D::Const {
name,
typ: type_map(typ, ft),
- init: expr_map(init, fe_pre, fe_post),
+ init: expr_map(init, fe_pre, fe_post, ft),
},
D::TypeDef {
name,
@@ 350,11 445,57 @@ fn type_map(typ: Type, f: &mut dyn FnMut
Type::Prim(_) => typ,
Type::Never => typ,
Type::Enum(_) => typ,
- Type::Generic(_) => typ,
+ Type::Uniq(t) => {
+ let new_t = type_map(*t, f);
+ Type::Uniq(Box::new(new_t))
+ }
};
f(res)
}
+/// Does NOT iterate over type parameter inputs. Is that what we want?
+/// Not sure.
+pub fn _type_iter(ty: &Type, callback: &mut dyn FnMut(&Type)) {
+ fn types_iter(tys: &[Type], callback: &mut dyn FnMut(&Type)) {
+ for ty in tys {
+ callback(ty);
+ _type_iter(ty, callback);
+ }
+ }
+
+ match ty {
+ /*
+ Type::Struct(fields, generics) => {
+ let fields = types_map_btree(fields, f);
+ Type::Struct(fields, generics)
+ }
+ Type::Sum(fields, generics) => {
+ let new_fields = types_map_btree(fields, f);
+ Type::Sum(new_fields, generics)
+ }
+ */
+ Type::Struct(body, typeparams) | Type::Sum(body, typeparams) => {
+ for (_nm, ty) in body {
+ _type_iter(ty, callback);
+ }
+ for ty in typeparams {
+ _type_iter(ty, callback);
+ }
+ }
+ Type::Func(args, rettype, _typeparams) => {
+ types_iter(args, callback);
+ _type_iter(rettype, callback);
+ }
+ Type::Uniq(t) => {
+ _type_iter(&*t, callback);
+ }
+ Type::Array(ty, _len) => _type_iter(&*ty, callback),
+ // Not super sure whether this is necessary, but can't hurt.
+ Type::Named(_, _) | Type::Prim(_) | Type::Never | Type::Enum(_) => (),
+ };
+ callback(ty);
+}
+
/// Produce a new signature by transforming the types
fn signature_map(sig: hir::Signature, f: &mut dyn FnMut(Type) -> Type) -> hir::Signature {
let new_params = sig
@@ 365,11 506,16 @@ fn signature_map(sig: hir::Signature, f:
hir::Signature {
params: new_params,
rettype: type_map(sig.rettype, f),
- typeparams: types_map(sig.typeparams, f),
+ typeparams: sig.typeparams,
}
}
-pub fn generate_type_name(typ: &Type) -> String {
+/// Makes a unique, alphanum+underscores-only type name for the given
+/// type based off its structure. Prooooooobably mostly works.
+/// Probably doesn't handle nested types well though.
+/// Generates a mangled type name for the given type that is unique (hopefully) and
+/// printable. See also TODO on crate::Type.
+pub fn mangled_type_name(typ: &Type) -> String {
match typ {
Type::Enum(fields) => {
let fieldnames: Vec<_> = fields
@@ 380,17 526,17 @@ pub fn generate_type_name(typ: &Type) ->
format!("__Enum{}", fieldstr)
}
Type::Func(params, rettype, typeparams) => {
- let paramnames: Vec<String> = params.iter().map(generate_type_name).collect();
+ let paramnames: Vec<String> = params.iter().map(mangled_type_name).collect();
let paramstr = paramnames.join("_");
- let retname = generate_type_name(rettype);
- let tparamnames: Vec<String> = typeparams.iter().map(generate_type_name).collect();
+ let retname = mangled_type_name(rettype);
+ let tparamnames: Vec<String> = typeparams.iter().map(mangled_type_name).collect();
let tparamstr = tparamnames.join("_");
format!("__Func__{}__{}_{}", paramstr, retname, tparamstr)
}
Type::Struct(body, _) => {
let fieldnames: Vec<_> = body
.iter()
- .map(|(nm, ty)| format!("{}_{}", nm, generate_type_name(ty)))
+ .map(|(nm, ty)| format!("{}_{}", nm, mangled_type_name(ty)))
.collect();
let fieldstr = fieldnames.join("_");
format!("__Struct__{}", fieldstr)
@@ 398,23 544,23 @@ pub fn generate_type_name(typ: &Type) ->
Type::Sum(body, _) => {
let fieldnames: Vec<_> = body
.iter()
- .map(|(nm, ty)| format!("{}_{}", nm, generate_type_name(ty)))
+ .map(|(nm, ty)| format!("{}_{}", nm, mangled_type_name(ty)))
.collect();
let fieldstr = fieldnames.join("_");
format!("__Sum__{}", fieldstr)
}
- Type::Generic(name) => {
- format!("__G{}", name)
- }
Type::Named(name, fields) => {
- let field_names: Vec<_> = fields.iter().map(generate_type_name).collect();
+ let field_names: Vec<_> = fields.iter().map(mangled_type_name).collect();
let field_str = field_names.join("_");
format!("__Named{}__{}", name, field_str)
}
Type::Prim(p) => p.get_name().into_owned(),
Type::Never => format!("!"),
Type::Array(t, len) => {
- format!("__Arr{}__{}", generate_type_name(t), len)
+ format!("__Arr{}__{}", mangled_type_name(t), len)
+ }
+ Type::Uniq(t) => {
+ format!("__Uniq__{}", mangled_type_name(t))
}
}
}
A => src/passes/closure_convert.rs +529 -0
@@ 0,0 1,529 @@
+//! Closure conversion.
+//!
+//! Takes function expressions and rewrites
+//! them to make any captured variables (and type
+//! parameters!) explicit.
+//!
+//! From https://matt.might.net/articles/closure-conversion/
+//! the key points are to rewrite a lambda term into
+//! a closure-creation term that returns both a closure
+//! and an environment? Then rewrite function calls calling
+//! the variable to include the environment.
+//!
+//! Right now we have no borrowing or anything, so all values
+//! are *copied* into their environment.
+//!
+//! So for a first stab at this, let us merely worry about type
+//! parameters.
+
+use std::collections::BTreeSet;
+
+use crate::hir::{Decl, Expr, Ir};
+use crate::passes::*;
+use crate::typeck::Tck;
+
+// This might be the time to split the Symtbl into the
+// scope bit and the renamed table.
+use crate::symtbl::{self, Symtbl};
+
+/// A dumb and simple scope for type params.
+/// Will eventually get folded into Symtbl but I want to
+/// be able to figure out what shape it needs to be first without
+/// needing to alter anything that touches Symtbl.
+#[derive(Debug, Default)]
+struct ScopeThing {
+ type_params: Vec<BTreeSet<Sym>>,
+}
+
+struct ScopeGuard<'a> {
+ scope: &'a mut ScopeThing,
+}
+
+impl<'a> Drop for ScopeGuard<'a> {
+ fn drop(&mut self) {
+ self.scope.pop_scope()
+ }
+}
+
+impl ScopeThing {
+ fn push_scope(&mut self) -> ScopeGuard<'_> {
+ self.type_params.push(Default::default());
+ ScopeGuard { scope: self }
+ }
+
+ fn pop_scope(&mut self) {
+ self.type_params
+ .pop()
+ .expect("Scope stack underflow aieeee");
+ }
+
+ fn add_type_params(&mut self, params: &[Sym]) {
+ let top = self.type_params.last_mut().expect("Scope stack underflow");
+ top.extend(params.iter().cloned());
+ }
+
+ fn is_type_param(&self, ty: Sym) -> bool {
+ let msg = format!("Walking down scope {:?}", self);
+ dbg!(msg);
+ for scope in self.type_params.iter().rev() {
+ let msg = format!("is {} a type param? {}", ty, scope.contains(&ty));
+ dbg!(&msg);
+ if scope.contains(&ty) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+fn cc_type(scope: &mut ScopeThing, ty: Type) -> Type {
+ match &ty {
+ Type::Struct(fields, params) => ty,
+ Type::Sum(fields, generics) => ty,
+ Type::Array(ty, len) => *ty.clone(),
+ Type::Func(args, rettype, typeparams) => ty,
+ // Not super sure whether this is necessary, but can't hurt.
+ Type::Named(nm, tys) => ty,
+ Type::Prim(_) => ty,
+ Type::Never => ty,
+ Type::Enum(_) => ty,
+ Type::Uniq(t) => ty,
+ }
+}
+
+/// Is a more limited form of recursion scheme what we actually want?
+/// This only recurses one level deep, if you want to recurse deeper
+/// than that you have to call it explicitly.
+/// But that also allows you more control over pre/post/around traversal
+/// order.
+///
+/// Basically the idea is that you can handle the expr types that you
+/// want to, and just call do_to_children(...) on the rest.
+///
+/// You can imagine this as being equivalent to a get_children()
+/// function that returns a list of the immediate subexpr's of an expr,
+/// and then calls the given function on each item in it. The advantage
+/// of this form being that you don't need to allocate a vec.
+/// The result seems to be some kind of weird mutant hybrid of imperative
+/// and functional which also seems to be just what I need...
+fn do_to_children(expr: &ExprNode, f: &mut dyn FnMut(&ExprNode)) {
+ use Expr::*;
+ match &*expr.e {
+ BinOp { lhs, rhs, .. } => {
+ f(lhs);
+ f(rhs);
+ }
+ UniOp { rhs, .. } => f(rhs),
+ TupleCtor { body }
+ | ArrayCtor { body }
+ | Block { body }
+ | Loop { body }
+ | Lambda { body, .. } => {
+ for expr in body {
+ f(expr);
+ }
+ }
+ TupleRef { expr, .. }
+ | StructRef { expr, .. }
+ | Ref { expr }
+ | TypeUnwrap { expr }
+ | Deref { expr } => f(expr),
+ Funcall {
+ func,
+ params,
+ type_params: _,
+ } => {
+ f(func);
+ for vl in params {
+ f(vl);
+ }
+ }
+ Let { init, .. } => f(init),
+ If { cases } => {
+ for (test, exprs) in cases {
+ f(test);
+ for expr in exprs {
+ f(expr);
+ }
+ }
+ }
+ TypeCtor { body, .. } => f(body),
+ SumCtor { body, .. } => todo!(),
+ Typecast { .. } => todo!(),
+ Return { retval } => f(retval),
+ StructCtor { body } => {
+ for (_nm, expr) in body {
+ f(expr);
+ }
+ }
+ ArrayRef { expr, idx } => {
+ f(expr);
+ f(idx);
+ }
+ Assign { lhs, rhs } => {
+ f(lhs);
+ f(rhs);
+ }
+ // No sub-expressions to these terminals
+ Lit { .. } | Var { .. } | EnumCtor { .. } | Break => (),
+ // other => expr_iter(expr, &mut |e| {
+ // let res = get_free_type_params(e, scope);
+ // accm.extend(res);
+ // }),
+ }
+}
+
+/// Returns a list of type parameters that are not defined in the given scope.
+fn get_free_type_params(expr: &ExprNode, scope: &mut ScopeThing, symtbl: &Symtbl) -> BTreeSet<Sym> {
+ let mut accm = BTreeSet::new();
+ /// Take a type and collect any named types that aren't in the symbol table
+ /// into an array
+ ///
+ /// Eventually we can probably refactor this into using type_iter()
+ /// but handling type params is weird enough that for now I want to keep
+ /// it separate.
+ fn collect_free_types(
+ t: &Type,
+ scope: &mut ScopeThing,
+ symtbl: &Symtbl,
+ accm: &mut BTreeSet<Sym>,
+ ) {
+ use Type::*;
+ match t {
+ Named(nm, params) => {
+ if !symtbl.type_exists(*nm) && !scope.is_type_param(*nm) {
+ accm.insert(nm.clone());
+ }
+ for ty in params {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ }
+ Func(params, rettype, typeparams) => {
+ for ty in params {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ collect_free_types(rettype, scope, symtbl, accm);
+ for ty in typeparams {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ }
+ Struct(body, typeparams) | Sum(body, typeparams) => {
+ for (_nm, ty) in body {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ for ty in typeparams {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ }
+ Array(ty, _) | Uniq(ty) => {
+ collect_free_types(ty, scope, symtbl, accm);
+ }
+ // No type parameters for these types
+ Prim(_) | Never | Enum(_) => (),
+ }
+ }
+ use hir::Expr::*;
+ match &*expr.e {
+ Funcall {
+ func,
+ params,
+ type_params,
+ } => {
+ type_params
+ .iter()
+ .for_each(&mut |t| collect_free_types(t, scope, symtbl, &mut accm));
+ do_to_children(func, &mut |e| {
+ accm.extend(get_free_type_params(e, scope, symtbl))
+ });
+ for param in params {
+ do_to_children(param, &mut |e| {
+ accm.extend(get_free_type_params(e, scope, symtbl))
+ });
+ }
+ }
+ Let {
+ typename: Some(ty),
+ init,
+ ..
+ } => {
+ collect_free_types(ty, scope, symtbl, &mut accm);
+ do_to_children(init, &mut |e| {
+ accm.extend(get_free_type_params(e, scope, symtbl))
+ });
+ }
+ Lambda { signature, body } => {
+ // These are type params *defined by* the funcall
+ let guard = scope.push_scope();
+ guard.scope.add_type_params(&signature.typeparams);
+ let ty = signature.to_type();
+ collect_free_types(&ty, guard.scope, symtbl, &mut accm);
+
+ // This recuses into the lambda's body *before* we
+ // release the scope guard.
+ for expr in body {
+ do_to_children(expr, &mut |e| {
+ accm.extend(get_free_type_params(e, guard.scope, symtbl))
+ });
+ }
+ }
+ TypeCtor {
+ type_params, body, ..
+ } => {
+ // Like a funcall,
+ // These are type params *passed into* the constructor
+ /*
+ let f = &mut |t| collect_free_types(t, scope, &mut accm);
+ type_params.iter().for_each(f);
+ do_to_children(body, &mut |e| accm.extend(get_free_type_params(e, scope)));
+ */
+
+ type_params
+ .iter()
+ .for_each(&mut |t| collect_free_types(t, scope, symtbl, &mut accm));
+ do_to_children(body, &mut |e| {
+ accm.extend(get_free_type_params(e, scope, symtbl))
+ });
+ }
+ Typecast { .. } => todo!(),
+ // Nothing else binds new type parameters, so we just walk down
+ // them looking for expressions that do.
+ _other => do_to_children(expr, &mut |e| {
+ accm.extend(get_free_type_params(e, scope, symtbl))
+ }),
+ }
+ accm
+}
+
+fn cc_expr(scope: &mut ScopeThing, symtbl: &Symtbl, tck: &mut Tck, expr: ExprNode) -> ExprNode {
+ let f = &mut |e: Expr| {
+ // BUGGO:
+ // Not sure whether get_free_type_params() etc should take
+ // Expr or ExprNode here, so for now we do this ugly and maybe
+ // broken thing
+ let hackhackhack = ExprNode::new(e.clone());
+ match e {
+ Expr::Lambda { signature, body } => {
+ // Ok, so what we need to do is look at the body of the closure
+ // for types that are not declared in its type params,
+ // and if we see any of them in the
+ // signature we will need to add type params to this
+ // lambda.
+ let free_type_params = get_free_type_params(&hackhackhack, scope, symtbl);
+ if free_type_params.is_empty() {
+ // no change necessary
+ Expr::Lambda {
+ signature: signature.clone(),
+ body: exprs_map_pre(body, &mut |e| cc_expr(scope, symtbl, tck, e)),
+ }
+ } else {
+ // Rewrite lambda into an expression that contains the type params.
+ let mut new_sig = signature.clone();
+ new_sig.typeparams.extend(free_type_params.into_iter());
+ Expr::Lambda {
+ signature: new_sig.clone(),
+ body: exprs_map_pre(body, &mut |e| cc_expr(scope, symtbl, tck, e)),
+ }
+ }
+ }
+ // Here we look at the function invoked and see if we need to modify the
+ // funcall to give it more params.
+ /*
+ Expr::Funcall {
+ func,
+ params,
+ type_params,
+ } => {
+ todo!()
+ }
+ */
+ x => x,
+ }
+ };
+ expr.map(f)
+}
+
+fn cc_exprs(
+ scope: &mut ScopeThing,
+ symtbl: &Symtbl,
+ tck: &mut Tck,
+ exprs: &[ExprNode],
+) -> Vec<ExprNode> {
+ exprs
+ .into_iter()
+ .map(|e| cc_expr(scope, symtbl, tck, e.clone()))
+ .collect()
+}
+
+fn cc_decl(tck: &mut Tck, symtbl: &Symtbl, decl: Decl) -> Decl {
+ let scope = &mut ScopeThing::default();
+ scope.push_scope();
+ match decl {
+ D::Function {
+ name,
+ signature,
+ body,
+ } => D::Function {
+ name,
+ signature,
+ body: exprs_map_pre(body, &mut |e| cc_expr(scope, symtbl, tck, e)),
+ },
+ D::Const { name, typ, init } => D::Const {
+ name,
+ typ,
+ init: expr_map_pre(init, &mut |e| cc_expr(scope, symtbl, tck, e)),
+ },
+ // If our typedecl contains a lambda, we have to closure-convert
+ // that too.
+ D::TypeDef {
+ name,
+ params,
+ typedecl,
+ } => D::TypeDef {
+ name,
+ params,
+ typedecl,
+ },
+ D::Import { .. } => decl,
+ }
+}
+
+/// I am somewhat dissatisfied that we have to do symbol table
+/// scope-wrangling stuff for this, and then do it again for
+/// the symtbl alpha-renaming, but oh well. They're different
+/// things, this adds information and the renaming just
+/// transforms it into something more convenient.
+pub fn closure_convert(ir: Ir, symtbl: &symtbl::Symtbl, tck: &mut typeck::Tck) -> Ir {
+ let new_decls: Vec<Decl> = ir
+ .decls
+ .into_iter()
+ .map(|decl| cc_decl(tck, symtbl, decl))
+ .collect();
+ Ir {
+ decls: new_decls,
+ ..ir
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_free_type_param() {
+ let test_cases = vec![
+ ("3", vec![]),
+ ("let foo = bar", vec![]),
+ ("let foo I32 = bar", vec![]),
+ ("let foo T = bar", vec![Sym::new("T")]),
+ (
+ "let foo T(Thing) = bar",
+ vec![Sym::new("T"), Sym::new("Thing")],
+ ),
+ ("fn(x I32) I32 = x end", vec![]),
+ ("fn(x T) T = x end", vec![Sym::new("T")]),
+ ("fn(|T| x T) T = x end", vec![]),
+ (
+ // We can't have leading newlines in these strings, alas.
+ r#"fn(|A| x A) A =
+ let f = fn(x A) A = x end
+ f(x)
+end"#,
+ vec![],
+ ),
+ (
+ r#"fn(|A| x A) A =
+ let f = fn(x A) A = x end
+ -- Shadow the type param A
+ let g = fn(|A| x A) A = x end
+ f(x)
+end"#,
+ vec![],
+ ),
+ (
+ r#"fn(|A| x A) A =
+ let f = fn(x A) A = x end
+ -- Use the unbound type param B
+ let g = fn(x B) B = x end
+ f(x)
+end"#,
+ vec![Sym::new("B")],
+ ),
+ (
+ r#"let my_thing = Thing( {
+ .do_stuff = fn(x T1, y T2) I32 =
+ 12
+ end
+})
+
+ "#,
+ vec![Sym::new("T1"), Sym::new("T2")],
+ ),
+ (
+ r#"fn(|T1, T2|) {T1, T2} =
+ let my_thing = {
+ .do_stuff = fn(x T1, y T2) I32 =
+ 12
+ end
+ }
+ my_thing
+end
+})
+
+ "#,
+ vec![],
+ ),
+ ];
+ let scope = &mut ScopeThing::default();
+ let symtbl = &symtbl::Symtbl::default();
+ for (src, expected) in test_cases {
+ let ir = compile_to_hir_expr(src);
+ let frees = get_free_type_params(&ir, scope, symtbl);
+ use std::iter::FromIterator;
+ let expected: BTreeSet<Sym> = BTreeSet::from_iter(expected);
+ assert_eq!(frees, expected);
+ }
+ }
+
+ /*
+ we need to run typechecking and stuff to make this work,
+ and whole-program tests are best handled as whole-program tests.
+ Alas.
+ #[test]
+ fn test_cc_exprs() {
+ let test_cases = vec![
+ (r#"3; fn(x A) A = x end"#, r#"3; fn(|A| x A) A = x end"#),
+ (
+ r#"fn(x A) A =
+ let f = fn(x A) A = x end
+ -- Use the unbound type param B
+ let g = fn(x B) B = x end
+ f(x)
+ end"#,
+ r#"fn(|A, B| x A) A =
+ let f = fn(|A| x A) A = x end
+ -- Use the unbound type param B
+ let g = fn(|B| x B) B = x end
+ f(x)
+ end"#,
+ ),
+ ];
+ let scope = &mut ScopeThing::default();
+ for (src, expected) in test_cases {
+ let hir = compile_to_hir_exprs(src);
+ let (hir, mut symtbl) = symtbl::resolve_symbols(hir);
+ let tck = &mut typeck::typecheck(&hir, &mut symtbl).unwrap();
+ let res = cc_exprs(scope, &ir);
+ dbg!(&res);
+ let expected_ir = compile_to_hir_exprs(expected);
+ assert_eq!(
+ res,
+ expected_ir,
+ "exprs do not match. Got:\n{}Expected:\n{}\n",
+ Expr::Block { body: res.clone() },
+ Expr::Block {
+ body: expected_ir.clone()
+ },
+ );
+ }
+ }
+ */
+}
M src/passes/constinfer.rs +3 -1
@@ 64,13 64,15 @@ fn constinfer_expr(expr: ExprNode) -> Ex
E::Loop { .. } => false,
E::Lambda { .. } => true,
E::Funcall { .. } => false, // TODO: True if function being called is also const
+ E::Ref { .. } => false, // TODO: Maybe sometimes true?
+ E::Deref { .. } => false, // Probably never true
};
let mut expr = expr;
expr.is_const = is_const;
expr
}
-pub fn constinfer(ir: Ir, _tck: &mut typeck::Tck) -> Ir {
+pub fn constinfer(ir: Ir, _: &symtbl::Symtbl, _tck: &mut typeck::Tck) -> Ir {
let type_map = &mut |t| t;
let new_decls = ir
.decls
A => src/passes/double_typeck.rs +38 -0
@@ 0,0 1,38 @@
+//! Sanity checks the results of the type checker.
+//! Doesn't actually change anything, just walks through
+//! the entire IR tree and makes sure that every expression
+//! has a real type.
+//!
+//! Technically unnecessary but occasionally useful for
+//! debugging stuff.
+
+use crate::hir::*;
+use crate::passes::*;
+use crate::*;
+
+fn check_expr(expr: ExprNode, tck: &mut typeck::Tck) -> ExprNode {
+ let expr_typeid = tck.get_expr_type(&expr);
+ let expr_type = tck
+ .reconstruct(expr_typeid)
+ .unwrap_or_else(|e| panic!("Typechecker couldn't reconstruct something: {}", e));
+ match &expr_type {
+ Type::Prim(PrimType::UnknownInt) => panic!("Unknown int in expr {:?}", expr.e),
+ Type::Prim(PrimType::AnyPtr) => panic!("should be unused"),
+ _ => (),
+ }
+
+ expr
+}
+
+pub(super) fn double_typeck(ir: Ir, _: &symtbl::Symtbl, tck: &mut typeck::Tck) -> Ir {
+ let type_map = &mut |t| t;
+ let new_decls = ir
+ .decls
+ .into_iter()
+ .map(|d| decl_map_pre(d, &mut |e| check_expr(e, tck), type_map))
+ .collect();
+ Ir {
+ decls: new_decls,
+ ..ir
+ }
+}
A => src/passes/generic_infer.rs +208 -0
@@ 0,0 1,208 @@
+//! A pass that takes Named type parameters and turns them into Generics
+//! if they are not declared.
+//! We could do this in the typechecker but I think it will be simpler as a preprocessing pass.
+
+use crate::passes::*;
+
+use crate::symtbl::Symtbl;
+
+fn generic_infer_type(symtbl: &Symtbl, ty: Type) -> Type {
+ match ty {
+ Type::Named(nm, ref _tys) => {
+ if let Some(t) = symtbl.get_type(nm) {
+ // TODO: Do we have to recurse down the type params?
+ // or will type_map() do that for us?
+ // t should just be a Generic(nm), so we just replace this type with it.
+ t
+ } else {
+ // Not declared, just carry on
+ ty
+ }
+ }
+ _ => ty,
+ }
+}
+
+/*
+/// signature_map() doesn't do the correct thing here 'cause we have to
+/// shuffle around generic param
+fn generic_infer_sig(symtbl: &Symtbl, sig: Signature) -> Signature {
+ sig
+}
+*/
+
+/// Basically, if we see a Named type mentioned, we check to see if it's
+/// in the symtbl. If it is, we replace it with a Generic. Otherwise
+/// we leave it as is and let typeck decide whether or not it exists.
+fn generic_infer_expr(_symtbl: &Symtbl, expr: ExprNode) -> ExprNode {
+ expr
+}
+
+pub(super) fn generic_infer(ir: Ir) -> Ir {
+ let symtbl = Symtbl::default();
+ let generic_infer_expr = &mut |e| generic_infer_expr(&symtbl, e);
+ let generic_infer_ty = &mut |ty| generic_infer_type(&symtbl, ty);
+ // Fuck decl_map, it's not made for this. So we'll just
+ // do it by hand.
+ let mut new_decls = vec![];
+ for decl in ir.decls {
+ let d = match decl {
+ D::Function {
+ name,
+ signature,
+ body,
+ } => {
+ let _guard = symtbl.push_scope();
+ for typeparam in &signature.typeparams {
+ match typeparam {
+ Type::Named(sym, _) if *sym == Sym::new("Tuple") => (),
+ Type::Named(nm, _v) => {
+ symtbl.add_type(*nm, &Type::Generic(*nm));
+ }
+ _ => (),
+ }
+ }
+ let new_sig = signature_map(signature, generic_infer_ty);
+ D::Function {
+ name,
+ signature: new_sig,
+ body: exprs_map(body, &mut id, generic_infer_expr, generic_infer_ty),
+ }
+ }
+ D::Const { name, typ, init } => {
+ let _guard = symtbl.push_scope();
+ dbg!("dealing with const", name);
+ for name in typ.get_type_params() {
+ dbg!(name);
+ symtbl.add_type(name, &Type::Generic(name));
+ }
+ D::Const {
+ name,
+ typ: type_map(typ, generic_infer_ty),
+ init: expr_map(init, &mut id, generic_infer_expr, generic_infer_ty),
+ }
+ }
+ D::TypeDef {
+ name,
+ params,
+ typedecl,
+ } => {
+ let _guard = symtbl.push_scope();
+ for name in ¶ms {
+ symtbl.add_type(*name, &Type::Generic(*name));
+ }
+ D::TypeDef {
+ name,
+ params,
+ typedecl: type_map(typedecl, generic_infer_ty),
+ }
+ }
+ D::Import { .. } => decl,
+ };
+ new_decls.push(d);
+ }
+ Ir {
+ decls: new_decls,
+ ..ir
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::hir::{Decl as D, Ir};
+ use crate::*;
+
+ #[test]
+ fn heckin_heck() {
+ let typedef = D::TypeDef {
+ name: Sym::new("thing"),
+ params: vec![Sym::new("T")],
+ typedecl: Type::Named(Sym::new("T"), vec![]),
+ };
+ let ir = Ir {
+ decls: vec![typedef],
+ filename: String::from("test"),
+ modulename: String::from("test"),
+ };
+ let res = super::generic_infer(ir);
+
+ let wanted_typedef = D::TypeDef {
+ name: Sym::new("thing"),
+ params: vec![Sym::new("T")],
+ typedecl: Type::generic("T"),
+ };
+
+ assert_eq!(res.decls[0], wanted_typedef);
+ }
+
+ #[test]
+ fn heckin_heck2() {
+ let structbody = [
+ (Sym::new("a"), Type::i32()),
+ (Sym::new("b"), Type::Named(Sym::new("T"), vec![])),
+ ];
+ let typedef = D::TypeDef {
+ name: Sym::new("thing"),
+ params: vec![Sym::new("T")],
+ typedecl: Type::Struct(BTreeMap::from(structbody), vec![]),
+ };
+ let ir = Ir {
+ decls: vec![typedef],
+ filename: String::from("test"),
+ modulename: String::from("test"),
+ };
+ let res = super::generic_infer(ir);
+
+ let wanted_structbody = [
+ (Sym::new("a"), Type::i32()),
+ (Sym::new("b"), Type::generic("T")),
+ ];
+ let wanted_typedef = D::TypeDef {
+ name: Sym::new("thing"),
+ params: vec![Sym::new("T")],
+ typedecl: Type::Struct(BTreeMap::from(wanted_structbody), vec![]),
+ };
+
+ assert_eq!(res.decls[0], wanted_typedef);
+ }
+
+ /// Todo: maybe check out nested functions?
+ #[test]
+ fn heckin_heck3() {
+ let sig1 = hir::Signature {
+ params: vec![
+ (Sym::new("a"), Type::named0("T")),
+ (Sym::new("b"), Type::named0("Something")),
+ ],
+ rettype: Type::Named(Sym::new("T"), vec![]),
+ typeparams: vec![Type::Named(Sym::new("T"), vec![])],
+ };
+ let f1 = D::Function {
+ name: Sym::new("foo"),
+ signature: sig1,
+ body: vec![],
+ };
+ let ir = Ir {
+ decls: vec![f1],
+ filename: String::from("test"),
+ modulename: String::from("test"),
+ };
+ let res = super::generic_infer(ir);
+
+ let wanted_sig = hir::Signature {
+ params: vec![
+ (Sym::new("a"), Type::generic("T")),
+ (Sym::new("b"), Type::named0("Something")),
+ ],
+ rettype: Type::generic("T"),
+ typeparams: vec![Type::generic("T")],
+ };
+ let wanted_f = D::Function {
+ name: Sym::new("foo"),
+ signature: wanted_sig,
+ body: vec![],
+ };
+
+ assert_eq!(res.decls[0], wanted_f);
+ }
+}
M src/passes/lambda_lift.rs +3 -0
@@ 43,6 43,9 @@ fn lambda_lift_expr(expr: ExprNode, outp
/// Eeeeeeeeh we might just have to check on it later one way or another,
/// either when doing codegen or when transforming our HIR to a lower-level IR.
pub(super) fn lambda_lift(ir: Ir) -> Ir {
+ // Ok we do some limited symbol table type stuff in here
+ // cause we have to know what scopes we are adjusting
+ // let symtbl = Symtbl::new();
let mut new_functions = vec![];
let new_decls: Vec<D> = ir
.decls
M src/passes/struct_to_tuple.rs +15 -16
@@ 39,8 39,8 @@ fn tuplize_type(typ: Type) -> Type {
// TODO: What do we do with generics? Anything?
let tuple_fields = fields
- .iter()
- .map(|(_sym, ty)| type_map(ty.clone(), &mut tuplize_type))
+ .values()
+ .map(|ty| type_map(ty.clone(), &mut tuplize_type))
.collect();
Type::tuple(tuple_fields)
}
@@ 63,7 63,7 @@ fn tuplize_expr(expr: ExprNode, tck: &mu
init,
mutable,
} => {
- let new_type = tuplize_type(t.clone());
+ let new_type = tuplize_type(t);
E::Let {
varname,
init,
@@ 76,13 76,10 @@ fn tuplize_expr(expr: ExprNode, tck: &mu
Type::Struct(type_body, _generics) => {
let mut ordered_body: Vec<_> = body
.into_iter()
- .map(|(ky, vl)| (offset_of_field(&type_body, ky), vl))
+ .map(|(ky, vl)| (offset_of_field(type_body, ky), vl))
.collect();
ordered_body.sort_by(|a, b| a.0.cmp(&b.0));
- let new_body = ordered_body
- .into_iter()
- .map(|(_i, expr)| expr.clone())
- .collect();
+ let new_body = ordered_body.into_iter().map(|(_i, expr)| expr).collect();
E::TupleCtor { body: new_body }
}
@@ 100,7 97,7 @@ fn tuplize_expr(expr: ExprNode, tck: &mu
Type::Struct(type_body, _generics) => {
let offset = offset_of_field(&type_body, elt);
E::TupleRef {
- expr: inner_expr.clone(),
+ expr: inner_expr,
elt: offset,
}
}
@@ 132,7 129,7 @@ fn tuplize_expr(expr: ExprNode, tck: &mu
/// kinda squirrelly, because we don't really have a decl that translates
/// directly into a Rust struct without being wrapped in a typedef first. So I
/// think I will translate them to tuples after all.
-pub(super) fn struct_to_tuple(ir: Ir, tck: &mut typeck::Tck) -> Ir {
+pub(super) fn struct_to_tuple(ir: Ir, _: &symtbl::Symtbl, tck: &mut typeck::Tck) -> Ir {
let mut new_decls = vec![];
let tuplize_expr = &mut |e| tuplize_expr(e, tck);
@@ 153,9 150,11 @@ pub(super) fn struct_to_tuple(ir: Ir, tc
// passes::expr_map that does a post-traversal instead of a pre-traversal?
//
// So for now we just throw away all type info and regenerate it!
- let new_tck =
- typeck::typecheck(&new_ir).expect("Generated monomorphized IR that doesn't typecheck");
- *tck = new_tck;
+ // However this seems to be part of messy monomorph, so yanking it out
+ // for now seems fine.
+ // let new_tck =
+ // typeck::typecheck(&new_ir).expect("Generated monomorphized IR that doesn't typecheck");
+ // *tck = new_tck;
new_ir
}
@@ 279,9 278,9 @@ mod tests {
let out = type_map(inp.clone(), &mut tuplize_type);
assert_eq!(out, desired);
- let desired2 = Type::Array(Box::new(out.clone()), 3);
- let inp2 = Type::Array(Box::new(inp.clone()), 3);
- let out2 = type_map(inp2.clone(), &mut tuplize_type);
+ let desired2 = Type::Array(Box::new(out), 3);
+ let inp2 = Type::Array(Box::new(inp), 3);
+ let out2 = type_map(inp2, &mut tuplize_type);
assert_eq!(out2, desired2);
}
}
A => src/symtbl.rs +710 -0
@@ 0,0 1,710 @@
+//! Symbol table.
+//!
+//! Basically we need to make the symtable persistent,
+//! so we only need to figure out all meta-info about it
+//! once instead of needing to walk through scopes multiple
+//! times and then throw the scope information away when
+//! we're done with it.
+
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::rc::Rc;
+
+use anymap::Map;
+
+use crate::*;
+
+/// A symbol that has been renamed to be globally unique.
+/// So basically instead of `foo` being scoped, it gets
+/// renamed to `foo_1` where every mention of `foo_1`
+/// refers to the same value.
+///
+/// TODO: The typeck code needs to construct/retrieve its own
+/// UniqueSym's, which is why this is public. It's a bit of
+/// a pickle though, since really the goal is to have these
+/// make non-unique syms unrepresentable.
+///
+/// But after the alpha-renaming pass all Sym's are unique already
+/// anyway, soooooo... idk.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct UniqueSym(pub Sym);
+
+#[derive(Clone, Default, Debug)]
+struct ScopeFrame {
+ symbols: BTreeMap<Sym, UniqueSym>,
+ types: BTreeMap<Sym, UniqueSym>,
+}
+
+/// A shortcut for a cloneable AnyMap
+type CloneMap = Map<dyn anymap::any::CloneAny>;
+
+/// Basic symbol table that maps names to type ID's
+/// and manages scope.
+/// Looks ugly, works well.
+#[derive(Debug, Clone)]
+pub struct Symtbl {
+ frames: Rc<RefCell<Vec<ScopeFrame>>>,
+ /// A mapping from unique symbol names to whatever
+ /// we need to know about them. It's an AnyMap, so
+ /// we can just stuff whatever data we need into it.
+ unique_symbols: BTreeMap<UniqueSym, CloneMap>,
+
+ /// Same as unique_symbols but for types.
+ unique_types: BTreeMap<UniqueSym, CloneMap>,
+
+ /// Every time we generate a new symbol we increment
+ /// this.
+ ///
+ /// TODO: It'd be kinda nice to have foo_1 , bar_1, foo_2,
+ /// baz_1, foo_3 etc but don't need it for now.
+ ///
+ /// Also it might be nice to have each variable have a unique
+ /// number someday. ...though symbols already kinda do, hm.
+ /// That can go in the SymbolInfo tho.
+ gensym_increment: usize,
+}
+
+impl Default for Symtbl {
+ /// We start with an empty toplevel scope existing,
+ /// then add some builtin's to it.
+ fn default() -> Self {
+ Self {
+ frames: Rc::new(RefCell::new(vec![ScopeFrame::default()])),
+ unique_symbols: Default::default(),
+ unique_types: Default::default(),
+ gensym_increment: 0,
+ }
+ }
+}
+
+pub struct ScopeGuard {
+ scope: Symtbl,
+}
+
+impl Drop for ScopeGuard {
+ fn drop(&mut self) {
+ self.scope
+ .frames
+ .borrow_mut()
+ .pop()
+ .expect("Scope stack underflow");
+ }
+}
+
+impl Symtbl {
+ fn add_builtins(&mut self) {
+ for builtin in &*builtins::BUILTINS {
+ let _new = self.new_unchanged_symbol(builtin.name);
+ }
+ // Add another builtin called "main" so the main function
+ // doesn't get renamed.
+ self.new_unchanged_symbol(Sym::new("main"));
+ }
+
+ /// Creates a new symbol with the name of the given one
+ /// and a unique numerical suffix.
+ fn gensym(&mut self, base: Sym) -> UniqueSym {
+ self.gensym_increment += 1;
+ let name = format!("{}_{}", &*base.val(), self.gensym_increment);
+ UniqueSym(Sym::new(name))
+ }
+
+ pub fn push_scope(&self) -> ScopeGuard {
+ self.frames.borrow_mut().push(ScopeFrame::default());
+ // TODO: This clone is a little cursed 'cause
+ // it'll clone the whole `unique_symbols` map,
+ // We should probably just wrap the whole symtbl in
+ // a Rc<RefCell<>> intead of just the scopeframe.
+ ScopeGuard {
+ scope: self.clone(),
+ }
+ }
+
+ /// Returns the UniqueSym that the given Sym is *currently*
+ /// bound to, or None if DNE
+ fn get_binding(&self, sym: Sym) -> Option<UniqueSym> {
+ for scope in self.frames.borrow().iter().rev() {
+ let v = scope.symbols.get(&sym).cloned();
+ if v.is_some() {
+ return v;
+ }
+ }
+ None
+ }
+
+ /// Get a reference to the symbol most recently bound
+ /// with that name, or panic if it does not exist
+ ///
+ /// ...I kinda miss Elixir's convention of having
+ /// a function `foo` return a result and `foo!` panic
+ /// on error.
+ fn really_get_binding(&self, sym: Sym) -> UniqueSym {
+ let msg = format!("Symbol {} is not bound!", sym);
+ self.get_binding(sym).expect(&msg)
+ }
+
+ fn get_type_binding(&self, sym: Sym) -> Option<UniqueSym> {
+ for scope in self.frames.borrow().iter().rev() {
+ let v = scope.types.get(&sym).cloned();
+ if v.is_some() {
+ return v;
+ }
+ }
+ None
+ }
+
+ fn really_get_type_binding(&self, sym: Sym) -> UniqueSym {
+ let msg = format!("Type {} is not bound!", sym);
+ self.get_type_binding(sym).expect(&msg)
+ }
+
+ /// Get the specified info for the given UniqueSym, or
+ /// None if DNE
+ pub fn get_info<T>(&self, sym: UniqueSym) -> Option<&T>
+ where
+ T: anymap::any::CloneAny,
+ {
+ self.unique_symbols
+ .get(&sym)
+ .and_then(|anymap| anymap.get::<T>())
+ }
+
+ /// Adds the given info struct to the symbol, or
+ /// panics if it already exists.
+ pub fn put_info<T>(&mut self, sym: UniqueSym, info: T)
+ where
+ T: anymap::any::CloneAny,
+ {
+ self.unique_symbols
+ .get_mut(&sym)
+ .and_then(|anymap| anymap.insert(info));
+ }
+
+ pub fn type_exists(&self, sym: Sym) -> bool {
+ if sym == Sym::new("Tuple") {
+ return true;
+ }
+ let thing = self.unique_types.get(&UniqueSym(sym));
+ // fuck combinators
+ match thing {
+ Some(_anymap) => true,
+ None => false,
+ }
+ }
+
+ fn get_type_info<T>(&self, sym: UniqueSym) -> Option<&T>
+ where
+ T: anymap::any::CloneAny,
+ {
+ // dbg!(&self.unique_types);
+ self.unique_types
+ .get(&sym)
+ .and_then(|anymap| anymap.get::<T>())
+ }
+
+ /// BUGGO: See discussion on UniqueSym type
+ pub fn get_type_info2<T>(&self, sym: Sym) -> Option<&T>
+ where
+ T: anymap::any::CloneAny,
+ {
+ self.get_type_info(UniqueSym(sym))
+ }
+
+ /// Adds the given type struct to the symbol, or
+ /// panics if it already exists.
+ fn put_type_info<T>(&mut self, sym: UniqueSym, info: T)
+ where
+ T: anymap::any::CloneAny + std::fmt::Debug,
+ {
+ let anymap = self.unique_types.entry(sym).or_insert_with(CloneMap::new);
+ // slightly weird error message formatting 'cause we apparently can't
+ // clone `info` easily???
+ let info_dbg = format!("{:?}", info);
+ if let Some(x) = anymap.insert(info) {
+ panic!(
+ "Type info for {} already exists: {:?}\nAttempting to replace it with: {}",
+ sym.0, x, info_dbg
+ );
+ }
+ }
+ /// BUGGO: same as get_type_info2()
+ pub fn put_type_info2<T>(&mut self, sym: Sym, info: T)
+ where
+ T: anymap::any::CloneAny + std::fmt::Debug,
+ {
+ self.put_type_info(UniqueSym(sym), info)
+ }
+
+ fn binding_exists(&self, sym: Sym) -> bool {
+ self.get_binding(sym).is_some()
+ }
+
+ fn _unique_exists(&self, sym: UniqueSym) -> bool {
+ self.unique_symbols.get(&sym).is_some()
+ }
+
+ /// Takes a symbol, generates a new UniqueSym for it,
+ /// and stuffs it into the topmost scope, overwriting
+ /// any symbol of the same name already there.
+ fn bind_new_symbol(&mut self, sym: Sym) -> UniqueSym {
+ let newsym = self.gensym(sym);
+ self.frames
+ .borrow_mut()
+ .last_mut()
+ .expect("Scope stack underflow")
+ .symbols
+ .insert(sym, newsym);
+ self.unique_symbols.insert(newsym, Map::new());
+ newsym
+ }
+
+ /// Takes a name and generates a new type param name for it
+ pub(crate) fn bind_new_type(&mut self, sym: Sym) -> UniqueSym {
+ let newsym = self.gensym(sym);
+ self.frames
+ .borrow_mut()
+ .last_mut()
+ .expect("Scope stack underflow")
+ .types
+ .insert(sym, newsym);
+ self.unique_types.insert(newsym, Map::new());
+ newsym
+ }
+
+ /// Introduce a new symbol, or panic if that name is already
+ /// defined (ie, it is a name conflict, like two functions with
+ /// the same name)
+ fn new_unique_symbol(&mut self, sym: Sym) -> UniqueSym {
+ if self.binding_exists(sym) {
+ panic!("Attempting to create duplicate symbol {}", sym);
+ } else {
+ self.bind_new_symbol(sym)
+ }
+ }
+
+ /// Introduce a new symbol as long as it doesn't clash with one
+ /// currently in the top scope.
+ fn _new_local_symbol(&mut self, sym: Sym) -> UniqueSym {
+ // Does the symbol exist in the topmost scope?
+ if self
+ .frames
+ .borrow_mut()
+ .last_mut()
+ .expect("Scope stack underflow")
+ .symbols
+ .contains_key(&sym)
+ {
+ panic!("Attempting to create duplicate global symbol {}", sym)
+ } else {
+ self.bind_new_symbol(sym)
+ }
+ }
+
+ /// Insert a new symbol that is actually identical to the given one,
+ fn new_unchanged_symbol(&mut self, sym: Sym) -> UniqueSym {
+ if self.binding_exists(sym) {
+ panic!("Attempting to create duplicate symbol {}", sym);
+ } else {
+ let new_sym = UniqueSym(sym);
+ self.frames
+ .borrow_mut()
+ .last_mut()
+ .expect("Scope stack underflow")
+ .symbols
+ .insert(sym, new_sym);
+ self.unique_symbols.insert(new_sym, Map::new());
+ new_sym
+ }
+ }
+
+ fn handle_types(&mut self, tys: Vec<Type>) -> Vec<Type> {
+ tys.into_iter().map(|t| self.handle_type(t)).collect()
+ }
+
+ /// Replace any generic names in the type with unique ones
+ fn handle_type(&mut self, ty: Type) -> Type {
+ use crate::Type::*;
+ match ty {
+ Named(nm, type_params) if &*nm.val() == "Tuple" => {
+ let new_type_params = self.handle_types(type_params);
+ Named(nm, new_type_params)
+ }
+ Named(nm, type_params) => {
+ let nm = self
+ .get_type_binding(nm)
+ .unwrap_or_else(|| panic!("Could not find declared type {}", nm))
+ .0;
+ let _guard = self.push_scope();
+ let new_type_params = self.handle_types(type_params);
+ Named(nm, new_type_params)
+ }
+ Func(args, rettype, type_params) => {
+ let _guard = self.push_scope();
+ let new_args = self.handle_types(args);
+ let new_rettype = Box::new(self.handle_type(*rettype));
+ let new_type_params = self.handle_types(type_params);
+ Func(new_args, new_rettype, new_type_params)
+ }
+ Struct(body, type_params) => {
+ let _guard = self.push_scope();
+ let new_type_params = self.handle_types(type_params);
+ let new_body = body
+ .into_iter()
+ .map(|(nm, ty)| (nm, self.handle_type(ty)))
+ .collect();
+ Struct(new_body, new_type_params)
+ }
+ Sum(body, type_params) => {
+ let _guard = self.push_scope();
+ let new_type_params = self.handle_types(type_params);
+ let new_body = body
+ .into_iter()
+ .map(|(nm, ty)| (nm, self.handle_type(ty)))
+ .collect();
+ Sum(new_body, new_type_params)
+ }
+ Array(t, size) => Array(Box::new(self.handle_type(*t)), size),
+ Uniq(inner) => Uniq(Box::new(self.handle_type(*inner))),
+
+ // all these have no subtypes
+ Prim(_) => ty,
+ Never => ty,
+ Enum(_) => ty,
+ }
+ }
+
+ fn handle_exprs(&mut self, exprs: Vec<hir::ExprNode>) -> Vec<hir::ExprNode> {
+ trace!("Handling exprs");
+ //let f = &mut |e| self.handle_expr2(e);
+ exprs.into_iter().map(|e| self.handle_expr(e)).collect()
+ //passes::exprs_map_pre(exprs, f)
+ }
+
+ /// TODO: These can't *quite* be part of the Pass framework,
+ /// because we need to handle different parts of the expr
+ /// in different orders depending on what it is, pushing and
+ /// popping scopes in different places as we go. We *could*
+ /// probably make it fit if we tried hard enough but I'm not
+ /// sure it's worth it, so for now we'll just brute-force
+ /// it and see how it looks at the end.
+ fn handle_expr(&mut self, expr: hir::ExprNode) -> hir::ExprNode {
+ use hir::Expr::*;
+ let f = &mut |e| match e {
+ Lit { val } => Lit { val },
+ Var { name } => {
+ let new_name = self.really_get_binding(name);
+ Var { name: new_name.0 }
+ }
+ BinOp { op, lhs, rhs } => BinOp {
+ op,
+ lhs: self.handle_expr(lhs),
+ rhs: self.handle_expr(rhs),
+ },
+ UniOp { rhs, op } => UniOp {
+ op,
+ rhs: self.handle_expr(rhs),
+ },
+ Block { body } => {
+ let _guard = self.push_scope();
+ let new_body = self.handle_exprs(body);
+ Block { body: new_body }
+ }
+ Loop { body } => Loop {
+ body: self.handle_exprs(body),
+ },
+ Funcall {
+ func,
+ params,
+ type_params,
+ } => {
+ let func = self.handle_expr(func);
+ let params = self.handle_exprs(params);
+ let type_params = self.handle_types(type_params);
+ Funcall {
+ func,
+ params,
+ type_params,
+ }
+ }
+ Let {
+ varname,
+ init,
+ typename,
+ mutable,
+ } => {
+ // init expression cannot refer to the same
+ // symbol name
+ let init = self.handle_expr(init);
+ let varname = self.bind_new_symbol(varname).0;
+ let typename = typename.map(|t| self.handle_type(t));
+ Let {
+ varname,
+ init,
+ typename,
+ mutable,
+ }
+ }
+ If { cases } => {
+ let cases = cases
+ .into_iter()
+ .map(|(test, body)| {
+ // The test cannot introduce a new scope unless
+ // it contains some cursed structure like a block
+ // or fn that introduces a new scope anyway, so.
+ let t = self.handle_expr(test);
+ let _guard = self.push_scope();
+ let b = self.handle_exprs(body);
+ (t, b)
+ })
+ .collect();
+ If { cases }
+ }
+ EnumCtor {
+ name,
+ variant,
+ value,
+ } => {
+ // Translate the type name
+ // No need to translate the variants 'cause
+ // they are namespaced anyway
+ let new_name = self.really_get_type_binding(name).0;
+ EnumCtor {
+ name: new_name,
+ variant,
+ value,
+ }
+ }
+ TupleCtor { body } => TupleCtor {
+ body: self.handle_exprs(body),
+ },
+ TupleRef { expr, elt } => TupleRef {
+ expr: self.handle_expr(expr),
+ elt,
+ },
+ StructCtor { body } => {
+ let body = body
+ .into_iter()
+ .map(|(nm, expr)| (nm, self.handle_expr(expr)))
+ .collect();
+ StructCtor { body }
+ }
+ StructRef { expr, elt } => StructRef {
+ expr: self.handle_expr(expr),
+ elt,
+ },
+ Assign { lhs, rhs } => Assign {
+ lhs: self.handle_expr(lhs),
+ rhs: self.handle_expr(rhs),
+ },
+ Break => Break,
+ Lambda { signature, body } => {
+ let _scope = self.push_scope();
+ // Here we DECLARE the types in the param
+ // and rettype, similar to in handle_decl()
+ let new_type_params = signature
+ .typeparams
+ .into_iter()
+ .map(|sym| self.bind_new_type(sym).0)
+ .collect();
+ let new_rettype = self.handle_type(signature.rettype);
+
+ // We have to do this AFTER adding the type params
+ // because the function args can mention
+ // type params.
+ let new_params = signature
+ .params
+ .into_iter()
+ .map(|(sym, typ)| (self.bind_new_symbol(sym).0, self.handle_type(typ)))
+ .collect();
+ let new_sig = hir::Signature {
+ params: new_params,
+ rettype: new_rettype,
+ typeparams: new_type_params,
+ };
+ let new_body = self.handle_exprs(body);
+ Lambda {
+ signature: new_sig,
+ body: new_body,
+ }
+ }
+ Return { retval } => Return {
+ retval: self.handle_expr(retval),
+ },
+ TypeCtor {
+ name,
+ type_params,
+ body,
+ } => {
+ // Translate the type name
+ let new_name = self.really_get_type_binding(name).0;
+ // Translate the type params it's called with
+ let new_params = self.handle_types(type_params);
+ let body = self.handle_expr(body);
+ TypeCtor {
+ name: new_name,
+ type_params: new_params,
+ body,
+ }
+ }
+ TypeUnwrap { expr } => TypeUnwrap {
+ expr: self.handle_expr(expr),
+ },
+ SumCtor {
+ name,
+ variant,
+ body,
+ } => {
+ let new_name = self.really_get_type_binding(name).0;
+ SumCtor {
+ name: new_name,
+ variant,
+ body: self.handle_expr(body),
+ }
+ }
+ ArrayCtor { body } => ArrayCtor {
+ body: self.handle_exprs(body),
+ },
+ ArrayRef { expr, idx } => ArrayRef {
+ expr: self.handle_expr(expr),
+ idx: self.handle_expr(idx),
+ },
+ Typecast { .. } => {
+ todo!()
+ }
+ Ref { .. } => {
+ todo!()
+ }
+ Deref { .. } => {
+ todo!()
+ }
+ };
+ expr.map(f)
+ }
+}
+
+pub(crate) fn predeclare_decls(symtbl: &mut Symtbl, decls: &[hir::Decl]) {
+ use hir::Decl::*;
+ for d in decls {
+ match d {
+ Function {
+ name,
+ signature: _,
+ body: _,
+ } => {
+ // Don't rename functions already declared as builtins, like "main"
+ // Little hacky but does what we want.
+ symtbl
+ .get_binding(*name)
+ .unwrap_or_else(|| symtbl.new_unique_symbol(*name));
+ }
+ TypeDef {
+ name,
+ params: _,
+ typedecl: _,
+ } => {
+ // Let's rename all types, which I think doesn't
+ // quite matter yet but will in the future when we
+ // have more powerful modules and such.
+ symtbl.bind_new_type(*name);
+ }
+ Const {
+ name,
+ typ: _,
+ init: _,
+ } => {
+ symtbl.new_unique_symbol(*name);
+ }
+ Import { .. } => todo!(),
+ }
+ }
+}
+
+fn handle_decl(symtbl: &mut Symtbl, decl: hir::Decl) -> hir::Decl {
+ use hir::Decl::*;
+ match decl {
+ Function {
+ name,
+ signature,
+ body,
+ } => {
+ // This has to happen before we push the scope, so
+ // if we have a function arg with the same name as the
+ // function it doesn't rename the function.
+ let fn_name = symtbl.really_get_binding(name).0;
+
+ let _scope = symtbl.push_scope();
+ // These must be handled first 'cause types in the
+ // function arguments can refer to them.
+ let new_type_params = signature
+ .typeparams
+ .into_iter()
+ .map(|sym| symtbl.bind_new_type(sym).0)
+ .collect();
+ let new_params = signature
+ .params
+ .into_iter()
+ .map(|(sym, typ)| (symtbl.bind_new_symbol(sym).0, symtbl.handle_type(typ)))
+ .collect();
+ let new_rettype = symtbl.handle_type(signature.rettype);
+ let new_sig = hir::Signature {
+ params: new_params,
+ rettype: new_rettype,
+ typeparams: new_type_params,
+ };
+ let new_body = symtbl.handle_exprs(body);
+ Function {
+ name: fn_name,
+ signature: new_sig,
+ body: new_body,
+ }
+ }
+ TypeDef {
+ name,
+ params,
+ typedecl,
+ } => {
+ // Ok this is exactly like resolving variables in a function.
+ // We bind the declared type params, check that all the types
+ // named in the body exist, and rename the ones in the params.
+ let ty_name = symtbl.really_get_type_binding(name).0;
+ let _scope = symtbl.push_scope();
+ let new_params = params
+ .into_iter()
+ .map(|sym| (symtbl.bind_new_type(sym).0))
+ .collect();
+ TypeDef {
+ name: ty_name,
+ params: new_params,
+ typedecl: symtbl.handle_type(typedecl),
+ }
+ }
+ Const { name, typ, init } => {
+ let new_name = symtbl.really_get_binding(name).0;
+ let new_type = symtbl.handle_type(typ);
+ let new_body = symtbl.handle_expr(init);
+ Const {
+ name: new_name,
+ typ: new_type,
+ init: new_body,
+ }
+ }
+ Import { .. } => todo!(),
+ }
+}
+
+/// Takes all value and type names and renames them all to
+/// remove scope dependence.
+///
+/// aka alpha-renaming
+pub fn resolve_symbols(ir: hir::Ir) -> (hir::Ir, Symtbl) {
+ let mut s = Symtbl::default();
+ s.add_builtins();
+ predeclare_decls(&mut s, &ir.decls);
+ let mut ir = ir;
+ ir.decls = ir
+ .decls
+ .into_iter()
+ .map(|d| handle_decl(&mut s, d))
+ .collect();
+ (ir, s)
+}
M src/typeck.rs +274 -289
@@ 2,15 2,14 @@
//! Operates on the HIR.
use std::borrow::Cow;
-use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
-use std::rc::Rc;
use log::*;
use crate::*;
use crate::hir;
+use crate::symtbl::Symtbl;
use crate::{Sym, Type};
/// A identifier to uniquely refer to our `TypeInfo`'s
@@ 44,6 43,8 @@ pub enum TypeInfo {
/// This is some generic type that has a name like `@A`
/// AKA a type parameter.
TypeParam(Sym),
+ /// Unique borrow. Probably needs a lifetime param of some kind added someday?
+ Uniq(TypeId),
}
impl TypeInfo {
@@ 66,11 67,8 @@ impl TypeInfo {
accm.into()
}
Named(s, g) => {
- let name = (&*s.val()).to_owned();
- let generics: Vec<_> = g
- .iter()
- .map(|t| tck.get(t).get_name(tck).to_owned())
- .collect();
+ let name = (*s.val()).to_owned();
+ let generics: Vec<_> = g.iter().map(|t| tck.get(t).get_name(tck)).collect();
let joined_generics = generics.join(", ");
format!("{}({})", name, joined_generics).into()
}
@@ 108,10 106,14 @@ impl TypeInfo {
// length of the array.
let len_str = len
.map(|l| format!("{}", l))
- .unwrap_or_else(|| format!("unknown"));
+ .unwrap_or_else(|| "unknown".to_string());
Cow::Owned(format!("{}[{}]", tck.get(id).get_name(tck), len_str))
}
TypeParam(sym) => Cow::Owned(format!("@{}", &*sym.val())),
+ Uniq(ty) => {
+ let typename = tck.get(ty).get_name(tck);
+ format!("&{}", typename).into()
+ }
}
}
}
@@ 125,7 127,12 @@ pub enum TypeError {
got: Type,
expected: Type,
},
- TypeListMismatch, // TODO
+ TypeListMismatch {
+ // We can't necessarily display TypeId's sensibly, so I guess
+ // we have to turn them into strings
+ expected: String,
+ got: String,
+ },
AmbiguousType {
expr_name: Cow<'static, str>,
},
@@ 208,7 215,9 @@ impl std::fmt::Display for TypeError {
}
impl TypeError {
- /// TODO: This should just be turned into the Display impl
+ /// TODO: This should just be turned into the Display impl.
+ /// But we might need access to the Tck sometimes? Do we make
+ /// it so that has to happen when the error is created? I guess so
pub fn format(&self) -> String {
match self {
TypeError::UnknownVar(sym) => format!("Unknown var: {}", sym.val()),
@@ 226,8 235,8 @@ impl TypeError {
expected.get_name(),
got.get_name()
),
- TypeError::TypeListMismatch => {
- format!("Type list mismatch, make better error message")
+ TypeError::TypeListMismatch { expected, got } => {
+ format!("Type list mismatch\nExpected {}, got {}", expected, got)
}
TypeError::AmbiguousType { expr_name } => {
format!("Ambiguous/unknown type for expression '{}'", expr_name)
@@ 343,13 352,14 @@ pub struct Tck {
impl Tck {
fn get(&self, id: &TypeId) -> &TypeInfo {
- self.vars.get(&id).unwrap_or_else(|| {
+ self.vars.get(id).unwrap_or_else(|| {
panic!(
"TypeId {:?} does not exist, should probably never happen",
id
)
})
}
+
/// Save the type variable associated with the given expr.
/// Panics if it is redefining the expr's type.
fn set_expr_type(&mut self, expr: &hir::ExprNode, ty: TypeId) {
@@ 384,7 394,11 @@ impl Tck {
})
}
- /// Create a new type var with whatever we have about its type
+ /// Create a new type var with whatever we have about its type.
+ ///
+ /// Someday it may be worthwhile to dedupe type vars for
+ /// terminals like I32 or {} , but for now this is fine
+ /// (if inefficient and slightly confusing)
pub fn insert(&mut self, info: TypeInfo) -> TypeId {
// Generate a new ID for our type term
self.id_counter += 1;
@@ 399,12 413,13 @@ impl Tck {
pub fn insert_known(&mut self, t: &Type) -> TypeId {
// Recursively insert all subtypes.
let tinfo = match t {
- Type::Prim(ty) => TypeInfo::Prim(ty.clone()),
+ Type::Prim(ty) => TypeInfo::Prim(*ty),
Type::Never => TypeInfo::Never,
Type::Enum(vals) => TypeInfo::Enum(vals.clone()),
Type::Named(s, args) => {
+ // BUGGO: These can be type params sometimes, hrmm
let new_args = args.iter().map(|t| self.insert_known(t)).collect();
- TypeInfo::Named(s.clone(), new_args)
+ TypeInfo::Named(*s, new_args)
}
Type::Func(args, rettype, typeparams) => {
let new_args = args.iter().map(|t| self.insert_known(t)).collect();
@@ 412,9 427,8 @@ impl Tck {
let new_typeparams = typeparams.iter().map(|t| self.insert_known(t)).collect();
TypeInfo::Func(new_args, new_rettype, new_typeparams)
}
- Type::Generic(s) => TypeInfo::TypeParam(*s),
Type::Array(ty, len) => {
- let new_body = self.insert_known(&ty);
+ let new_body = self.insert_known(ty);
TypeInfo::Array(new_body, Some(*len))
}
// TODO: Generics?
@@ 433,6 447,10 @@ impl Tck {
.collect();
TypeInfo::Sum(new_body)
}
+ Type::Uniq(ty) => {
+ let new_body = self.insert_known(&*ty);
+ TypeInfo::Uniq(new_body)
+ }
};
self.insert(tinfo)
}
@@ 467,15 485,10 @@ impl Tck {
}
/// Same as `get_struct_field_type()` but takes a tuple type and an integer.
- pub fn get_tuple_field_type(
- &mut self,
- symtbl: &Symtbl,
- tuple_type: TypeId,
- n: usize,
- ) -> TypeId {
+ pub fn get_tuple_field_type(&mut self, tuple_type: TypeId, n: usize) -> TypeId {
use TypeInfo::*;
match self.get(&tuple_type).clone() {
- Ref(t) => self.get_tuple_field_type(symtbl, t, n),
+ Ref(t) => self.get_tuple_field_type(t, n),
Named(nm, tys) if &*nm.val() == "Tuple" => tys.get(n).cloned().unwrap_or_else(|| {
panic!("Tuple has no field {}, valid fields are: {:#?}", n, tys)
}),
@@ 538,6 551,9 @@ impl Tck {
}
/// Unify two lists of types. They must be the same length.
+ ///
+ /// TODO: It'd be nice to have some reference to the EID or
+ /// such in the source code for better error handling.
fn unify_lists(
&mut self,
symtbl: &Symtbl,
@@ 550,7 566,24 @@ impl Tck {
}
Ok(())
} else {
- Err(TypeError::TypeListMismatch)
+ // gramble gramble no `Iterator::intersperse()`` in stable
+ let mut expected_str = String::from("[");
+ for e in a {
+ expected_str += &TypeInfo::Ref(*e).get_name(self);
+ expected_str += " ";
+ }
+ expected_str += "]";
+
+ let mut got_str = String::from("[");
+ for e in b {
+ got_str += &TypeInfo::Ref(*e).get_name(self);
+ got_str += " ";
+ }
+ got_str += "]";
+ Err(TypeError::TypeListMismatch {
+ expected: expected_str,
+ got: got_str,
+ })
}
}
@@ 568,15 601,20 @@ impl Tck {
}
use TypeInfo::*;
match (self.get(&a).clone(), self.get(&b).clone()) {
+ // IDK what these integers are but they must be the same as each other
+ (Prim(PrimType::UnknownInt), Prim(PrimType::UnknownInt)) => {
+ self.vars.insert(b, TypeInfo::Ref(a));
+ Ok(())
+ }
// Primitives just match directly
(Prim(p1), Prim(p2)) if p1 == p2 => Ok(()),
// Unknown integers unified with known integers become known
// integers.
- (Prim(PrimType::UnknownInt), Prim(PrimType::SInt(_))) => {
+ (Prim(PrimType::UnknownInt), Prim(PrimType::Int(_, _))) => {
self.vars.insert(a, TypeInfo::Ref(b));
Ok(())
}
- (Prim(PrimType::SInt(_)), Prim(PrimType::UnknownInt)) => {
+ (Prim(PrimType::Int(_, _)), Prim(PrimType::UnknownInt)) => {
self.vars.insert(b, TypeInfo::Ref(a));
Ok(())
}
@@ 603,6 641,8 @@ impl Tck {
// For type constructors, if their names are the same we try
// to unify their args
(Named(n1, args1), Named(n2, args2)) => {
+ // Ok they may be generics so we need to look up their
+ // actual types in the symbol table...?
if n1 == n2 {
self.unify_lists(symtbl, &args1, &args2)
} else {
@@ 627,6 667,12 @@ impl Tck {
// same basic idea
(Struct(body1), Struct(body2)) => {
for (nm, t1) in body1.iter() {
+ assert!(
+ body2.contains_key(nm),
+ "struct {:?} does not contain key '{}'",
+ body1,
+ nm
+ );
let t2 = body2[nm];
self.unify(symtbl, *t1, t2)?;
}
@@ 666,13 712,11 @@ impl Tck {
self.unify(symtbl, body1, body2)
// todo!("propegate array lengths")
}
- (Array(body1, len1), Array(body2, len2)) if len1 == len2 => {
- self.unify(symtbl, body1, body2)
- }
// For declared type parameters like @T they match if their names match.
// TODO: And if they have been declared? Not sure we can ever get to
// here if they haven't.
(TypeParam(s1), TypeParam(s2)) if s1 == s2 => Ok(()),
+ (Uniq(s1), Uniq(s2)) => self.unify(symtbl, s1, s2),
// If no previous attempts to unify were successful, raise an error
(a, b) => {
self.print_types();
@@ 694,17 738,17 @@ impl Tck {
match &self.get(&id) {
Unknown => Err(TypeError::ReconstructFail { tid: id }),
Never => Ok(Type::Never),
- Prim(ty) => Ok(Type::Prim(ty.clone())),
+ Prim(ty) => Ok(Type::Prim(*ty)),
Ref(id) => self.reconstruct(*id),
Enum(ts) => Ok(Type::Enum(ts.clone())),
Named(s, args) => {
let arg_types: Result<Vec<_>, _> =
args.iter().map(|x| self.reconstruct(*x)).collect();
- Ok(Type::Named(s.clone(), arg_types?))
+ Ok(Type::Named(*s, arg_types?))
}
Func(args, rettype, typeparams) => {
let real_args: Result<Vec<Type>, TypeError> =
- args.into_iter().map(|arg| self.reconstruct(*arg)).collect();
+ args.iter().map(|arg| self.reconstruct(*arg)).collect();
let type_param_types: Result<Vec<Type>, _> =
typeparams.iter().map(|x| self.reconstruct(*x)).collect();
@@ 714,13 758,13 @@ impl Tck {
type_param_types?,
))
}
- TypeParam(name) => Ok(Type::Generic(*name)),
+ TypeParam(name) => Ok(Type::named0(*name)),
Struct(body) => {
let real_body: Result<BTreeMap<_, _>, TypeError> = body
.iter()
.map(|(nm, t)| {
let new_t = self.reconstruct(*t)?;
- Ok((nm.clone(), new_t))
+ Ok((*nm, new_t))
})
.collect();
// TODO: The empty params here feels suspicious, verify.
@@ 736,12 780,16 @@ impl Tck {
.iter()
.map(|(nm, t)| {
let new_t = self.reconstruct(*t)?;
- Ok((nm.clone(), new_t))
+ Ok((*nm, new_t))
})
.collect();
let params = vec![];
Ok(Type::Sum(real_body?, params))
}
+ Uniq(ty) => {
+ let inner_type = self.reconstruct(*ty)?;
+ Ok(Type::Uniq(Box::new(inner_type)))
+ }
}
}
@@ 765,23 813,28 @@ impl Tck {
/// This has to actually be an empty hashtable on the first instantitaion
/// instead of the symtbl, since the symtbl is full of type parameter names from the
/// enclosing function and those are what we explicitly want to get away from.
+ ///
+ /// TODO: We can get rid of the known_types shit now that we rename
+ /// all type params to unique things, I think
fn instantiate(&mut self, t: &Type, known_types: Option<BTreeMap<Sym, TypeId>>) -> TypeId {
fn helper(tck: &mut Tck, named_types: &mut BTreeMap<Sym, TypeId>, t: &Type) -> TypeId {
let typeinfo = match t {
- Type::Prim(val) => TypeInfo::Prim(val.clone()),
+ Type::Prim(val) => TypeInfo::Prim(*val),
Type::Never => TypeInfo::Never,
Type::Enum(vals) => TypeInfo::Enum(vals.clone()),
- Type::Named(s, args) => {
- let inst_args: Vec<_> =
- args.iter().map(|t| helper(tck, named_types, t)).collect();
- TypeInfo::Named(s.clone(), inst_args)
- }
- Type::Generic(s) => {
- // If we know this is is a particular generic, match wiht it
+ Type::Named(s, typeparams) => {
+ // If we know this is a type params that has been declared, refer to it
if let Some(ty) = named_types.get(s) {
+ if typeparams.len() != 0 {
+ panic!("halp, HKT here");
+ }
TypeInfo::Ref(*ty)
} else {
- panic!("Referred to unknown generic named {}", s);
+ let inst_args: Vec<_> = typeparams
+ .iter()
+ .map(|t| helper(tck, named_types, t))
+ .collect();
+ TypeInfo::Named(*s, inst_args)
}
}
Type::Func(args, rettype, typeparams) => {
@@ 797,21 850,25 @@ impl Tck {
Type::Struct(body, _names) => {
let inst_body = body
.iter()
- .map(|(nm, ty)| (nm.clone(), helper(tck, named_types, ty)))
+ .map(|(nm, ty)| (*nm, helper(tck, named_types, ty)))
.collect();
TypeInfo::Struct(inst_body)
}
Type::Array(ty, len) => {
- let inst_ty = helper(tck, named_types, &*ty);
+ let inst_ty = helper(tck, named_types, ty);
TypeInfo::Array(inst_ty, Some(*len))
}
Type::Sum(body, _names) => {
let inst_body = body
.iter()
- .map(|(nm, ty)| (nm.clone(), helper(tck, named_types, ty)))
+ .map(|(nm, ty)| (*nm, helper(tck, named_types, ty)))
.collect();
TypeInfo::Sum(inst_body)
}
+ Type::Uniq(ty) => {
+ let inst_ty = helper(tck, named_types, ty);
+ TypeInfo::Uniq(inst_ty)
+ }
};
tck.insert(typeinfo)
}
@@ 821,8 878,8 @@ impl Tck {
// will do later.
// We do have to take any of those unknowns that are actually
// known and preserve that knowledge though.
- let known_types = &mut known_types.unwrap_or_else(Default::default);
- let type_params = t.get_type_params();
+ let known_types = &mut known_types.unwrap_or_default();
+ let type_params = t.get_toplevel_type_params();
// Probably a cleaner way to do this but oh well.
// We to through the type params, and if any of them
// are unknown we put a new TypeInfo::Unknown in for them.
@@ 835,104 892,46 @@ impl Tck {
}
}
-#[derive(Clone, Default)]
-struct ScopeFrame {
- /// Values are (type, mutability)
- symbols: BTreeMap<Sym, (TypeId, bool)>,
- types: BTreeMap<Sym, Type>,
-}
-
-/// Basic symbol table that maps names to type ID's
-/// and manages scope.
-/// Looks ugly, works well.
-#[derive(Clone)]
-pub struct Symtbl {
- frames: Rc<RefCell<Vec<ScopeFrame>>>,
-}
-
-impl Default for Symtbl {
- /// We start with an empty toplevel scope existing,
- /// then add some builtin's to it.
- fn default() -> Self {
- let s = Self {
- frames: Rc::new(RefCell::new(vec![ScopeFrame::default()])),
- };
- s
- }
-}
-
-pub struct ScopeGuard {
- scope: Symtbl,
-}
-
-impl Drop for ScopeGuard {
- fn drop(&mut self) {
- self.scope
- .frames
- .borrow_mut()
- .pop()
- .expect("Scope stack underflow");
- }
+#[derive(Debug, Clone)]
+struct TckInfo {
+ ty: TypeId,
+ mutable: bool,
}
impl Symtbl {
- fn add_builtins(&self, tck: &mut Tck) {
+ fn add_var(&mut self, var: Sym, ty: TypeId, mutable: bool) {
+ let info = TckInfo { ty, mutable };
+ self.put_info(symtbl::UniqueSym(var), info)
+ }
+
+ /// Checks whether the var exists in the currently alive scopes
+ fn get_var_binding(&self, var: Sym) -> Option<&TckInfo> {
+ let sym = symtbl::UniqueSym(var);
+ self.get_info::<TckInfo>(sym)
+ }
+
+ fn add_builtin_typeinfo(&mut self, tck: &mut Tck) {
for builtin in &*builtins::BUILTINS {
let ty = tck.insert_known(&builtin.sig);
self.add_var(builtin.name, ty, false);
}
}
- fn push_scope(&self) -> ScopeGuard {
- self.frames.borrow_mut().push(ScopeFrame::default());
- ScopeGuard {
- scope: self.clone(),
- }
- }
- fn add_var(&self, var: Sym, ty: TypeId, mutable: bool) {
- self.frames
- .borrow_mut()
- .last_mut()
- .expect("Scope stack underflow")
- .symbols
- .insert(var, (ty, mutable));
+ fn add_type(&mut self, var: Sym, ty: &Type) {
+ self.put_type_info2(var, ty.clone())
}
- /// Checks whether the var exists in the currently alive scopes
- fn get_var_binding(&self, var: Sym) -> Option<(TypeId, bool)> {
- for scope in self.frames.borrow().iter().rev() {
- let v = scope.symbols.get(&var);
- if v.is_some() {
- return v.cloned();
- }
- }
- return None;
- }
-
- fn add_type(&self, name: Sym, ty: &Type) {
- self.frames
- .borrow_mut()
- .last_mut()
- .expect("Scope stack underflow")
- .types
- .insert(name, ty.to_owned());
- }
-
- fn get_type(&self, ty: Sym) -> Option<Type> {
- for scope in self.frames.borrow().iter().rev() {
- let v = scope.types.get(&ty);
- if v.is_some() {
- return v.cloned();
- }
- }
- return None;
+ fn get_type(&self, var: Sym) -> Option<&Type> {
+ self.get_type_info2(var)
}
}
fn infer_lit(lit: &ast::Literal) -> TypeInfo {
match lit {
ast::Literal::Integer(_) => TypeInfo::Prim(PrimType::UnknownInt),
- ast::Literal::SizedInteger { bytes, .. } => TypeInfo::Prim(PrimType::SInt(*bytes)),
+ ast::Literal::SizedInteger { bytes, signed, .. } => {
+ TypeInfo::Prim(PrimType::Int(*bytes, *signed))
+ }
ast::Literal::Bool(_) => TypeInfo::Prim(PrimType::Bool),
//ast::Literal::EnumLit(nm, _) => TypeInfo::Named(*nm, vec![]),
}
@@ 942,20 941,22 @@ fn is_mutable_lvalue(symtbl: &Symtbl, ex
use hir::Expr::*;
match &*expr.e {
Var { name } => {
- let (_ty, mutable) = symtbl.get_var_binding(*name).unwrap();
- mutable
+ let info = symtbl.get_var_binding(*name).unwrap();
+ info.mutable
}
StructRef { expr, .. } => is_mutable_lvalue(symtbl, expr),
+ TupleRef { expr, .. } => is_mutable_lvalue(symtbl, expr),
+ TypeUnwrap { expr } => is_mutable_lvalue(symtbl, expr),
_ => false,
}
}
-/// TODO: This doesn't necessarily handle a func body as much
-/// as a block with its own scope, which is what we actually want.
-fn typecheck_func_body(
+/// This typechecks a block with its own scope, such as a function
+/// body or if arm.
+fn typecheck_block(
name: Option<Sym>,
tck: &mut Tck,
- symtbl: &Symtbl,
+ symtbl: &mut Symtbl,
signature: &hir::Signature,
body: &[hir::ExprNode],
) -> Result<TypeId, TypeError> {
@@ 975,7 976,7 @@ fn typecheck_func_body(
let tparams = signature
.typeparams
.iter()
- .map(|p| tck.insert_known(p))
+ .map(|p| tck.insert_known(&Type::named0(*p)))
.collect();
let f = tck.insert(TypeInfo::Func(params, rettype, tparams));
@@ 988,8 989,7 @@ fn typecheck_func_body(
symtbl.add_var(n, f, false);
}
- // Add params to function's scope
- let _guard = symtbl.push_scope();
+ // Add type info for params.
for (paramname, paramtype) in &signature.params {
let p = tck.insert_known(paramtype);
symtbl.add_var(*paramname, p, false);
@@ 1024,7 1024,7 @@ fn typecheck_func_body(
/// or unit if the list is empty. Does NOT push a new scope.
fn typecheck_exprs(
tck: &mut Tck,
- symtbl: &Symtbl,
+ symtbl: &mut Symtbl,
func_rettype: TypeId,
exprs: &[hir::ExprNode],
) -> Result<TypeId, TypeError> {
@@ 1033,7 1033,7 @@ fn typecheck_exprs(
}
let last_exprtype = exprs
.last()
- .and_then(|last_expr| Some(tck.get_expr_type(last_expr)))
+ .map(|last_expr| tck.get_expr_type(last_expr))
// If we have an empty body, our rettype is unit
.unwrap_or_else(|| tck.insert_known(&Type::unit()));
Ok(last_exprtype)
@@ 1041,11 1041,12 @@ fn typecheck_exprs(
fn typecheck_expr(
tck: &mut Tck,
- symtbl: &Symtbl,
+ symtbl: &mut Symtbl,
func_rettype: TypeId,
expr: &hir::ExprNode,
) -> Result<TypeId, TypeError> {
use hir::Expr::*;
+ trace!("Typechecking expr {:?}", expr);
let rettype: Result<_, TypeError> = match &*expr.e {
Lit { val } => {
let lit_type = infer_lit(val);
@@ 1054,11 1055,12 @@ fn typecheck_expr(
Ok(typeid)
}
Var { name } => {
- let (ty, _mutable) = symtbl
+ trace!("Looking up var {}", name);
+ let varinfo = symtbl
.get_var_binding(*name)
.unwrap_or_else(|| panic!("unbound var: {:?}", name));
- tck.set_expr_type(expr, ty);
- Ok(ty)
+ tck.set_expr_type(expr, varinfo.ty);
+ Ok(varinfo.ty)
}
BinOp { op, lhs, rhs } => {
let t1 = typecheck_expr(tck, symtbl, func_rettype, lhs)?;
@@ 1090,7 1092,7 @@ fn typecheck_expr(
let expr_in = typecheck_expr(tck, symtbl, func_rettype, rhs)?;
let expected_in = tck.uop_input_type(*op);
tck.unify(symtbl, expr_in, expected_in)?;
- // Similar problem as BOp
+ // BUGGO: Similar problem as BOp
let expected_output = tck.uop_output_type(*op, expected_in);
//tck.unify(symtbl, expr_in, expected_output)?;
tck.set_expr_type(expr, expected_output);
@@ 1119,14 1121,14 @@ fn typecheck_expr(
let func_type = typecheck_expr(tck, symtbl, func_rettype, func)?;
// We know this will work because we require full function signatures
// on our functions.
- let actual_func_type = tck.reconstruct(func_type)?;
- let actual_func_type_params = match &actual_func_type {
- Type::Func(_args, _rettype, typeparams) => {
+ let actual_func_type: Type = tck.reconstruct(func_type)?;
+ let (_actual_args, _actual_rettype, actual_type_params) = match &actual_func_type {
+ Type::Func(args, rettype, typeparams) => {
//trace!("Calling function {:?} is {:?}", func, actual_func_type);
// So when we call a function we need to know what its
// type params are. Then we bind those type parameters
// to things.
- typeparams
+ (args, rettype, typeparams)
}
other => panic!(
"Tried to call something not a function, it is a {:?}",
@@ 1134,88 1136,64 @@ fn typecheck_expr(
),
};
- // Synthesize what we know about the function
- // from the call.
- let params_list: Result<Vec<_>, _> = params
+ // Typecheck the function args
+ let params_list: Result<Vec<TypeId>, _> = params
.iter()
.map(|param| typecheck_expr(tck, symtbl, func_rettype, param))
.collect();
+
let params_list = params_list?;
- // Also synthesize what we know about the generics passed, if any,
- // or if there aren't any then figure them out from the params that
- // are passed.
- let type_param_vars = match (type_params.len(), actual_func_type_params.len()) {
- // all is good, no inference needed
- (0, 0) => vec![],
- // Infer type params from function call
- //
- // TODO: Should this be Ref(expected) rather than
- // Unknown? For now let's just infer it from nothing
- // and see what happens.
- (0, _expected) => actual_func_type_params
- .iter()
- .map(|_| tck.insert(TypeInfo::Unknown))
- .collect(),
- // Map the given type params to the ones the function expects.
- (given, expected) if given == expected => {
- type_params.iter().map(|t| tck.insert_known(t)).collect()
- }
- // Wrong number of type params given.
- (given, expected) => {
- let errmsg = format!(
- "Tried to call func with {} type parameters but it expects {} of them!",
- given, expected
- );
- return Err(errmsg.into());
- }
- };
+
+ // Instantiate the function's type params with the types that
+ // we have passed to it
+
// We don't know what the expected return type of the function call
// is yet; we make a type var that will get resolved when the enclosing
- // expression is.
- let rettype_var = tck.insert(TypeInfo::Unknown);
+ // expression is 'cause it may be a generic type.
+ let rettype_typevar = tck.insert(TypeInfo::Unknown);
+
+ // This attempts to infer function type params if they are absent. If the wrong number of params are given the
+ // unification below will just fail.
+ let type_params_vars: Vec<_> = if type_params.len() == 0 {
+ actual_type_params
+ .iter()
+ .map(|_t| tck.insert(TypeInfo::Unknown))
+ .collect()
+ } else {
+ // TODO: Oooooooh, we could make a _ type param work
+ // here just by having it become TypeInfo::Unknown
+ type_params.iter().map(|t| tck.insert_known(t)).collect()
+ };
+
// So this is now the inferred type that the function has been
// called with.
- let funcall_var = tck.insert(TypeInfo::Func(
- params_list.clone(),
- rettype_var,
- type_param_vars.clone(),
+ let funcall_typeinfo = tck.insert(TypeInfo::Func(
+ params_list,
+ rettype_typevar,
+ type_params_vars.clone(),
));
- // Now I guess this is where we make a copy of the function
- // with new generic types.
- // Is this "instantiation"???
- // Yes it is. Differentiate "type parameters", which are the
- // types a function takes as input (our `Generic` or `TypeParam`
- // things I suppose), from "type variables" which are the TypeId
- // we have to solve for.
- //
- // So we go through the generics the function declares and create
- // new type vars for each of them.
- //
- // We also create type variables for any type paramss we've been
- // given values to.
- //
- // Ok we *also* need to see if we can infer type params on function
- // calls that haven't been passed them explicitly.
- // like, if we have id(thing @T | @T) @T and we call id(false | Bool)
- // that is fine, if we call id(false) then we need to infer the
- // Bool there. We require either all or no type params for these
- // functions, so it's actually possible.
- let input_type_params = type_param_vars
+ // Instantiate the function's declared type params.
+ // Basically make unique copies of its type params for this
+ // one specific function call
+ let input_type_params = type_params_vars
.iter()
- .zip(actual_func_type_params.iter())
+ .zip(actual_type_params.iter())
.filter_map(
|(given_type_param, fnexpr_type_param)| match fnexpr_type_param {
- Type::Generic(nm) => Some((*nm, given_type_param.clone())),
+ Type::Named(nm, _) => Some((*nm, *given_type_param)),
_ => None,
},
)
.collect();
let heck = tck.instantiate(&actual_func_type, Some(input_type_params));
- tck.unify(symtbl, heck, funcall_var)?;
- tck.set_expr_type(expr, rettype_var);
- Ok(rettype_var)
+ // Does it match the real function type?
+ // tck.unify(symtbl, func_type, funcall_typeinfo)?;
+ tck.unify(symtbl, heck, funcall_typeinfo)?;
+
+ tck.set_expr_type(expr, rettype_typevar);
+ Ok(rettype_typevar)
}
Let {
varname,
@@ 1251,7 1229,6 @@ fn typecheck_expr(
let case_type = typecheck_expr(tck, symtbl, func_rettype, case)?;
tck.unify(symtbl, case_type, booltype)?;
- let _guard = symtbl.push_scope();
let body_type = typecheck_exprs(tck, symtbl, func_rettype, body)?;
tck.unify(symtbl, body_type, rettype)?;
}
@@ 1263,18 1240,15 @@ fn typecheck_expr(
variant,
value,
} => {
- let enumtype = symtbl.get_type(*name).expect("Unknown enum type!");
+ let errmsg = format!("Unknown enum type in enum constructor: {}", name);
+ let enumtype = symtbl.get_type(*name).expect(&errmsg);
// Make sure type actually is an enum
// Enums are terminal types so I guess we don't need to do
- // any inference or unification or such here?
+ // any inference or unification or such here
match &enumtype {
Type::Enum(body) => {
// make sure variant actually exists
- if !body
- .iter()
- .find(|(ky, vl)| (*ky, *vl) == (*variant, *value))
- .is_some()
- {
+ if !body.iter().any(|(ky, vl)| (*ky, *vl) == (*variant, *value)) {
return Err(format!(
"Enum {} variant {} does not match typedef",
name, variant
@@ 1309,7 1283,7 @@ fn typecheck_expr(
TupleRef { expr: e, elt } => {
typecheck_expr(tck, symtbl, func_rettype, e)?;
let tuple_type = tck.get_expr_type(e);
- let field_type = tck.get_tuple_field_type(symtbl, tuple_type, *elt);
+ let field_type = tck.get_tuple_field_type(tuple_type, *elt);
trace!(
"Heckin tuple ref... Type of {:?}.{} is {:?}",
e,
@@ 1326,7 1300,7 @@ fn typecheck_expr(
.map(|(name, expr)| {
// ? in map doesn't work too well...
trace!("Checking field {} expr {:?}", name, expr);
- typecheck_expr(tck, symtbl, func_rettype, expr).and_then(|t| (Ok((*name, t))))
+ typecheck_expr(tck, symtbl, func_rettype, expr).map(|t| (*name, t))
})
.collect();
trace!("Typechecking struct ctor: {:?}", body_types);
@@ 1357,7 1331,7 @@ fn typecheck_expr(
let hopefully_a_struct = symtbl.get_type(s).unwrap();
match hopefully_a_struct {
Type::Struct(body, _elts) => Ok(tck.insert_known(&body[elt])),
- _other => Err(format!("Yeah I know this is wrong bite me").into()),
+ _other => Err("Yeah I know this is wrong bite me".to_string().into()),
}
}
other => Err(format!(
@@ 1379,7 1353,7 @@ fn typecheck_expr(
expr_name: s.into(),
});
*/
- return Err(format!("Mutability mismatch in 'assignment'").into());
+ return Err("Mutability mismatch in 'assignment'".to_string().into());
}
tck.unify(symtbl, lhs_type, rhs_type)?;
let unit = tck.insert_known(&Type::unit());
@@ 1395,7 1369,7 @@ fn typecheck_expr(
Ok(unit)
}
Lambda { signature, body } => {
- let t = typecheck_func_body(None, tck, symtbl, &signature, body)?;
+ let t = typecheck_block(None, tck, symtbl, signature, body)?;
tck.set_expr_type(expr, t);
Ok(t)
}
@@ 1413,16 1387,17 @@ fn typecheck_expr(
type_params,
body,
} => {
- let named_type = symtbl.get_type(*name).expect("Unknown type constructor");
+ trace!("Handling typector {}, {:?}, {:?}", name, type_params, body);
+ let errmsg = format!("Unknown type constructor: {:?}", *name);
+ let named_type: &Type = symtbl.get_type(*name).expect(&errmsg);
trace!("Got type named {}: is {:?}", name, named_type);
// Ok if we have declared type params we gotta instantiate them
// to match the type's generics.
- //let type_param_names = named_type.get_generic_args();
- let type_param_names = named_type.get_type_params();
+ let type_param_names = named_type.get_toplevel_type_params();
assert_eq!(
type_params.len(),
type_param_names.len(),
- "Type '{}' expected params {:?} but got params {:?}",
+ "Type constructor '{}' expected params {:?} but got params {:?}",
name,
type_param_names,
type_params
@@ 1435,8 1410,7 @@ fn typecheck_expr(
tck.unify(symtbl, tid, body_type)?;
trace!("Done unifying type ctor");
// The type the expression returns
- let constructed_type =
- tck.insert_known(&Type::Named(name.clone(), type_params.clone()));
+ let constructed_type = tck.insert_known(&Type::Named(*name, type_params.clone()));
tck.set_expr_type(expr, constructed_type);
Ok(constructed_type)
}
@@ 1460,22 1434,19 @@ fn typecheck_expr(
let tinfo = &tck.get(&t);
match tinfo {
// Lookup name in symbol table
- TypeInfo::Named(nm, params) => return Ok((*nm, params.clone())),
+ TypeInfo::Named(nm, params) => Ok((*nm, params.clone())),
// Follow type ref
TypeInfo::Ref(id) => try_resolve_named_type(tck, symtbl, *id),
- other => {
- return Err(format!(
- "Can't resolve {:?} because it's not a named type!",
- other
- )
- .into())
- }
+ other => Err(format!(
+ "Can't resolve {:?} because it's not a named type!",
+ other
+ )),
}
}
let body_type = typecheck_expr(tck, symtbl, func_rettype, inner_expr)?;
let (nm, params) = try_resolve_named_type(tck, symtbl, body_type)?;
- let inner_type = symtbl
+ let inner_type: &Type = symtbl
.get_type(nm)
.ok_or(format!("Named type {} is not known!", nm))?;
trace!("Inner type is {:?}", inner_type);
@@ 1486,7 1457,7 @@ fn typecheck_expr(
//
// But then we have to bind those type params to
// what we *know* about the type already...
- let type_param_names = inner_type.get_type_params();
+ let type_param_names = inner_type.get_toplevel_type_params();
let known_type_params = type_param_names
.iter()
.cloned()
@@ 1504,12 1475,13 @@ fn typecheck_expr(
} => {
let named_type = symtbl
.get_type(*name)
- .expect("Unknown sum type constructor");
+ .expect("Unknown sum type constructor")
+ .clone();
// This might be wrong, we can probably do it the other way around
// like we do with TypeUnwrap: start by checking the inner expr type and make
// sure it matches what we expect. Generics might require that.
- match named_type.clone() {
+ match named_type {
Type::Sum(sum_body, generics) => {
let variant_type = &sum_body[variant];
let variant_typeid = tck.insert_known(variant_type);
@@ 1520,7 1492,7 @@ fn typecheck_expr(
// sum type we conjure up
// TODO: Might be easier to have our compiler generate
// the TypeCtor for it?
- let rettype = tck.insert_known(&Type::Named(name.clone(), generics));
+ let rettype = tck.insert_known(&Type::Named(*name, generics));
tck.set_expr_type(expr, rettype);
Ok(rettype)
}
@@ 1532,10 1504,15 @@ fn typecheck_expr(
// So if the body has len 0 we can't know what type it is.
// So we create a new unknown and then try unifying it with
// all the expressions in the body.
+
+ // Ok so this typechecks the first element of the array correctly
+ // but not subsequent ones.
let body_type = tck.insert(TypeInfo::Unknown);
- for expr in body {
- let expr_type = typecheck_expr(tck, symtbl, func_rettype, expr)?;
- tck.unify(symtbl, body_type, expr_type)?;
+ let last_expr_type = tck.insert(TypeInfo::Ref(body_type));
+ for inner_expr in body {
+ let expr_type = typecheck_expr(tck, symtbl, func_rettype, inner_expr)?;
+ //let intended_expr_type = tck.insert(TypeInfo::Ref(body_type));
+ tck.unify(symtbl, last_expr_type, expr_type)?;
}
let arr_type = tck.insert(TypeInfo::Array(body_type, Some(len)));
tck.set_expr_type(expr, arr_type);
@@ 1556,6 1533,22 @@ fn typecheck_expr(
Ok(elt_type)
}
Typecast { e: _, to: _ } => todo!(),
+ Ref { expr: inner_expr } => {
+ // We can take a reference to basically anything
+ let expr_type = typecheck_expr(tck, symtbl, func_rettype, &*inner_expr)?;
+ // NOT a TypeInfo::Ref !!!
+ let ref_type = tck.insert(TypeInfo::Uniq(expr_type));
+ tck.set_expr_type(expr, ref_type);
+ Ok(ref_type)
+ }
+ Deref { expr: inner_expr } => {
+ let expr_type = typecheck_expr(tck, symtbl, func_rettype, &*inner_expr)?;
+ let inner_type = tck.insert(TypeInfo::Unknown);
+ let desired_type = tck.insert(TypeInfo::Uniq(inner_type));
+ tck.unify(symtbl, expr_type, desired_type)?;
+ tck.set_expr_type(expr, inner_type);
+ Ok(inner_type)
+ }
};
if let Err(e) = rettype {
panic!("Error typechecking expression {:?}: {}", expr, e);
@@ 1563,6 1556,9 @@ fn typecheck_expr(
rettype
}
+/// TODO: I am a little miffed because the alpha-renaming was supposed
+/// to make it so this had been done already, but we still need to do all
+/// the type-y things for it.
fn predeclare_decls(tck: &mut Tck, symtbl: &mut Symtbl, decls: &[hir::Decl]) {
use hir::Decl::*;
for d in decls {
@@ 1582,32 1578,26 @@ fn predeclare_decls(tck: &mut Tck, symtb
let typeparams = signature
.typeparams
.iter()
- .map(|t| tck.insert_known(t))
+ .map(|t| tck.insert_known(&Type::named0(*t)))
.collect();
let f = tck.insert(TypeInfo::Func(params, rettype, typeparams));
symtbl.add_var(*name, f, false);
}
TypeDef {
name,
- params,
+ params: _,
typedecl,
} => {
- // Make sure that there are no unbound generics in the typedef
- // that aren't mentioned in the params.
- let generic_names: BTreeSet<Sym> = typedecl.get_type_params().into_iter().collect();
- let param_names: BTreeSet<Sym> = params.iter().cloned().collect();
- let difference: Vec<_> = generic_names.symmetric_difference(¶m_names).collect();
- if difference.len() != 0 {
- // gramble gramble strings
- let differences: Vec<_> = difference
- .into_iter()
- .map(|sym| (&*sym.val()).clone())
- .collect();
- let differences = differences.join(", ");
- panic!("Error in typedef {}: Type params do not match generics mentioned in body. Unmatched types: {}", name, differences);
- }
-
+ //todo!("Start here");
+ // ...ok but when checking the typector it needs
+ // to get the type params and make sure those match
+ // too... or can we just wing it 'cause we always
+ // generate those so they're always correct?
+ // A typedef really needs to be a signature, not just
+ // a name...
+ trace!("Predeclaring type {:?} with decl {:?}", *name, typedecl);
// Remember that we know about a type with this name
+ // All the real work is done in typecheck()
symtbl.add_type(*name, typedecl)
}
Const {
@@ 1616,7 1606,7 @@ fn predeclare_decls(tck: &mut Tck, symtb
init: _,
} => {
// We don't try typechecking the body yet.
- let ty = tck.insert_known(&typename);
+ let ty = tck.insert_known(typename);
symtbl.add_var(*name, ty, false)
}
Import { .. } => todo!(),
@@ 1629,11 1619,10 @@ fn predeclare_decls(tck: &mut Tck, symtb
/// terms to each of your nodes with whatever information you have available. You
/// will also need to call `engine.unify(x, y)` when you know two nodes have the
/// same type, such as in the statement `x = y;`."
-pub fn typecheck(ast: &hir::Ir) -> Result<Tck, TypeError> {
+pub fn typecheck(ast: &hir::Ir, symtbl: &mut Symtbl) -> Result<Tck, TypeError> {
let mut t = Tck::default();
let tck = &mut t;
- let symtbl = &mut Symtbl::default();
- symtbl.add_builtins(tck);
+ symtbl.add_builtin_typeinfo(tck);
predeclare_decls(tck, symtbl, &ast.decls);
for decl in &ast.decls {
use hir::Decl::*;
@@ 1644,7 1633,7 @@ pub fn typecheck(ast: &hir::Ir) -> Resul
signature,
body,
} => {
- let t = typecheck_func_body(Some(*name), tck, symtbl, signature, body);
+ let t = typecheck_block(Some(*name), tck, symtbl, signature, body);
t.unwrap_or_else(|e| {
error!("Error, type context is:");
tck.print_types();
@@ 1659,19 1648,19 @@ pub fn typecheck(ast: &hir::Ir) -> Resul
// TODO: Handle recursive types properly? Somehow.
// Make sure that there are no unbound generics in the typedef
// that aren't mentioned in the params.
- let generic_names: BTreeSet<Sym> = typedecl.get_type_params().into_iter().collect();
+ let generic_names: BTreeSet<Sym> =
+ typedecl.get_toplevel_type_params().into_iter().collect();
let param_names: BTreeSet<Sym> = params.iter().cloned().collect();
let difference: Vec<_> = generic_names.symmetric_difference(¶m_names).collect();
- if difference.len() != 0 {
+ if !difference.is_empty() {
let differences: Vec<_> = difference
.into_iter()
- .map(|sym| (&*sym.val()).clone())
+ .map(|sym| (*sym.val()).clone())
.collect();
panic!("Error in typedef {}: Type params do not match generics mentioned in body. Unmatched types: {:?}", name, differences);
}
- // Remember that we know about a type with this name
- symtbl.add_type(*name, typedecl)
+ // Unnecessary to call add_type() since we've already predeclared it
}
Const {
name: _,
@@ 1682,11 1671,7 @@ pub fn typecheck(ast: &hir::Ir) -> Resul
// scope, since it may theoretically be a `let` or
// something that introduces new names inside it.
let desired_type = tck.insert_known(typename);
- let init_type = {
- let _guard = symtbl.push_scope();
- let t = typecheck_expr(tck, symtbl, desired_type, init).unwrap();
- t
- };
+ let init_type = { typecheck_expr(tck, symtbl, desired_type, init).unwrap() };
tck.unify(symtbl, desired_type, init_type)
.expect("Error typechecking const decl");
//println!("Typechecked const {}, type is {:?}", name, init_type);
@@ 1695,13 1680,13 @@ pub fn typecheck(ast: &hir::Ir) -> Resul
}
}
// Print out toplevel symbols
- for (name, id) in &symtbl.frames.borrow().last().unwrap().symbols {
- info!(
- "value {} type is {:?}",
- name,
- tck.reconstruct(id.0).map(|t| t.get_name())
- );
- }
+ // for (name, id) in &symtbl.frames.borrow().last().unwrap().symbols {
+ // info!(
+ // "value {} type is {:?}",
+ // name,
+ // tck.reconstruct(id.0).map(|t| t.get_name())
+ // );
+ // }
trace!("Type variables:");
for (id, info) in &tck.vars {
trace!(" ${} = {info:?}", id.0);
M tests/endtoend.rs +2 -2
@@ 52,7 52,7 @@ fn main() {
// succeed on others where actual compilation fails (like type errors).
// Ah well.
let mut fmt = Command::new("cargo");
- fmt.args(&[
+ fmt.args([
"run",
"--bin",
"garnetfmt",
@@ 66,7 66,7 @@ fn main() {
exe.push(&tempdir);
exe.push(p.file_stem().unwrap());
let mut compile = Command::new("cargo");
- compile.args(&[
+ compile.args([
"run",
"--bin",
"garnetc",
M tests/programs/ambi_parse1.gt +5 -3
@@ 19,7 19,9 @@
fn foo() I32 =
let y I32 = 2
- -1
+ -- See https://todo.sr.ht/~icefox/garnetc/28
+ -- for this and related numbers needing type annotations
+ -1_I32
y
end
@@ 48,9 50,9 @@ fn main() {} =
let x I32 = 2 - 1
let y I32 = 2
- -1
+ -1_I32
let z I32 = 2;
- -1
+ -1_I32
__println(x)
__println(y)
__println(z)
M tests/programs/block1.gt +2 -1
@@ 9,7 9,8 @@
fn main() {} =
let x I32 = do
- 12
+ -- See https://todo.sr.ht/~icefox/garnetc/28
+ 12_I64
42
end
__println(x)
M tests/programs/comparison.gt +14 -9
@@ 36,15 36,20 @@ fn main() {} =
__println_bool(false /= true)
__println(2)
- __println_bool(1 == 1)
- __println_bool(1 /= 0)
- __println_bool(0 == 1)
- __println_bool(0 == 0)
- __println_bool(0 /= 1)
+ -- TODO LATER:
+ -- ah, so THIS is why rustc assumes an integer is I32 if it can't come
+ -- up with something better. Type inference knows that `unknownint == unknownint`
+ -- is ok but cannot figure out what the unknownint actually is.
+ -- See https://todo.sr.ht/~icefox/garnetc/28
+ __println_bool(1_I32 == 1)
+ __println_bool(1_I32 /= 0)
+ __println_bool(0_I32 == 1)
+ __println_bool(0_I32 == 0)
+ __println_bool(0_I32 /= 1)
__println(3)
- __println_bool(0 < 1)
- __println_bool(0 > 1)
- __println_bool(0 <= 1)
- __println_bool(0 >= 1)
+ __println_bool(0_I32 < 1)
+ __println_bool(0_I32 > 1)
+ __println_bool(0_I32 <= 1)
+ __println_bool(0_I32 >= 1)
end
M tests/programs/function_generics1.gt +3 -3
@@ 9,15 9,15 @@
-- 42
-- 42
-fn foo(i @T) @T =
+fn foo(|T| i T) T =
i
end
-fn first(a @T1, b @T2) @T1 =
+fn first(|T1, T2| a T1, b T2) T1 =
a
end
-fn second(a @T1, b @T2) @T2 =
+fn second(|T1, T2| a T1, b T2) T2 =
b
end
M tests/programs/function_generics2.gt +3 -3
@@ 3,15 3,15 @@
-- Compile:
-- status: error
--
-fn foo(i @T) @T =
+fn foo(i T) T =
i
end
-fn first(a @T1, b @T2) @T1 =
+fn first(a T1, b T2) T1 =
a
end
-fn second(a @T1, b @T2) @T2 =
+fn second(a T1, b T2) T2 =
b
end
M tests/programs/test1.gt +1 -1
@@ 32,7 32,7 @@ fn invalid2(x I32) Bool =
end
-/
-fn identity(i @T) @T =
+fn identity(|T| i T) T =
i
end
M tests/programs/test2.gt +4 -4
@@ 8,14 8,14 @@
-- 1
-- false
-fn identity(i @T) @T =
- let y @T = i
+fn identity(|T| i T) T =
+ let y T = i
y
end
fn main() {} =
- let a I32 = identity(1)
+ let a I32 = identity(|I32| 1)
__println(a)
- let c Bool = identity(false)
+ let c Bool = identity(|Bool| false)
__println_bool(c)
end
M tests/programs/test_array1.gt +3 -3
@@ 3,12 3,12 @@
-- Compile:
-- status: success
-fn identity(i @T) @T =
- let y @T = i
+fn identity(|T| i T) T =
+ let y T = i
y
end
-fn foo(i [3]@T) [3]@T =
+fn foo(|T| i [3]T) [3]T =
i
end
M tests/programs/test_array4.gt +1 -2
@@ 29,6 29,5 @@ fn main() {} =
let b = a[1][1]
__println(b)
- __println(actually_use_array([1, 2, 3, 4, 5]))
-
+ __println(actually_use_array([1, 2, 3, 4, 5]))
end
A => tests/programs/test_borrow1.gt +17 -0
@@ 0,0 1,17 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+
+
+fn should_fail() &{I32, I32} =
+ let f1 = {1, 2}
+ -- for now this should be just a unique reference
+ let f2 &{I32, I32} = f1&
+ f2
+end
+
+fn main() {} =
+ should_fail()
+ {}
+end
M tests/programs/test_eq_module.gt +3 -3
@@ 9,8 9,8 @@
-- true
-type Eq(@T) = struct
- eq: fn(@T, @T) Bool,
+type Eq(T) = struct(T)
+ eq: fn(T, T) Bool,
end
const IntEq Eq(I32) = Eq {
@@ 21,7 21,7 @@ const BoolEq Eq(Bool) = Eq {
.eq = fn(lhs Bool, rhs Bool) Bool = lhs == rhs end,
}
-fn all(|@T| eq_impl Eq(@T), val @T, array [3]@T) Bool =
+fn all(|T| eq_impl Eq(T), val T, array [3]T) Bool =
let mut i I32 = 0
loop
if i == 3 then break end
M tests/programs/test_failure.gt +2 -2
@@ 16,11 16,11 @@ fn baz(x Bool) Bool =
x
end
-fn identity(i @T) @T =
+fn identity(i T) T =
i
end
-fn identity2(i @X) @X =
+fn identity2(i X) X =
identity(i)
end
M tests/programs/test_forever1.gt +1 -1
@@ 6,7 6,7 @@
-- Who doesn't like infinite recursion?
-- Actually I'm not sure what this should do.
-- ...According to OCaml and Rust, the *type checking* is fine.
-fn zoom(i @T) @T =
+fn zoom(|T| i T) T =
zoom(i)
end
M tests/programs/test_lambda3.gt +1 -1
@@ 4,7 4,7 @@
-- status: success
-fn apply(f fn(@T) @T, arg @T) @T =
+fn apply(|T| f fn(T) T, arg T) T =
f(arg)
end
M tests/programs/test_lambda4.gt +1 -1
@@ 4,7 4,7 @@
-- status: success
-fn apply(f fn(@In) @Out, arg @In) @Out =
+fn apply(|In, Out| f fn(In) Out, arg In) Out =
f(arg)
end
M tests/programs/test_lambda5.gt +1 -1
@@ 4,7 4,7 @@
-- status: error
-fn apply(f fn(@In) @Out, arg @In) @Out =
+fn apply(f fn(In) Out, arg In) Out =
f(arg)
end
A => tests/programs/test_lambda_generics1.gt +30 -0
@@ 0,0 1,30 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+
+
+-- Run:
+-- status: success
+-- stdout:
+-- 12
+
+-- TODO: This should succeed but does not yet.
+-- We need some way to make closures over type parameters;
+-- either directly or by monomorphization.
+-- I tried doing it directly a la lambda lifting and it
+-- really didn't work too heckin' well, so we'll try monomorph.
+
+
+fn make_id(|T1|) fn(T1) T1 =
+ let id = fn(x T1) T1 =
+ x
+ end
+ id
+end
+
+fn main() {} =
+ let id = make_id(|I32|)
+ let res = id(12)
+ __println(res)
+end
A => tests/programs/test_lambda_generics2.gt +41 -0
@@ 0,0 1,41 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+
+
+-- Run:
+-- status: success
+-- stdout:
+-- 12
+
+
+-- TODO: This should succeed but does not yet.
+-- We need some way to make closures over type parameters;
+-- either directly or by monomorphization.
+-- I tried doing it directly a la lambda lifting and it
+-- really didn't work too heckin' well, so we'll try monomorph.
+
+-- Can we stuff a lambda into a struct with type parameters,
+-- and have we want?
+type Thing(T1, T2) = struct(T1, T2)
+ do_stuff: fn(T1, T2) I32,
+end
+
+fn impl_thing(|T1, T2|) Thing(T1, T2) =
+ let my_thing = Thing( {
+ .do_stuff = fn(x T1, y T2) I32 =
+ 12
+ end
+ })
+ my_thing
+end
+
+fn main() {} =
+/-
+ let impl = impl_thing(|I32, U32|)
+ let res = impl$.do_stuff(1_I32, 2_U32)
+ __println(res)
+ -/
+ __println(12)
+end
M tests/programs/test_let1.gt +4 -4
@@ 4,13 4,13 @@
-- status: success
-fn id(|@T| x @T) @T =
+fn id(|T| x T) T =
x
end
fn main() {} =
- let f1 = id(|I32| 3_I32)
- let f2 = id(3_I32)
- let f3 I32 = id(3)
+ let f1 = id(|I32| 3)
+ let f2 = id(|I32| 3_I32)
+ let f3 I32 = id(|I32| 3)
end
M tests/programs/test_let2.gt +27 -7
@@ 4,12 4,12 @@
-- status: success
-type Foo(@Thing) = struct
- a: @Thing,
+type Foo(Thing) = struct(Thing)
+ a: Thing,
b: Bool
end
-fn id(|@T| x @T) @T =
+fn id(|T| x T) T =
x
end
@@ 20,22 20,42 @@ fn main() {} =
.b = false
}))
- let f1 = id(|Foo(I32)| Foo {
+ let f2 = id(|Foo(Bool)| Foo(|Bool| {
+ .a = true,
+ .b = false
+ }))
+
+ -- Now can we infer the type parameters for id() and Foo?
+
+ -- id() has type annotations, type constructor does not
+ let f3 = id(|Foo(I32)| Foo {
.a = 3_I32,
.b = false
})
- let f2 = id(|Foo(Bool)| Foo {
+ let f4 = id(|Foo(Bool)| Foo {
.a = true,
.b = false
})
- let f3 = id(Foo {
+ -- type constructor has type annotations, id() does not
+ let f5 = id(Foo(|I32| {
+ .a = 3_I32,
+ .b = false
+ }))
+
+ let f6 = id(Foo(|Bool| {
+ .a = true,
+ .b = false
+ }))
+
+ -- Neither have type annotations
+ let f7 = id(Foo {
.a = 3_I32,
.b = false
} )
- let f4 = id(Foo {
+ let f8 = id(Foo {
.a = true,
.b = false
} )
M tests/programs/test_module1.gt +6 -4
@@ 14,9 14,9 @@
--
-- No associated types either, so we just make it a type param for now,
-- which surprisingly appears to work.
-type Hasher(@Selff, @Out) = struct
- write: fn(@Selff, I32) @Selff,
- finish: fn(@Selff) @Out,
+type Hasher(Self, Out) = struct(Self, Out)
+ write: fn(Self, I32) Self,
+ finish: fn(Self) Out,
end
type DumbHashState = I32
@@ 24,7 24,9 @@ type DumbHashState = I32
fn main() {} =
let dumbhasher Hasher(DumbHashState, I32) = Hasher {
- .write = fn(s DumbHashState, i I32) DumbHashState = DumbHashState(i) end,
+ .write = fn(s DumbHashState, i I32) DumbHashState =
+ DumbHashState(i)
+ end,
.finish = fn(s DumbHashState) I32 = 3 end
}
let hash_state = DumbHashState(1)
M tests/programs/test_module2.gt +3 -3
@@ 11,8 11,8 @@ type String = I32
-- TODO: Make the compiler mangle "Self" since it's a keyword in Rust
-- Also "self" and "impl"
-type Show(@Selff) = struct
- show: fn(@Selff) String
+type Show(Self) = struct(Self)
+ show: fn(Self) String
end
-- To make this work we need monomorphization, 'cause Rust refuses to
@@ 21,7 21,7 @@ const IntShow Show(I32) = Show {
.show = fn(x I32) String = String(x) end
}
-fn show(show Show(@T), val @T) String =
+fn show(|T| show Show(T), val T) String =
show$.show(val)
end
M tests/programs/test_module3.gt +19 -19
@@ 11,8 11,8 @@
-- Fine let's try something simpler.
-type Eq(@Selff) = struct
- eq: fn(@Selff, @Selff) Bool,
+type Eq(Selff) = struct
+ eq: fn(Selff, Selff) Bool,
end
-- The name here has to be something other than Eq(I32) 'cause we
@@ 28,8 28,8 @@ const IntEq Eq(I32) = Eq {
end,
}
-type Ord(@Selff) = struct
- cmp: fn(@Selff, @Selff) I32,
+type Ord(Selff) = struct
+ cmp: fn(Selff, Selff) I32,
end
const IntOrd Ord(I32) = Ord {
@@ 39,47 39,47 @@ const IntOrd Ord(I32) = Ord {
}
-type From(@Selff, @In) = struct
- from: fn(@In) @Selff
+type From(Selff, In) = struct
+ from: fn(In) Selff
end
const BoolFromInt From(Bool, I32) = From {
.from = fn(i I32) Bool = false end
}
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct
+ dummy_data: T,
end
-type Idx(@Selff, @Output) = struct
- idx: fn(@Selff, I32) @Output,
+type Idx(Selff, Output) = struct
+ idx: fn(Selff, I32) Output,
end
-type Len(@Selff) = struct
- len: fn(@Selff) I32,
+type Len(Selff) = struct
+ len: fn(Selff) I32,
end
-const ListLen Len(List(@T)) = Len {
- .len = fn(selff List(@T)) I32 = 0 end
+const ListLen Len(List(T)) = Len {
+ .len = fn(selff List(T)) I32 = 0 end
}
-fn module_len(impll Len(@T), l @T) I32 =
+fn module_len(impll Len(T), l T) I32 =
let total I32 = 0
impll$.len(l)
end
-- Specialize it just to make sure everything fits together...
-fn list_len(l List(@T)) I32 =
+fn list_len(l List(T)) I32 =
module_len(ListLen, l)
end
-const ListIdx Idx(List(@T), @T) = Idx {
- .idx = fn(selff List(@T), i I32) @T = selff$.dummy_data end
+const ListIdx Idx(List(T), T) = Idx {
+ .idx = fn(selff List(T), i I32) T = selff$.dummy_data end
}
-- Generalized thingy...
-fn idx(l List(@T)) @T =
+fn idx(l List(T)) T =
let total I32 = 3
ListIdx$.idx(l, total)
end
M tests/programs/test_module4.gt +4 -4
@@ 3,12 3,12 @@
-- Compile:
-- status: error
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
+type Idx(Self, Output) = struct
+ idx: fn(Self, I32) Output,
end
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct
+ dummy_data: T,
end
fn foo(self List(I32), i I32) Bar =
M tests/programs/test_module6.gt +23 -13
@@ 1,38 1,48 @@
-- Format:
-- status: success
-- Compile:
--- status: success
---
--- Run:
--- stdout: 3
+-- status: error
+
+-- TODO: This should succeed but does not yet.
+-- We need some way to make closures over type parameters;
+-- either directly or by monomorphization.
+-- I tried doing it directly a la lambda lifting and it
+-- really didn't work too heckin' well, so we'll try monomorph.
-- A generic functional map from a key to a value
-type Map(@T, @K, @V) = struct(@T, @K, @V)
- get: fn(@T, @K) @V,
- set: fn(@T, @K, @V) @T,
+type Map(T, K, V) = struct(T, K, V)
+ get: fn(T, K) V,
+ --set: fn(T, K, V) T,
end
-- Implement Map for a cell type
-type Cell(@K, @V) = struct(@K, @V)
- key: @K,
- val: @V
+type Cell(K, V) = struct(K, V)
+ key: K,
+ val: V
end
-fn make_cell_map(|@K, @V| k @K, v @V) Map(Cell(@K, @V), @K, @V) =
+fn make_cell_map(|K, V| k K, v V) Map(Cell(K, V), K, V) =
+/-
let module = Map {
-- I guess we pretend this always succeeds,
-- since I don't really feel like implementing if's
- .get = fn(t Cell(@K, @V), key @K) @V =
+ .get = fn(t Cell(K, V), key K) V =
t$.val
end,
-- Just create a new cell with the given key and val
- .set = fn(t Cell(@K, @V), key @K, val @V) Cell(@K, @V) =
+ .set = fn(t Cell(K, V), key K, val V) Cell(K, V) =
Cell {
.key = key,
.val = val,
}
end
}
+-/
+ let module = Map {
+ .get = fn(t Cell(K, V), key K) V =
+ t$.val
+ end,
+ }
module
end
M tests/programs/test_module7.gt +6 -6
@@ 18,19 18,19 @@ module type Functor = sig
end
-/
-type Functor(@T1, @T2) = struct
- map: fn(@T1) @T2,
+type Functor(T1, T2) = struct
+ map: fn(T1) T2,
end
-- Implement Map for a cell type
-type Cell(@V) = struct
- val: @V
+type Cell(V) = struct
+ val: V
end
-fn make_cell_functor(f fn(@A) @B) Functor(Cell(@A), Cell(@B)) =
+fn make_cell_functor(f fn(A) B) Functor(Cell(A), Cell(B)) =
-- to work this needs closures, 'cause it
-- captures f()
- let m = fn(c Cell(@A)) Cell(@B) =
+ let m = fn(c Cell(A)) Cell(B) =
Cell({
.val = f(c$.val)
})
M tests/programs/test_module_specialization1.gt +5 -6
@@ 8,13 8,13 @@
-- 4
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct(T)
+ dummy_data: T,
end
-type Idx(@Selff, @Output) = struct
- idx: fn(@Selff, I32) @Output,
+type Idx(Selff, Output) = struct(Selff, Output)
+ idx: fn(Selff, I32) Output,
end
-- Can we make an instance for a specialization of List(T)?
@@ 25,6 25,5 @@ const IntListIdx Idx(List(I32), I32) = I
fn main() {} =
let x = List { .dummy_data = 4_I32 }
let y = IntListIdx$.idx(x, 3)
- __println(y); -- gfd parser ambiguity
- {}
+ __println(y)
end
M tests/programs/test_monad.gt +4 -4
@@ 1,5 1,5 @@
-- Format:
--- status: error
+-- status: success
-- Compile:
-- status: error
@@ 43,8 43,8 @@ end
-- The bad news is, we don't seem to be able to implement real monads
-- The good news is, we don't seem to be able to implement real monads
-- I think if we had associated types?
-type Monad(@A, @M) = struct
- return_: fn(@A) @M(@A),
- bind: fn(@M(@A), fn(@A) @M(@B)) @M(@B)
+type Monad(A, M, B) = struct(A, M, B)
+ return_: fn(A) M(A),
+ bind: fn(M(A), fn(A) M(B)) M(B)
end
M tests/programs/test_monomorphization1.gt +1 -1
@@ 11,7 11,7 @@
-- Test case for monomorphization of functions.
-fn identity(i @T) @T =
+fn identity(|T| i T) T =
let y = i
y
end
M tests/programs/test_monomorphization2.gt +2 -2
@@ 9,9 9,9 @@
-- 2
-fn identity(i @T) @T =
+fn identity(|T| i T) T =
-- Does it work properly when we have a declared type for our var?
- let y @T = i
+ let y T = i
y
end
M tests/programs/test_monomorphization3.gt +2 -2
@@ 18,11 18,11 @@ fn main() {} =
__println_bool(y.1)
end
-fn thing2(x @A) {I32, @A} =
+fn thing2(|A| x A) {I32, A} =
thing1(3, x)
end
-fn thing1(i I32, x @A) {I32, @A} =
+fn thing1(|A| i I32, x A) {I32, A} =
{i, x}
end
A => tests/programs/test_pcg.gt +74 -0
@@ 0,0 1,74 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: success
+-- Run:
+-- status: success
+-- stdout:
+-- -1403893721
+-- 222523091
+-- -1045347539
+-- -1893201942
+-- 1311075464
+-- 718749721
+-- -376464712
+-- -249673620
+-- -2027436315
+-- 263441840
+
+
+
+
+
+--- A test implementation of the PCG PRNG from https://sr.ht/~icefox/oorandom
+--- Fails to give perfect results after the first 3 numbers due to signed/unsigned
+--- arithmetic nonsense, but it's a start.
+type Rand32 = struct
+ state: U64,
+ inc: U64,
+end
+
+const RAND32_DEFAULT_INC U64 = 1442695040888963407
+const RAND32_MULTIPLIER U64 = 6364136223846793005
+
+fn rand32_new_inc(seed U64, inc U64) Rand32 =
+ let rng = Rand32({
+ .state = 0,
+ .inc = __bor_u64(__lshift_u64(inc, 1), 1),
+ })
+ let mut rng2 = rand32_i32(rng).0
+ rng2$.state = rng2$.state + seed
+ let rng3 = rand32_i32(rng2).0
+ rng3
+end
+
+fn rand32_new(seed U64) Rand32 =
+ rand32_new_inc(seed, RAND32_DEFAULT_INC)
+end
+
+fn rand32_i32(rand Rand32) {Rand32, I32} =
+ let oldstate = rand$.state
+ let mut newrng = rand
+ newrng$.state = oldstate * RAND32_MULTIPLIER + rand$.inc
+ -- ok maybe binary op operators are an ok idea
+ let xorshifted = __u64_to_u32( __rshift_u64( __bxor_u64( __rshift_u64(oldstate, 18), oldstate), 27))
+ let rot = __u64_to_i32(__rshift_u64(oldstate, 59))
+ {newrng, __u32_to_i32(__ror_u32(xorshifted, rot))}
+ --{newrng, 3}
+end
+
+
+
+
+fn main() {} =
+ let mut rng = rand32_new(54321);
+ let mut i I32 = 0
+ while i < 10 do
+ i = i + 1
+
+ let res = rand32_i32(rng)
+ rng = res.0
+ let out I32 = res.1
+ __println(out)
+ end
+end
A => tests/programs/test_recursive_type.gt +16 -0
@@ 0,0 1,16 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+
+--- TODO: Detect this and do something sensible about it.
+type Foo = struct
+ x: I32,
+ y: I32,
+ z: Foo,
+end
+
+fn main() {} =
+ {}
+end
+
M tests/programs/test_result1.gt +3 -3
@@ 3,9 3,9 @@
-- Compile:
-- status: error
-type Result(@T, @E) = sum
- Ok @T,
- Err @E,
+type Result(T, E) = sum
+ Ok T,
+ Err E,
end
-- TODO: This should probably succeed
M tests/programs/test_struct1.gt +3 -3
@@ 13,16 13,16 @@ type Foo = struct
b: Bool
end
-fn id(x @T) @T =
+fn id(|T| x T) T =
x
end
fn main() {} =
- let f1 Foo = id(Foo {
+ let f1 Foo = id(|Foo| Foo {
.a = 3,
.b = false
})
- let f2 struct a: I32, b: Bool end = id({ .a = 3, .b = false })
+ let f2 struct a: I32, b: Bool end = id { .a = 3, .b = false }
__println(f1$.a)
end
M tests/programs/test_struct4.gt +6 -3
@@ 4,17 4,19 @@
-- status: success
-type Foo(@Thing) = struct
- a: @Thing,
+type Foo(Thing) = struct(Thing)
+ a: Thing,
b: Bool
end
-fn id(x @T) @T =
+fn id(|T| x T) T =
x
end
fn main() {} =
+ {}
+/-
let f1 Foo(I32) = id(Foo {
.a = 3,
.b = false
@@ 24,4 26,5 @@ fn main() {} =
.a = true,
.b = false
})
+ -/
end
M tests/programs/test_struct5.gt +3 -3
@@ 3,12 3,12 @@
-- Compile:
-- status: error
-type Foo(@Thing) = struct
- a: @Blarg,
+type Foo(Thing) = struct
+ a: Blarg,
b: Bool
end
-fn id(x @T) @T =
+fn id(x T) T =
x
end
M tests/programs/test_struct6.gt +2 -2
@@ 3,8 3,8 @@
-- Compile:
-- status: error
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct
+ dummy_data: T,
end
fn foo(self List(I32), i I32) Bar =
M tests/programs/test_sum2.gt +5 -5
@@ 12,10 12,10 @@ TFW your typechecker catches a real bug.
Especially one you don't know how to fix.
We lower the constructors for this type into functions,
and then it complains that the function Option.None()
-returns Option(@T) and it has no way of knowing what
-the @T is for that function.
+returns Option(T) and it has no way of knowing what
+the T is for that function.
-This should technically be fine because @T is
+This should technically be fine because T is
instantiated and figured out wherever Option.None()
is actually called, but I don't know how to tell the
typechecker that.
@@ 30,8 30,8 @@ Added explicit type args to sum types an
but leaving this note here for now just in case
it pops up again later.
-/
-type Option(@T) = sum(@T)
- Some @T,
+type Option(T) = sum(T)
+ Some T,
None {},
end
M tests/programs/test_tuple2.gt +5 -3
@@ 4,11 4,13 @@
-- status: success
-fn identity(i @T) @T =
- let y @T = i
+fn identity(|T| i T) T =
+ let y T = i
y
end
fn main() {} =
- let a {I32, Bool, I32} = identity{1, false, 3}
+ let a {I32, Bool, I32} = identity(|{I32, Bool, I32}| {1, false, 3})
+ let b {I32, Bool, I32} = identity({1, false, 3})
+ let c {I32, Bool, I32} = identity {1, false, 3}
end
M tests/programs/test_tuple3.gt +2 -2
@@ 4,8 4,8 @@
-- status: error
-fn identity(i @T) @T =
- let y @T = i
+fn identity(i T) T =
+ let y T = i
y
end
M tests/programs/test_tuple4.gt +2 -2
@@ 4,10 4,10 @@
-- status: success
-fn thing(i {@T1, @T2}) {@T1, @T2} =
+fn thing(|T1, T2| i {T1, T2}) {T1, T2} =
i
end
fn main() {} =
- let a {I32, Bool} = thing{1, false}
+ let a {I32, Bool} = thing {1, false}
end
M tests/programs/test_typedef3.gt +2 -2
@@ 4,9 4,9 @@
-- status: error
-type Foo = {I32, @A}
+type Foo = {I32, A}
-fn thing(i I32, x @A) Foo =
+fn thing(i I32, x A) Foo =
Foo{i, x}
end
M tests/programs/test_typedef4.gt +7 -7
@@ 4,18 4,18 @@
-- status: success
-type Foo(@A) = {I32, @A}
+type Foo(A) = {I32, A}
-fn thing(i I32, x @A) Foo(@A) =
- Foo{i, x}
+fn thing(|A| i I32, x A) Foo(A) =
+ Foo(|A| {i, x})
end
-fn thing2(i I32, x @A) {I32, @A} =
+fn thing2(|A| i I32, x A) {I32, A} =
{i, x}
end
fn main() {} =
- let f1 Foo(I32) = thing(4, 5)
- let f2 Foo(Bool) = thing(4, true)
- let f2 {I32, Bool} = thing2(4, true)
+ let f1 Foo(I32) = thing(|I32| 4, 5)
+ let f2 Foo(Bool) = thing(|Bool| 4, true)
+ let f2 {I32, Bool} = thing2(|Bool| 4, true)
end
M tests/programs/test_typedef6.gt +2 -2
@@ 3,10 3,10 @@
-- Compile:
-- status: success
-type Foo(@T) = {I32, @T}
+type Foo(T) = {I32, T}
fn thing(i I32, x Bool) Foo(Bool) =
- Foo{i, x}
+ Foo(|Bool| {i, x})
end
fn main() {} =
M tests/programs/test_typedef7.gt +1 -1
@@ 3,7 3,7 @@
-- Compile:
-- status: error
-type Foo(@T) = {I32, @T}
+type Foo(T) = {I32, T}
fn thing(i I32, x Bool) Foo(Bool) =
Foo{i, x}
M tests/programs/test_typedef8.gt +2 -2
@@ 3,9 3,9 @@
-- Compile:
-- status: error
-type Foo(@T) = {I32, @T}
+type Foo(T) = {I32, T}
-fn thing2(i I32, x @Whatever) Foo(@Whatever) =
+fn thing2(i I32, x Whatever) Foo(Whatever) =
Foo{i, x}
end
A => tests/programs/test_unknownint_fails.gt +15 -0
@@ 0,0 1,15 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+-- stderr:
+-- ...
+-- ...Unknown int in expr Lit...
+-- ...
+
+fn main() {} =
+ let x = 3
+ let y = 4
+ let z = x + y
+end
+
M tests/programs/test_unnamed_failure1.gt +2 -2
@@ 3,8 3,8 @@
-- Compile:
-- status: error
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct
+ dummy_data: T,
end
fn should_fail() Bar =
M tests/programs/test_unnamed_failure2.gt +5 -6
@@ 4,20 4,19 @@
-- status: error
-- stderr:
-- ...
--- ...Conflict between Bool and Foo()...
+-- ...Could not find declared type Foo...
-- ...
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
+type Idx(Self, Output) = struct(Self, Output)
+ idx: fn(Self, I32) Output,
end
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct(T)
+ dummy_data: T,
end
-- This correctly fails
--- TODO: verify why and update stderr to make sure it fails *right*
const BadIntListIdx Idx(I32) = Idx {
.idx = fn(self List(I32), i I32) Foo = true end
}
M tests/programs/test_unnamed_failure3.gt +5 -6
@@ 4,20 4,19 @@
-- status: error
-- stderr:
-- ...
--- ...Conflict between I32 and Foo()...
+-- ...Could not find declared type Foo...
-- ...
-type Idx(@Self, @Output) = struct
- idx: fn(@Self, I32) @Output,
+type Idx(Self, Output) = struct(Self, Output)
+ idx: fn(Self, I32) Output,
end
-type List(@T) = struct
- dummy_data: @T,
+type List(T) = struct(T)
+ dummy_data: T,
end
-- This correctly fails
--- TODO: verify why and update stderr to make sure it fails *right*
const BadIntListIdx2 Idx(I32) = Idx {
.idx = fn(self List(I32), i I32) Foo = self$.dummy_data end
}
M tests/programs/test_unnamed_generic.gt +2 -2
@@ 4,8 4,8 @@
-- status: error
-fn identity(i @T) @T =
- let y @Heck = i
+fn identity(i T) T =
+ let y Heck = i
y
end
A => tests/programs/test_unsigned1.gt +18 -0
@@ 0,0 1,18 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: success
+-- Run:
+-- status: success
+-- stdout:
+-- 15
+
+
+fn foo(x U64) U64 =
+ x + 3
+end
+
+fn main() {} =
+ let x U64 = 12
+ __println_u64(foo(x))
+end
A => tests/programs/test_unsigned2.gt +18 -0
@@ 0,0 1,18 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: error
+-- stderr:
+-- ...
+-- ...Conflict between I64 and U64...
+-- ...
+
+
+fn foo(x I64) U64 =
+ x + 3
+end
+
+fn main() {} =
+ let x U64 = 12
+ __println_u64(foo(x))
+end
A => tests/programs/test_while1.gt +27 -0
@@ 0,0 1,27 @@
+-- Format:
+-- status: success
+-- Compile:
+-- status: success
+--
+-- Run:
+-- status: success
+-- stdout: 21
+
+fn main() {} =
+ __println(fib(8))
+end
+
+-- Iterative fib implementation, with a while loop
+fn fib(num I32) I32 =
+ let mut x I32 = 0
+ let mut y I32 = 1
+ let mut z I32 = 0
+ let mut i I32 = 0
+ while i < num do
+ z = x + y
+ x = y
+ y = z
+ i = i + 1
+ end
+ x
+end