1
1
Fork 0
mirror of https://github.com/azur1s/bobbylisp.git synced 2024-10-16 02:37:40 -05:00

Copy over

i dont know how to merge
This commit is contained in:
azur 2023-06-29 20:07:52 +07:00
parent 6624983ae3
commit 3a837e30be
34 changed files with 2618 additions and 1299 deletions

9
.gitignore vendored
View file

@ -1,10 +1 @@
/target
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by the compiler
/*.js

729
Cargo.lock generated
View file

@ -4,33 +4,103 @@ version = 3
[[package]]
name = "ahash"
version = "0.7.6"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"getrandom",
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
name = "anstream"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
dependencies = [
"winapi",
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "atty"
version = "0.2.14"
name = "anstyle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
dependencies = [
"hermit-abi",
"libc",
"winapi",
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "arbitrary"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
[[package]]
name = "ariadne"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702"
dependencies = [
"unicode-width",
"yansi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bin"
version = "0.1.0"
dependencies = [
"ariadne",
"chumsky",
"clap",
"com",
"ir",
"syntax",
"typing",
]
[[package]]
@ -39,6 +109,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "cc"
version = "1.0.79"
@ -53,38 +129,241 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.9.2"
version = "1.0.0-alpha.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
checksum = "379cdc19530b72a1e76d94a350676eaea1455375533eb38f18dfa712f9996902"
dependencies = [
"hashbrown",
"hashbrown 0.13.2",
"stacker",
]
[[package]]
name = "clap"
version = "2.34.0"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "getrandom"
version = "0.2.8"
name = "clap_builder"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
dependencies = [
"cfg-if",
"anstream",
"anstyle",
"bitflags",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "com"
version = "0.1.0"
dependencies = [
"chumsky",
"cranelift",
"cranelift-jit",
"cranelift-module",
"cranelift-native",
"syntax",
"typing",
]
[[package]]
name = "cranelift"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128089b15e74e782e4e1a51c4b5b3bd4fbc35755777988aff24300dfa120f1c9"
dependencies = [
"cranelift-codegen",
"cranelift-frontend",
]
[[package]]
name = "cranelift-bforest"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c064a534a914eb6709d198525321a386dad50627aecfaf64053f369993a3e5a"
dependencies = [
"cranelift-entity",
]
[[package]]
name = "cranelift-codegen"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619ed4d24acef0bd58b16a1be39077c0b36c65782e6c933892439af5e799110e"
dependencies = [
"bumpalo",
"cranelift-bforest",
"cranelift-codegen-meta",
"cranelift-codegen-shared",
"cranelift-control",
"cranelift-entity",
"cranelift-isle",
"gimli",
"hashbrown 0.13.2",
"log",
"regalloc2",
"smallvec",
"target-lexicon",
]
[[package]]
name = "cranelift-codegen-meta"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c777ce22678ae1869f990b2f31e0cd7ca109049213bfc0baf3e2205a18b21ebb"
dependencies = [
"cranelift-codegen-shared",
]
[[package]]
name = "cranelift-codegen-shared"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb65884d17a1fa55990dd851c43c140afb4c06c3312cf42cfa1222c3b23f9561"
[[package]]
name = "cranelift-control"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a0cea8abc90934d0a7ee189a29fd35fecd5c40f59ae7e6aab1805e8ab1a535e"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e50bebc05f2401a1320169314b62f91ad811ef20163cac00151d78e0684d4c"
[[package]]
name = "cranelift-frontend"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b82ccfe704d53f669791399d417928410785132d809ec46f5e2ce069e9d17c8"
dependencies = [
"cranelift-codegen",
"log",
"smallvec",
"target-lexicon",
]
[[package]]
name = "cranelift-isle"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2515d8e7836f9198b160b2c80aaa1f586d7749d57d6065af86223fb65b7e2c3"
[[package]]
name = "cranelift-jit"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3830a0c5c18c5ddf1c6d4a3dc049a3723d4606e24699796116963ac5bb0404"
dependencies = [
"anyhow",
"cranelift-codegen",
"cranelift-control",
"cranelift-entity",
"cranelift-module",
"cranelift-native",
"libc",
"wasi",
"log",
"region",
"target-lexicon",
"wasmtime-jit-icache-coherence",
"windows-sys",
]
[[package]]
name = "cranelift-module"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2709ba70d7340ef9ffc408df1905bfd02f33eddeb6f8a7198d93c1d0c1154c3"
dependencies = [
"anyhow",
"cranelift-codegen",
"cranelift-control",
]
[[package]]
name = "cranelift-native"
version = "0.96.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcb47ffdcdac7e9fed6e4a618939773a4dc4a412fa7da9e701ae667431a10af3"
dependencies = [
"cranelift-codegen",
"libc",
"target-lexicon",
]
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "gimli"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
dependencies = [
"fallible-iterator",
"indexmap",
"stable_deref_trait",
]
[[package]]
@ -92,47 +371,126 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "heck"
version = "0.3.3"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"libc",
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "holymer"
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "ir"
version = "0.1.0"
dependencies = [
"chumsky",
"structopt",
"serde",
"serde-lexpr",
"serde_derive",
"syntax",
"typing",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
name = "is-terminal"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "lexpr"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a84de6a9df442363b08f5dbf0cd5b92edc70097b89c4ce4bfea4679fe48bc67"
dependencies = [
"itoa",
"lexpr-macros",
"ryu",
]
[[package]]
name = "lexpr-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36b5cb8bb985c81a8ac1a0f8b5c4865214f574ddd64397ef7a99c236e21f35bb"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "libc"
version = "0.2.139"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "linux-raw-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
@ -140,35 +498,11 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
@ -184,13 +518,109 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regalloc2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a52e724646c6c0800fc456ec43b4165d2f91fba88ceaca06d9e0b400023478"
dependencies = [
"hashbrown 0.13.2",
"log",
"rustc-hash",
"slice-group-by",
"smallvec",
]
[[package]]
name = "region"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0"
dependencies = [
"bitflags",
"libc",
"mach",
"winapi",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "serde"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
[[package]]
name = "serde-lexpr"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb4cda13396159f59e7946118cdac0beadeecfb7cf76b197f4147e546f4ead6f"
dependencies = [
"lexpr",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slice-group-by"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "stacker"
version = "0.1.15"
@ -206,39 +636,15 @@ dependencies = [
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
@ -246,25 +652,31 @@ dependencies = [
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
name = "syntax"
version = "0.1.0"
dependencies = [
"unicode-width",
"chumsky",
]
[[package]]
name = "target-lexicon"
version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
[[package]]
name = "typing"
version = "0.1.0"
dependencies = [
"chumsky",
"syntax",
]
[[package]]
name = "unicode-ident"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-width"
@ -273,10 +685,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "vec_map"
version = "0.8.2"
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
@ -285,10 +697,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
name = "wasmtime-jit-icache-coherence"
version = "9.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "374ff63b3eb41db57c56682a9ef7737d2c9efa801f5dbf9da93941c9dd436a06"
dependencies = [
"cfg-if",
"libc",
"windows-sys",
]
[[package]]
name = "winapi"
@ -311,3 +728,75 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"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.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[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.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[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.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[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.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View file

@ -1,12 +1,9 @@
[package]
name = "holymer"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
chumsky = "0.9.0"
structopt = "0.3.26"
[[bin]]
name = "hlmc"
path = "src/main.rs"
members = [
"bin",
"syntax",
"typing",
"ir",
"com",
]

1
a.hlm
View file

@ -1 +0,0 @@
print("Hello\n");

17
bin/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "bin"
version = "0.1.0"
edition = "2021"
[dependencies]
ariadne = "0.2.0"
chumsky = "1.0.0-alpha.3"
clap = { version = "4.2.4", features = ["derive"] }
syntax = { path = "../syntax" }
typing = { path = "../typing" }
ir = { path = "../ir" }
com = { path = "../com" }
[[bin]]
name = "hc"
path = "src/main.rs"

15
bin/src/args.rs Normal file
View file

@ -0,0 +1,15 @@
use clap::Parser;
#[derive(Debug, Parser)]
pub struct Args {
/// The path to the file to be compiled.
#[arg(required = true)]
pub file: String,
/// Only run the type checker.
#[arg(short = 'c', long = "check")]
pub typecheck: bool,
}
pub fn get_args() -> Args {
Args::parse()
}

87
bin/src/main.rs Normal file
View file

@ -0,0 +1,87 @@
use ariadne::{sources, Color, Label, Report, ReportKind};
use chumsky::{Parser, prelude::Input};
use ir::lower_expr;
use syntax::parser::{lexer, exprs_parser};
use typing::infer::{infer_exprs, InferErrorKind};
pub mod args;
fn main() {
let args = args::get_args();
let filename = args.file.clone();
let src = std::fs::read_to_string(&args.file).expect("file not found");
// Lexing & parsing
let (ts, errs) = lexer().parse(&src).into_output_errors();
let (ast, parse_errs) = if let Some(tokens) = &ts {
let (ast, parse_errs) = exprs_parser()
.map_with_span(|ast, span| (ast, span))
.parse(tokens.as_slice().spanned((src.len()..src.len()).into()))
.into_output_errors();
(ast, parse_errs)
} else {
(None, vec![])
};
// Typecheck if there are no lexing or parsing errors
if let Some(ast) = ast.filter(|_| errs.len() + parse_errs.len() == 0) {
let (ast, e) = infer_exprs(ast.0);
// If there is an error, print it
if !e.is_empty() {
e.into_iter()
.for_each(|e| {
let mut r = Report::build(ReportKind::Error, filename.clone(), e.span.start)
.with_message(e.title.to_string());
for (msg, kind, span) in e.labels {
r = r.with_label(
Label::new((filename.clone(), span.into_range()))
.with_message(msg.to_string())
.with_color(match kind {
InferErrorKind::Error => Color::Red,
InferErrorKind::Hint => Color::Blue,
}),
);
}
r
.finish()
.print(sources([(filename.clone(), src.clone())]))
.unwrap()
});
// Else go to the next stage
} else {
if args.typecheck {
ast.iter().for_each(|node| println!("{:?}", node.0));
return;
}
// ast.iter().for_each(|node| println!("{:?}", node.0));
let irs = ast.into_iter().map(|node| lower_expr(node.0)).collect::<Vec<_>>();
irs.iter().for_each(|ir| println!("{}", ir));
}
};
// Report lex & parse errors
errs.into_iter()
.map(|e| e.map_token(|c| c.to_string()))
.chain(
parse_errs
.into_iter()
.map(|e| e.map_token(|tok| tok.to_string())),
)
.for_each(|e| {
Report::build(ReportKind::Error, filename.clone(), e.span().start)
.with_message(e.to_string())
.with_label(
Label::new((filename.clone(), e.span().into_range()))
.with_message(e.reason().to_string())
.with_color(Color::Red),
)
.finish()
.print(sources([(filename.clone(), src.clone())]))
.unwrap()
});
}

13
com/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "com"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = "1.0.0-alpha.3"
cranelift = "0.96.3"
cranelift-jit = "0.96.3"
cranelift-module = "0.96.3"
cranelift-native = "0.96.3"
syntax = { path = "../syntax" }
typing = { path = "../typing" }

0
com/src/lib.rs Normal file
View file

View file

@ -1,8 +0,0 @@
func fact (n : num) num =
if n == 0 then
1
else
n * fact(n - 1)
;
println(fact(5));

7
example/simple.hlm Normal file
View file

@ -0,0 +1,7 @@
let add = fun (x Int, y Int) Int -> x + y;
let succ = fun (x) -> x + 1;
let mul = fun (x, y) -> x * y;
add(33, 34)
|> fun (x) -> succ(x)
|> fun (x) -> mul(x, 10)

12
ir/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "ir"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = "1.0.0-alpha.3"
serde = "1.0.164"
serde-lexpr = "0.1.3"
serde_derive = "1.0.164"
syntax = { path = "../syntax" }
typing = { path = "../typing" }

163
ir/src/lib.rs Normal file
View file

@ -0,0 +1,163 @@
use typing::typed::TExpr;
use syntax::expr::{Lit as ExprLit, UnaryOp, BinaryOp};
use std::fmt::{self, Display, Formatter, Result as FmtResult};
#[derive(Clone, Debug)]
pub enum Lit<'src> {
Unit,
Bool(bool),
Int(i64),
Str(&'src str),
}
impl Display for Lit<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Lit::Unit => write!(f, "()"),
Lit::Bool(b) => write!(f, "{}", b),
Lit::Int(i) => write!(f, "{}", i),
Lit::Str(s) => write!(f, "\"{}\"", s),
}
}
}
#[derive(Clone, Debug)]
pub enum Expr<'src> {
Lit(Lit<'src>),
// v0
Var(&'src str),
// f(v0, v1, ...)
Call(Vec<Self>),
}
impl Display for Expr<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Expr::Lit(l) => write!(f, "{}", l),
Expr::Var(s) => write!(f, "{}", s),
Expr::Call(v) => {
write!(f, "(")?;
for (i, e) in v.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{}", e)?;
}
write!(f, ")")
}
}
}
}
macro_rules! unbox {
($e:expr) => {
*(($e).0)
};
}
macro_rules! str {
($e:expr) => {
Expr::Lit(Lit::Str($e))
};
}
macro_rules! var {
($e:expr) => {
Expr::Var($e)
};
}
macro_rules! call {
($e:expr) => {
Expr::Call($e)
};
}
pub fn lower_lit(lit: ExprLit) -> Lit {
match lit {
ExprLit::Unit => Lit::Unit,
ExprLit::Bool(b) => Lit::Bool(b),
ExprLit::Int(i) => Lit::Int(i),
ExprLit::Str(s) => Lit::Str(s),
}
}
pub fn lower_expr(e: TExpr) -> Expr {
match e {
TExpr::Lit(l) => Expr::Lit(lower_lit(l)),
TExpr::Ident(s) => var!(s),
TExpr::Unary { op, expr, .. } => {
let expr = lower_expr(unbox!(expr));
match op {
UnaryOp::Neg => call!(vec![var!("neg"), expr]),
UnaryOp::Not => call!(vec![var!("not"), expr]),
}
}
TExpr::Binary { op: BinaryOp::Pipe, lhs, rhs, .. } => {
let lhs = lower_expr(unbox!(lhs)); // arguments
let rhs = lower_expr(unbox!(rhs)); // function
call!(vec![rhs, lhs])
}
TExpr::Binary { op, lhs, rhs, .. } => {
let lhs = lower_expr(unbox!(lhs));
let rhs = lower_expr(unbox!(rhs));
match op {
BinaryOp::Add => call!(vec![var!("+"), lhs, rhs]),
BinaryOp::Sub => call!(vec![var!("-"), lhs, rhs]),
BinaryOp::Mul => call!(vec![var!("*"), lhs, rhs]),
BinaryOp::Div => call!(vec![var!("/"), lhs, rhs]),
BinaryOp::Rem => call!(vec![var!("%"), lhs, rhs]),
BinaryOp::Eq => call!(vec![var!("=="), lhs, rhs]),
BinaryOp::Ne => call!(vec![var!("!="), lhs, rhs]),
BinaryOp::Lt => call!(vec![var!("<"), lhs, rhs]),
BinaryOp::Le => call!(vec![var!("<="), lhs, rhs]),
BinaryOp::Gt => call!(vec![var!(">"), lhs, rhs]),
BinaryOp::Ge => call!(vec![var!(">="), lhs, rhs]),
BinaryOp::And => call!(vec![var!("&&"), lhs, rhs]),
BinaryOp::Or => call!(vec![var!("||"), lhs, rhs]),
BinaryOp::Pipe => unreachable!("pipe operator is handled separately"),
}
}
TExpr::Lambda { params, body, .. } => {
let body = lower_expr(unbox!(body));
call!(vec![
var!("lambda"),
call!(params.into_iter().map(|(p, _)| var!(p)).collect()),
body,
])
}
TExpr::Call { func, args } => {
let func = lower_expr(unbox!(func));
let args = args.into_iter()
.map(|(a, _)| lower_expr(a))
.collect::<Vec<_>>();
call!(vec![func].into_iter().chain(args).collect())
}
TExpr::If { cond, t, f, .. } => {
let cond = lower_expr(unbox!(cond));
let t = lower_expr(unbox!(t));
let f = lower_expr(unbox!(f));
call!(vec![var!("if"), cond, t, f])
}
TExpr::Let { name, value, body, .. } => {
let value = lower_expr(unbox!(value));
let body = lower_expr(unbox!(body));
call!(vec![var!("let"), str!(name), value, body])
}
TExpr::Define { name, value, .. } => {
let value = lower_expr(unbox!(value));
call!(vec![var!("define"), var!(name), value])
}
TExpr::Block { exprs, void, .. } => {
let exprs = exprs.into_iter()
.map(|(e, _)| lower_expr(e))
.collect::<Vec<_>>();
if void {
call!(vec![var!("block"), call!(exprs)])
} else {
call!(vec![var!("block"), call!(exprs), var!("()")])
}
}
}
}

View file

@ -1,11 +0,0 @@
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct Options {
#[structopt(name = "FILE", parse(from_os_str))]
pub file: PathBuf,
#[structopt(short, long = "out", parse(from_os_str))]
pub output: Option<PathBuf>,
}

View file

@ -1,129 +0,0 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::trans::ty::Type;
#[derive(Clone, Debug)]
pub enum UnaryOp {
Neg,
Not,
}
#[derive(Clone, Debug)]
pub enum BinaryOp {
Add, Sub, Mul, Div, Mod,
Eq, Neq, Lt, Gt, Lte, Gte,
And, Or,
}
#[derive(Clone, Debug)]
pub enum Literal {
Num(i64), Str(String), Bool(bool), Unit,
}
/// Enum to represent internal expression
#[derive(Clone, Debug)]
pub enum Expr {
Lit(Literal),
Sym(String),
Vec(Vec<Self>),
UnaryOp(UnaryOp, Box<Self>),
BinaryOp(BinaryOp, Box<Self>, Box<Self>),
Call(Box<Self>, Vec<Self>),
Lambda {
args: Vec<String>,
body: Vec<Self>,
},
If {
cond: Box<Self>,
t: Box<Self>,
f: Box<Self>,
},
Defines(Vec<(String, Self)>),
Return(Box<Self>),
}
#[derive(Clone, Debug)]
pub enum Stmt {
Expr(Expr),
Func {
name: String,
args: Vec<String>,
ret: Type,
body: Expr,
},
}
impl Display for Expr {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Expr::Lit(l) => match l {
Literal::Num(n) => write!(f, "{}", n),
Literal::Str(s) => write!(f, "\"{}\"", s),
Literal::Bool(b) => write!(f, "{}", b),
Literal::Unit => write!(f, "()"),
},
Expr::Sym(s) => write!(f, "{}", s),
Expr::Vec(v) => {
write!(f, "[")?;
for (i, e) in v.iter().enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{}", e)?;
}
write!(f, "]")
},
Expr::UnaryOp(op, e) => write!(f, "({} {})", format!("{:?}", op).to_lowercase(), e),
Expr::BinaryOp(op, e1, e2) => write!(f, "({} {} {})", format!("{:?}", op).to_lowercase(), e1, e2),
Expr::Call(c, args) => {
write!(f, "({}", c)?;
for arg in args {
write!(f, " {}", arg)?;
}
write!(f, ")")
},
Expr::Lambda { args, body } => {
write!(f, "(lambda ")?;
for arg in args {
write!(f, " {}", arg)?;
}
if body.len() == 1 {
write!(f, " {})", body[0])
} else {
write!(f, " (do")?;
for e in body {
write!(f, " {}", e)?;
}
write!(f, "))")
}
},
Expr::If { cond, t, f: fe } => {
write!(f, "(if {} {} {})", cond, t, fe)
},
Expr::Defines(defs) => {
write!(f, "(defs ")?;
for (name, expr) in defs {
write!(f, "({} {})", name, expr)?;
}
write!(f, ")")
},
Expr::Return(e) => write!(f, "(return {})", e),
}
}
}
impl Display for Stmt {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Stmt::Expr(e) => write!(f, "{}", e),
Stmt::Func { name, args, ret, body } => {
write!(f, "(defn {} [", name)?;
for name in args {
write!(f, " {}", name)?;
}
write!(f, "] {} {})", ret, body)
},
}
}
}

View file

@ -1,140 +0,0 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::trans::ty::Type;
#[derive(Clone, Debug)]
pub enum JSLiteral { Num(i64), Str(String), Bool(bool), Undefined }
/// Enum to represent javascript expression
#[derive(Clone, Debug)]
pub enum JSExpr {
Lit(JSLiteral),
Sym(String),
Array(Vec<Self>),
Op(&'static str, Box<Self>, Option<Box<Self>>),
Call(Box<Self>, Vec<Self>),
Method(Box<Self>, String),
Lambda {
args: Vec<String>,
body: Vec<Self>,
},
If {
cond: Box<Self>,
t: Box<Self>,
f: Box<Self>,
},
Defines(Vec<(String, Self)>),
Return(Box<Self>),
}
#[derive(Clone, Debug)]
pub enum JSStmt {
Expr(JSExpr),
Func {
name: String,
args: Vec<String>,
ret: Type,
body: JSExpr,
},
}
impl Display for JSExpr {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
JSExpr::Lit(l) => match l {
JSLiteral::Num(n) => write!(f, "{}", n),
JSLiteral::Str(s) => write!(f, "'{}'", s),
JSLiteral::Bool(b) => write!(f, "{}", b),
JSLiteral::Undefined => write!(f, "undefined"),
},
JSExpr::Sym(s) => write!(f, "{}", s),
JSExpr::Array(v) => {
write!(f, "[")?;
for (i, e) in v.iter().enumerate() {
if i > 0 { write!(f, ", ")?; }
write!(f, "{}", e)?;
}
write!(f, "]")
},
JSExpr::Op(op, lhs, rhs) => {
match rhs {
Some(rhs) => write!(f, "({} {} {})", lhs, op, rhs),
None => write!(f, "({} {})", op, lhs),
}
}
JSExpr::Call(c, args) => {
write!(f, "{}(", c)?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
},
JSExpr::Method(c, m) => write!(f, "{}.{}", c, m),
JSExpr::Lambda { args, body } => {
write!(f, "((")?;
for (i, name) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", name)?;
}
// write!(f, ") => {})", body)
if body.len() == 1 {
write!(f, ") => {})", body[0])
} else {
write!(f, ") => {{")?;
for e in body {
write!(f, "{};", e)?;
}
write!(f, "}})")
}
},
JSExpr::If { cond, t, f: fe } => {
write!(f, "({} ? {} : {})", cond, t, fe)
},
JSExpr::Defines(vs) => {
write!(f, "let [")?;
for (i, (name, _)) in vs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", name)?;
}
write!(f, "] = [")?;
for (i, (_, expr)) in vs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", expr)?;
}
write!(f, "]")
}
JSExpr::Return(e) => write!(f, "return {}", e),
}
}
}
impl Display for JSStmt {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
JSStmt::Expr(e) => write!(f, "{}", e),
JSStmt::Func { name, args, ret: _, body } => {
// const name = (args) => body;
write!(f, "const {} = (", name)?;
for (i, name) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", name)?;
}
write!(f, ") => {};", body)
},
}
}
}

View file

@ -1,3 +0,0 @@
pub mod past;
pub mod ast;
pub mod js;

View file

@ -1,59 +0,0 @@
use crate::trans::ty::*;
use crate::read::parse::Spanned;
#[derive(Clone, Debug)]
pub enum PUnaryOp {
Neg,
Not,
}
#[derive(Clone, Debug)]
pub enum PBinaryOp {
Add, Sub, Mul, Div, Mod,
Eq, Neq, Lt, Gt, Lte, Gte,
And, Or,
}
#[derive(Clone, Debug)]
pub enum PLiteral { Num(i64), Str(String), Bool(bool), Unit }
#[derive(Clone, Debug)]
pub enum PExpr {
Error,
Lit(PLiteral),
Sym(String),
Vec(Vec<Spanned<Self>>),
Unary(Spanned<PUnaryOp>, Box<Spanned<Self>>),
Binary(Spanned<PBinaryOp>, Box<Spanned<Self>>, Box<Spanned<Self>>),
Call(Box<Spanned<Self>>, Vec<Spanned<Self>>),
Lambda {
args: Vec<(String, Type)>,
body: Box<Spanned<Self>>,
},
Let {
vars: Vec<(String, Type, Spanned<Self>)>,
body: Option<Box<Spanned<Self>>>,
},
If {
cond: Box<Spanned<Self>>,
t: Box<Spanned<Self>>,
f: Box<Spanned<Self>>,
},
Block(Vec<Spanned<Self>>),
Return(Box<Spanned<Self>>),
}
#[derive(Clone, Debug)]
pub enum PStmt {
Expr(Spanned<PExpr>),
Func {
name: String,
args: Vec<(String, Type)>,
ret: Type,
body: Box<Spanned<PExpr>>,
},
}

View file

@ -1,55 +0,0 @@
#![feature(trait_alias)]
pub mod asts;
pub mod read;
pub mod trans;
pub mod args;
use std::io::Write;
use args::Options;
use read::parse::{lex, parse};
use structopt::StructOpt;
use trans::low::{translate_stmt, translate_js_stmt};
fn main() {
let opt = Options::from_args();
let src = std::fs::read_to_string(opt.file).expect("Failed to read file");
let (tokens, lex_errs) = lex(src.to_owned());
let parse_errs = if let Some(tokens) = tokens {
let (past, parse_errs) = parse(tokens, src.len());
if let Some(past) = past {
let ast = past.into_iter().map(|(e, _)| translate_stmt(e)).collect::<Vec<_>>();
let js = ast.into_iter().map(translate_js_stmt).collect::<Vec<_>>();
let mut file = std::fs::File::create(opt.output.unwrap_or_else(|| "out.js".to_owned()))
.expect("Failed to create file");
let s = js
.into_iter()
.map(|e| {
let s = format!("{}", e);
if s.ends_with(';') {
s
} else {
format!("{};", s)
}
})
.collect::<Vec<_>>()
.join("\n");
file.write_all(s.as_bytes()).expect("Failed to write to file");
}
parse_errs
} else {
Vec::new()
};
if !lex_errs.is_empty() || !parse_errs.is_empty() {
lex_errs
.into_iter()
.map(|e| e.map(|c| c.to_string()))
.chain(parse_errs.into_iter().map(|e| e.map(|t| t.to_string())))
.for_each(|e| println!("[{:?} {:?}] {}", e.span(), e.label(), e));
}
}

View file

@ -1 +0,0 @@
pub mod parse;

View file

@ -1,521 +0,0 @@
#![allow(clippy::type_complexity)]
use chumsky::{prelude::*, Stream};
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::trans::ty::Type;
use crate::asts::past::*;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Delim { Paren, Brack, Brace }
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Token {
Num(i64), Str(String), Bool(bool), Sym(String),
Add, Sub, Mul, Div, Mod,
Eq, Neq, Lt, Gt, Lte, Gte,
And, Or, Not,
Assign, Comma, Colon, Semicolon,
Open(Delim), Close(Delim),
Lambda, Arrow,
Let, In, Func, Return, If, Then, Else,
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Token::Num(n) => write!(f, "{}", n),
Token::Str(s) => write!(f, "\"{}\"", s),
Token::Bool(b) => write!(f, "{}", b),
Token::Sym(s) => write!(f, "{}", s),
Token::Add => write!(f, "+"),
Token::Sub => write!(f, "-"),
Token::Mul => write!(f, "*"),
Token::Div => write!(f, "/"),
Token::Mod => write!(f, "%"),
Token::Eq => write!(f, "=="),
Token::Neq => write!(f, "!="),
Token::Lt => write!(f, "<"),
Token::Gt => write!(f, ">"),
Token::Lte => write!(f, "<="),
Token::Gte => write!(f, ">="),
Token::And => write!(f, "&&"),
Token::Or => write!(f, "||"),
Token::Not => write!(f, "!"),
Token::Assign => write!(f, "="),
Token::Comma => write!(f, ","),
Token::Colon => write!(f, ":"),
Token::Semicolon => write!(f, ";"),
Token::Open(d) => write!(f, "{}", match d {
Delim::Paren => "(",
Delim::Brack => "[",
Delim::Brace => "{",
}),
Token::Close(d) => write!(f, "{}", match d {
Delim::Paren => ")",
Delim::Brack => "]",
Delim::Brace => "}",
}),
Token::Lambda => write!(f, "\\"),
Token::Arrow => write!(f, "->"),
Token::Let => write!(f, "let"),
Token::In => write!(f, "in"),
Token::Func => write!(f, "func"),
Token::Return => write!(f, "return"),
Token::If => write!(f, "if"),
Token::Then => write!(f, "then"),
Token::Else => write!(f, "else"),
}
}
}
pub type Span = std::ops::Range<usize>;
pub type Spanned<T> = (T, Span);
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
let num = text::int(10)
.map(|s: String| Token::Num(s.parse().unwrap()));
let string = just('"')
.ignore_then(filter(|c| *c != '"').repeated())
.then_ignore(just('"'))
.collect::<String>()
.map(Token::Str);
let symbol = choice((
just("->").to(Token::Arrow),
just('+').to(Token::Add),
just('-').to(Token::Sub),
just('*').to(Token::Mul),
just('/').to(Token::Div),
just('%').to(Token::Mod),
just("==").to(Token::Eq),
just("!=").to(Token::Neq),
just("<=").to(Token::Lte),
just(">=").to(Token::Gte),
just('<').to(Token::Lt),
just('>').to(Token::Gt),
just("&&").to(Token::And),
just("||").to(Token::Or),
just('!').to(Token::Not),
just('=').to(Token::Assign),
just(',').to(Token::Comma),
just(':').to(Token::Colon),
just(';').to(Token::Semicolon),
just('\\').to(Token::Lambda),
));
let delim = choice((
just('(').to(Token::Open(Delim::Paren)),
just(')').to(Token::Close(Delim::Paren)),
just('[').to(Token::Open(Delim::Brack)),
just(']').to(Token::Close(Delim::Brack)),
just('{').to(Token::Open(Delim::Brace)),
just('}').to(Token::Close(Delim::Brace)),
));
let kw = text::ident()
.map(|s: String| match s.as_str() {
"true" => Token::Bool(true),
"false" => Token::Bool(false),
"let" => Token::Let,
"in" => Token::In,
"func" => Token::Func,
"return" => Token::Return,
"if" => Token::If,
"then" => Token::Then,
"else" => Token::Else,
_ => Token::Sym(s),
});
let token = num
.or(string)
.or(symbol)
.or(delim)
.or(kw)
.map_with_span(move |token, span| (token, span))
.padded()
.recover_with(skip_then_retry_until([]));
let comments = just('/')
.then_ignore(
just('*')
.ignore_then(take_until(just("*/")).ignored())
.or(just('/').ignore_then(none_of('\n').ignored().repeated().ignored())),
)
.padded()
.ignored()
.repeated();
token
.padded_by(comments)
.repeated()
.padded()
.then_ignore(end())
}
pub fn lex(src: String) -> (Option<Vec<(Token, Span)>>, Vec<Simple<char>>) {
let (tokens, lex_error) = lexer().parse_recovery(src.as_str());
(tokens, lex_error)
}
pub trait P<T> = chumsky::Parser<Token, T, Error = Simple<Token>> + Clone;
pub fn literal_parser() -> impl P<PLiteral> {
filter_map(|span, token| match token {
Token::Num(i) => Ok(PLiteral::Num(i)),
Token::Bool(b) => Ok(PLiteral::Bool(b)),
Token::Str(s) => Ok(PLiteral::Str(s)),
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
})
.labelled("literal")
}
pub fn symbol_parser() -> impl P<String> {
filter_map(|span, token| match token {
Token::Sym(s) => Ok(s),
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
})
.labelled("symbol")
}
pub fn type_parser() -> impl P<Type> {
recursive(|ty| {
let litty = symbol_parser().map(|s| match s.as_str() {
"num" => Type::Num,
"str" => Type::Str,
"bool" => Type::Bool,
"?" => Type::Unknown,
_ => Type::Sym(s),
});
let fun = just(Token::Open(Delim::Paren))
.ignore_then(
ty.clone()
.separated_by(just(Token::Comma))
)
.then_ignore(just(Token::Close(Delim::Paren)))
.then_ignore(just(Token::Arrow))
.then(ty)
.map(|(args, ret)| Type::Fun(args, Box::new(ret)));
litty
.or(fun)
.labelled("type")
})
}
pub fn nested_parser<'a, T: 'a>(
parser: impl P<T> + 'a,
delim: Delim,
f: impl Fn(Span) -> T + Clone + 'a,
) -> impl P<T> + 'a {
parser
.delimited_by(just(Token::Open(delim)), just(Token::Close(delim)))
.recover_with(nested_delimiters(
Token::Open(delim),
Token::Close(delim),
[
(
Token::Open(Delim::Paren),
Token::Close(Delim::Paren),
),
(
Token::Open(Delim::Brack),
Token::Close(Delim::Brack),
),
(
Token::Open(Delim::Brace),
Token::Close(Delim::Brace),
),
],
f,
))
.boxed()
}
pub fn expr_parser() -> impl P<Spanned<PExpr>> {
recursive(|expr: Recursive<Token, Spanned<PExpr>, Simple<Token>>| {
let lit = literal_parser().map(PExpr::Lit);
let sym = symbol_parser().map(PExpr::Sym);
let vec = nested_parser(
expr.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.map(Some),
Delim::Brack,
|_| None,
)
.map(|xs| match xs {
Some(xs) => PExpr::Vec(xs),
None => PExpr::Vec(Vec::new()),
})
.labelled("vector");
// (e)
let paren_expr = just(Token::Open(Delim::Paren))
.ignore_then(expr.clone())
.then_ignore(just(Token::Close(Delim::Paren)))
.map(|e| e.0)
.labelled("parenthesized expression");
// \[sym : type]* -> expr
let lam = just(Token::Lambda)
.ignore_then(
(
symbol_parser()
.then_ignore(just(Token::Colon))
.then(type_parser())
)
.repeated()
)
.then_ignore(just(Token::Arrow))
.then(expr.clone())
.map(|(args, body)| PExpr::Lambda {
args,
body: Box::new(body),
})
.labelled("lambda");
let let_binds = symbol_parser()
.then_ignore(just(Token::Colon))
.then(type_parser())
.then_ignore(just(Token::Assign))
.then(expr.clone())
.map(|((sym, ty), body)| (sym, ty, body))
.boxed()
.labelled("let binding")
.separated_by(just(Token::Comma))
.allow_trailing()
.labelled("let bindings");
let let_in = just(Token::Let)
.ignore_then(let_binds.clone())
.then_ignore(just(Token::In))
.then(expr.clone())
.map(|(vars, body)| PExpr::Let {
vars,
body: Some(Box::new(body)),
})
.boxed()
.labelled("let with expression");
let let_def = just(Token::Let)
.ignore_then(let_binds)
.map(|vars| PExpr::Let { vars, body: None })
.boxed()
.labelled("let definition");
let block = nested_parser(
expr.clone()
.separated_by(just(Token::Semicolon))
.allow_trailing(),
Delim::Brace,
|_| Vec::new(),
)
.map(PExpr::Block)
.labelled("block");
let ret = just(Token::Return)
.ignore_then(expr.clone())
.map(Box::new)
.map(PExpr::Return)
.labelled("return");
let ifelse = just(Token::If)
.ignore_then(expr.clone())
.then_ignore(just(Token::Then))
.then(expr.clone())
.then_ignore(just(Token::Else))
.then(expr.clone())
.map(|((cond, then), f)| PExpr::If {
cond: Box::new(cond),
t: Box::new(then),
f: Box::new(f),
})
.boxed()
.labelled("if else");
let atom = lit
.or(sym)
.or(vec)
.or(paren_expr)
.or(lam)
.or(let_in)
.or(let_def)
.or(block)
.or(ret)
.or(ifelse)
.map_with_span(|e, s| (e, s))
.boxed()
.labelled("atom");
// e(e*)
let call = atom
.then(
nested_parser(
expr.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.map(Some),
Delim::Paren,
|_| None,
)
.or_not(),
)
.map_with_span(|(f, args), s| match args {
Some(Some(args)) => (PExpr::Call(Box::new(f), args), s),
Some(None) => (PExpr::Error, s),
None => f,
});
// op e
let unary = choice((
just(Token::Sub).to(PUnaryOp::Neg),
just(Token::Not).to(PUnaryOp::Not),
))
.map_with_span(|op, s| (op, s))
.repeated()
.then(call)
.foldr(|op, expr| {
let s = op.1.start()..expr.1.end();
(PExpr::Unary(op, Box::new(expr)), s)
})
.boxed();
let product = unary
.clone()
.then(
choice((
just(Token::Mul).to(PBinaryOp::Mul),
just(Token::Div).to(PBinaryOp::Div),
just(Token::Mod).to(PBinaryOp::Mod),
))
.map_with_span(|op, s| (op, s))
.then(unary)
.repeated(),
)
.foldl(|lhs, (op, rhs)| {
let s = lhs.1.start()..rhs.1.end();
(PExpr::Binary(op, Box::new(lhs), Box::new(rhs)), s)
})
.boxed();
let sum = product
.clone()
.then(
choice((
just(Token::Add).to(PBinaryOp::Add),
just(Token::Sub).to(PBinaryOp::Sub),
))
.map_with_span(|op, s| (op, s))
.then(product)
.repeated(),
)
.foldl(|lhs, (op, rhs)| {
let s = lhs.1.start()..rhs.1.end();
(PExpr::Binary(op, Box::new(lhs), Box::new(rhs)), s)
})
.boxed();
let comparison = sum
.clone()
.then(
choice((
just(Token::Eq).to(PBinaryOp::Eq),
just(Token::Neq).to(PBinaryOp::Neq),
just(Token::Lt).to(PBinaryOp::Lt),
just(Token::Lte).to(PBinaryOp::Lte),
just(Token::Gt).to(PBinaryOp::Gt),
just(Token::Gte).to(PBinaryOp::Gte),
))
.map_with_span(|op, s| (op, s))
.then(sum)
.repeated(),
)
.foldl(|lhs, (op, rhs)| {
let s = lhs.1.start()..rhs.1.end();
(PExpr::Binary(op, Box::new(lhs), Box::new(rhs)), s)
})
.boxed();
comparison
.clone()
.then(
choice((
just(Token::And).to(PBinaryOp::And),
just(Token::Or).to(PBinaryOp::Or),
))
.map_with_span(|op, s| (op, s))
.then(comparison)
.repeated(),
)
.foldl(|lhs, (op, rhs)| {
let s = lhs.1.start()..rhs.1.end();
(PExpr::Binary(op, Box::new(lhs), Box::new(rhs)), s)
})
.boxed()
})
}
pub fn exprs_parser() -> impl P<Vec<Spanned<PExpr>>> {
expr_parser()
.then_ignore(just(Token::Semicolon))
.repeated()
}
pub fn stmt_parser() -> impl P<Spanned<PStmt>> {
let func = just(Token::Func)
.ignore_then(symbol_parser())
.then(
nested_parser(
symbol_parser()
.then_ignore(just(Token::Colon))
.then(type_parser())
.separated_by(just(Token::Comma))
.allow_trailing(),
Delim::Paren,
|_| Vec::new(),
)
)
.then(type_parser())
.then_ignore(just(Token::Assign))
.then(expr_parser().map(Box::new))
.map(|(((name, args), ret), body)| PStmt::Func {
name,
args,
ret,
body,
});
let expr = expr_parser().map(PStmt::Expr);
func
.or(expr)
.map_with_span(|s, span| (s, span))
}
pub fn stmts_parser() -> impl P<Vec<Spanned<PStmt>>> {
stmt_parser()
.then_ignore(just(Token::Semicolon))
.repeated()
}
pub fn parse(
tokens: Vec<Spanned<Token>>,
len: usize,
) -> (Option<Vec<Spanned<PStmt>>>, Vec<Simple<Token>>) {
let (ast, parse_error) = stmts_parser()
.then_ignore(end())
.parse_recovery(Stream::from_iter(len..=len, tokens.into_iter()));
(ast, parse_error)
}

View file

@ -1,198 +0,0 @@
use crate::asts::{
past::*,
ast::*,
js::*,
};
pub fn translate_stmt(stmt: PStmt) -> Stmt {
match stmt {
PStmt::Expr(e) => Stmt::Expr(translate_expr(e.0)),
PStmt::Func { name, args, ret, body } => Stmt::Func {
name,
args: args.into_iter().map(|(name, _ty)| name).collect(),
ret,
body: translate_expr(body.0),
},
}
}
pub fn exprs_to_lam(es: Vec<PExpr>) -> Expr {
let lam = Expr::Lambda {
args: vec![],
body: es.into_iter().map(translate_expr).collect(),
};
Expr::Call(Box::new(lam), vec![])
}
pub fn translate_expr(expr: PExpr) -> Expr {
match expr {
PExpr::Error => panic!("Error in expression!"),
PExpr::Lit(l) => Expr::Lit(match l {
PLiteral::Num(n) => Literal::Num(n),
PLiteral::Str(s) => Literal::Str(s),
PLiteral::Bool(b) => Literal::Bool(b),
PLiteral::Unit => Literal::Unit,
}),
PExpr::Sym(s) => Expr::Sym(s),
PExpr::Vec(v) => Expr::Vec(v.into_iter().map(|e| translate_expr(e.0)).collect()),
PExpr::Unary(op, e) => Expr::UnaryOp(match op.0 {
PUnaryOp::Neg => UnaryOp::Neg,
PUnaryOp::Not => UnaryOp::Not,
}, Box::new(translate_expr((*e).0))),
PExpr::Binary((op, _), e1, e2) => Expr::BinaryOp(
match op {
PBinaryOp::Add => BinaryOp::Add,
PBinaryOp::Sub => BinaryOp::Sub,
PBinaryOp::Mul => BinaryOp::Mul,
PBinaryOp::Div => BinaryOp::Div,
PBinaryOp::Mod => BinaryOp::Mod,
PBinaryOp::Eq => BinaryOp::Eq,
PBinaryOp::Neq => BinaryOp::Neq,
PBinaryOp::Lt => BinaryOp::Lt,
PBinaryOp::Gt => BinaryOp::Gt,
PBinaryOp::Lte => BinaryOp::Lte,
PBinaryOp::Gte => BinaryOp::Gte,
PBinaryOp::And => BinaryOp::And,
PBinaryOp::Or => BinaryOp::Or,
},
Box::new(translate_expr((*e1).0)),
Box::new(translate_expr((*e2).0)),
),
PExpr::Call(f, args) => Expr::Call(
Box::new(translate_expr((*f).0)),
args.into_iter().map(|a| translate_expr(a.0)).collect(),
),
PExpr::Lambda { args, body } => Expr::Lambda {
args: args.into_iter().map(|(name, _ty)| name).collect(),
body: vec![translate_expr((*body).0)],
},
PExpr::Let { vars, body } => {
if let Some(body) = body {
let mut expr: Expr = translate_expr(body.0); // The expression we're building up
for (name, _ty, val) in vars.into_iter().rev() { // Reverse so we can build up the lambda
// e.g.: let x : t = e1 in e2; => (lambda (x : t) = e2)(e1)
// Build up the lambda
expr = Expr::Lambda {
args: vec![name],
body: vec![expr],
};
// Call the lambda with the value
let val = translate_expr(val.0);
expr = Expr::Call(Box::new(expr), vec![val]);
}
expr
} else {
Expr::Defines(vars.into_iter().map(|(name, _ty, val)|
(name, translate_expr(val.0))
).collect())
}
},
PExpr::If { cond, t, f } => Expr::If {
cond: Box::new(translate_expr((*cond).0)),
t: Box::new(translate_expr((*t).0)),
f: Box::new(translate_expr((*f).0)),
},
PExpr::Block(es) => {
exprs_to_lam(es.into_iter().map(|e| e.0).collect())
},
PExpr::Return(e) => Expr::Return(Box::new(translate_expr((*e).0))),
}
}
pub fn translate_js_stmt(stmt: Stmt) -> JSStmt {
match stmt {
Stmt::Expr(e) => JSStmt::Expr(translate_js_expr(e)),
Stmt::Func { name, args, ret, body } => JSStmt::Func {
name,
args,
ret,
body: translate_js_expr(body),
},
}
}
pub fn translate_js_expr(expr: Expr) -> JSExpr {
match expr {
Expr::Lit(l) => match l {
Literal::Num(n) => JSExpr::Lit(JSLiteral::Num(n)),
Literal::Str(s) => JSExpr::Lit(JSLiteral::Str(s)),
Literal::Bool(b) => JSExpr::Lit(JSLiteral::Bool(b)),
Literal::Unit => JSExpr::Lit(JSLiteral::Undefined),
},
Expr::Sym(s) => JSExpr::Sym(s),
Expr::Vec(v) => JSExpr::Array(v.into_iter().map(translate_js_expr).collect()),
Expr::UnaryOp(op, e) => JSExpr::Op(match op {
UnaryOp::Neg => "-",
UnaryOp::Not => "!",
}, Box::new(translate_js_expr(*e)), None),
Expr::BinaryOp(op, e1, e2) => JSExpr::Op(match op {
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::Eq => "==",
BinaryOp::Neq => "!=",
BinaryOp::Lt => "<",
BinaryOp::Gt => ">",
BinaryOp::Lte => "<=",
BinaryOp::Gte => ">=",
BinaryOp::And => "&&",
BinaryOp::Or => "||",
}, Box::new(translate_js_expr(*e1)), Some(Box::new(translate_js_expr(*e2)))),
Expr::Call(f, args) => {
match *f {
Expr::Sym(ref s) => {
match s.as_str() {
"println" => JSExpr::Call(
Box::new(JSExpr::Method(
Box::new(JSExpr::Sym("console".to_string())),
"log".to_string(),
)),
args.into_iter().map(translate_js_expr).collect()),
"print" => JSExpr::Call(
Box::new(JSExpr::Method(
Box::new(JSExpr::Method(
Box::new(JSExpr::Sym("process".to_string())),
"stdout".to_string(),
)),
"write".to_string(),
)),
args.into_iter().map(translate_js_expr).collect()),
_ => JSExpr::Call(
Box::new(translate_js_expr(*f)),
args.into_iter().map(translate_js_expr).collect(),
),
}
},
_ => JSExpr::Call(
Box::new(translate_js_expr(*f)),
args.into_iter().map(translate_js_expr).collect(),
),
}
}
Expr::Lambda { args, body } => JSExpr::Lambda {
args,
body: body.into_iter().map(translate_js_expr).collect(),
},
Expr::If { cond, t, f } => JSExpr::If {
cond: Box::new(translate_js_expr(*cond)),
t: Box::new(translate_js_expr(*t)),
f: Box::new(translate_js_expr(*f)),
},
Expr::Defines(defs) => JSExpr::Defines(defs.into_iter().map(|(name, val)|
(name, translate_js_expr(val))
).collect()),
Expr::Return(e) => JSExpr::Return(Box::new(translate_js_expr(*e))),
}
}

View file

@ -1,2 +0,0 @@
pub mod ty;
pub mod low;

View file

@ -1,31 +0,0 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
#[derive(Clone, Debug)]
pub enum Type {
Num, Str, Bool,
Sym(String),
Fun(Vec<Self>, Box<Self>),
Unknown,
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Type::Num => write!(f, "num"),
Type::Str => write!(f, "str"),
Type::Bool => write!(f, "bool"),
Type::Sym(s) => write!(f, "{}", s),
Type::Fun(args, ret) => {
write!(f, "(")?;
for (i, arg) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", arg)?;
}
write!(f, ") -> {}", ret)
},
Type::Unknown => write!(f, "unknown"),
}
}
}

7
syntax/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "syntax"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = { version = "1.0.0-alpha.3", features = ["label"] }

167
syntax/src/expr.rs Normal file
View file

@ -0,0 +1,167 @@
use std::fmt::{ Display, Formatter, self };
use chumsky::span::SimpleSpan;
use super::ty::Type;
#[derive(Clone, Debug, PartialEq)]
pub enum Delim { Paren, Brack, Brace }
// The tokens of the language.
// 'src is the lifetime of the source code string.
#[derive(Clone, Debug, PartialEq)]
pub enum Token<'src> {
Unit, Bool(bool), Int(i64), Str(&'src str),
Ident(&'src str),
Add, Sub, Mul, Div, Rem,
Eq, Ne, Lt, Gt, Le, Ge,
And, Or, Not,
Pipe,
Assign, Comma, Colon, Semicolon,
Open(Delim), Close(Delim),
Lambda, Arrow,
Let, In, Func, Return, If, Then, Else,
}
impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Token::Unit => write!(f, "()"),
Token::Bool(b) => write!(f, "{}", b),
Token::Int(n) => write!(f, "{}", n),
Token::Str(s) => write!(f, "\"{}\"", s),
Token::Ident(s) => write!(f, "{}", s),
Token::Add => write!(f, "+"),
Token::Sub => write!(f, "-"),
Token::Mul => write!(f, "*"),
Token::Div => write!(f, "/"),
Token::Rem => write!(f, "%"),
Token::Eq => write!(f, "=="),
Token::Ne => write!(f, "!="),
Token::Lt => write!(f, "<"),
Token::Gt => write!(f, ">"),
Token::Le => write!(f, "<="),
Token::Ge => write!(f, ">="),
Token::And => write!(f, "&&"),
Token::Or => write!(f, "||"),
Token::Not => write!(f, "!"),
Token::Pipe => write!(f, "|>"),
Token::Assign => write!(f, "="),
Token::Comma => write!(f, ","),
Token::Colon => write!(f, ":"),
Token::Semicolon => write!(f, ";"),
Token::Open(d) => write!(f, "{}", match d {
Delim::Paren => "(",
Delim::Brack => "[",
Delim::Brace => "{",
}),
Token::Close(d) => write!(f, "{}", match d {
Delim::Paren => ")",
Delim::Brack => "]",
Delim::Brace => "}",
}),
Token::Lambda => write!(f, "\\"),
Token::Arrow => write!(f, "->"),
Token::Let => write!(f, "let"),
Token::In => write!(f, "in"),
Token::Func => write!(f, "func"),
Token::Return => write!(f, "return"),
Token::If => write!(f, "if"),
Token::Then => write!(f, "then"),
Token::Else => write!(f, "else"),
}
}
}
pub type Span = SimpleSpan<usize>;
#[derive(Clone, Debug, PartialEq)]
pub enum Lit<'src> {
Unit,
Bool(bool),
Int(i64),
Str(&'src str),
}
#[derive(Clone, Debug)]
pub enum UnaryOp { Neg, Not }
impl Display for UnaryOp {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
UnaryOp::Neg => write!(f, "-"),
UnaryOp::Not => write!(f, "!"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum BinaryOp {
Add, Sub, Mul, Div, Rem,
And, Or,
Eq, Ne, Lt, Le, Gt, Ge,
Pipe,
}
impl Display for BinaryOp {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
BinaryOp::Add => write!(f, "+"),
BinaryOp::Sub => write!(f, "-"),
BinaryOp::Mul => write!(f, "*"),
BinaryOp::Div => write!(f, "/"),
BinaryOp::Rem => write!(f, "%"),
BinaryOp::And => write!(f, "&&"),
BinaryOp::Or => write!(f, "||"),
BinaryOp::Eq => write!(f, "=="),
BinaryOp::Ne => write!(f, "!="),
BinaryOp::Lt => write!(f, "<"),
BinaryOp::Le => write!(f, "<="),
BinaryOp::Gt => write!(f, ">"),
BinaryOp::Ge => write!(f, ">="),
BinaryOp::Pipe => write!(f, "|>"),
}
}
}
pub type Spanned<T> = (T, Span);
// Clone is needed for type checking since the type checking
// algorithm is recursive and sometimes consume the AST.
#[derive(Clone, Debug)]
pub enum Expr<'src> {
Lit(Lit<'src>),
Ident(&'src str),
Unary(UnaryOp, Spanned<Box<Self>>),
Binary(BinaryOp, Spanned<Box<Self>>, Spanned<Box<Self>>),
Lambda(Vec<(&'src str, Option<Type>)>, Option<Type>, Spanned<Box<Self>>),
Call(Spanned<Box<Self>>, Vec<Spanned<Self>>),
If {
cond: Spanned<Box<Self>>,
t: Spanned<Box<Self>>,
f: Spanned<Box<Self>>,
},
Let {
name: &'src str,
ty: Option<Type>,
value: Spanned<Box<Self>>,
body: Spanned<Box<Self>>,
},
Define {
name: &'src str,
ty: Option<Type>,
value: Spanned<Box<Self>>,
},
Block {
exprs: Vec<Spanned<Box<Self>>>,
void: bool, // True if last expression is discarded (ends with semicolon).
},
}

3
syntax/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod expr;
pub mod parser;
pub mod ty;

459
syntax/src/parser.rs Normal file
View file

@ -0,0 +1,459 @@
use chumsky::prelude::*;
use super::{ expr::*, ty::Type };
pub fn lexer<'src>() -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, extra::Err<Rich<'src, char, Span>>> {
// let num = text::int(10)
// .then(just('.').then(text::digits(10)).or_not())
// .slice()
// .from_str()
// .unwrapped()
// .map(Token::Int);
let int = text::int(10)
.slice()
.from_str()
.unwrapped()
.map(Token::Int);
let strn = just('"')
.ignore_then(none_of('"').repeated())
.then_ignore(just('"'))
.map_slice(Token::Str);
fn id_filter<C>(c: &C) -> bool where C: text::Char {
c.to_char().is_ascii_alphabetic()
|| "_'".contains(c.to_char())
}
let id = any()
.filter(id_filter)
.then(any()
.filter(id_filter)
.repeated())
.slice();
let word = id.map(|s: &str| match s {
"true" => Token::Bool(true),
"false" => Token::Bool(false),
"let" => Token::Let,
"in" => Token::In,
"fun" => Token::Func,
"return" => Token::Return,
"if" => Token::If,
"then" => Token::Then,
"else" => Token::Else,
_ => Token::Ident(s),
});
let sym = choice((
just("()").to(Token::Unit),
just("\\").to(Token::Lambda),
just("->").to(Token::Arrow),
just("|>").to(Token::Pipe),
just('+').to(Token::Add),
just('-').to(Token::Sub),
just('*').to(Token::Mul),
just('/').to(Token::Div),
just('%').to(Token::Rem),
just("==").to(Token::Eq),
just("!=").to(Token::Ne),
just("<=").to(Token::Le),
just(">=").to(Token::Ge),
just('<').to(Token::Lt),
just('>').to(Token::Gt),
just("&&").to(Token::And),
just("||").to(Token::Or),
just('!').to(Token::Not),
just('=').to(Token::Assign),
just(',').to(Token::Comma),
just(':').to(Token::Colon),
just(';').to(Token::Semicolon),
));
let delim = choice((
just('(').to(Token::Open(Delim::Paren)),
just(')').to(Token::Close(Delim::Paren)),
just('[').to(Token::Open(Delim::Brack)),
just(']').to(Token::Close(Delim::Brack)),
just('{').to(Token::Open(Delim::Brace)),
just('}').to(Token::Close(Delim::Brace)),
));
let token = choice((
int,
strn,
word,
sym,
delim,
));
let comment = just("//")
.then(any().and_is(just('\n').not()).repeated())
.padded();
token
.map_with_span(move |tok, span| (tok, span))
.padded_by(comment.repeated())
.padded()
// If we get an error, skip to the next character and try again.
.recover_with(skip_then_retry_until(any().ignored(), end()))
.repeated()
.collect()
}
// (a, s) -> (Box::new(a), s)
fn boxspan<T>(a: Spanned<T>) -> Spanned<Box<T>> {
(Box::new(a.0), a.1)
}
// Lifetime 'tokens is the lifetime of the token buffer from the lexer.
type ParserInput<'tokens, 'src> =
chumsky::input::SpannedInput<
Token<'src>,
Span,
&'tokens [(Token<'src>, Span)]
>;
pub fn expr_parser<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Spanned<Expr<'src>>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
recursive(|expr| {
let lit = select! {
Token::Unit => Expr::Lit(Lit::Unit),
Token::Bool(b) => Expr::Lit(Lit::Bool(b)),
Token::Int(n) => Expr::Lit(Lit::Int(n)),
Token::Str(s) => Expr::Lit(Lit::Str(s)),
};
let symbol = select! {
Token::Ident(s) => s,
};
let ident = symbol
.map(Expr::Ident);
let paren_expr = expr.clone()
.delimited_by(
just(Token::Open(Delim::Paren)),
just(Token::Close(Delim::Paren)),
)
.map(|e: Spanned<Expr>| e.0);
let lambda = just(Token::Func)
.ignore_then(
(symbol
.then(type_parser().or_not())
.separated_by(just(Token::Comma))
.collect::<Vec<_>>()
.delimited_by(
just(Token::Open(Delim::Paren)),
just(Token::Close(Delim::Paren)),
))
.or(just(Token::Unit).to(Vec::new()))
)
.then(type_parser().or_not())
.then_ignore(just(Token::Arrow))
.then(expr.clone())
.map(|((args, ret), body)| Expr::Lambda(args, ret, boxspan(body)));
// ident (: type)?
let bind = symbol
.then(
just(Token::Colon)
.ignore_then(type_parser())
.or_not()
)
.then_ignore(just(Token::Assign))
.then(expr.clone())
.map(|((name, ty), expr)| (name, ty, boxspan(expr)));
let let_or_define = just(Token::Let)
.ignore_then(bind)
.then(
just(Token::In)
.ignore_then(expr.clone())
.or_not()
)
.map(|((name, ty, expr), body)| match body {
Some(body) => Expr::Let { name, ty, value: expr, body: boxspan(body) },
None => Expr::Define { name, ty, value: expr },
});
let if_ = just(Token::If)
.ignore_then(expr.clone())
.then_ignore(just(Token::Then))
.then(expr.clone())
.then_ignore(just(Token::Else))
.then(expr.clone())
.map(|((cond, t), f)| Expr::If {
cond: boxspan(cond),
t: boxspan(t),
f: boxspan(f)
});
let block = expr.clone()
.map(boxspan)
.then_ignore(just(Token::Semicolon))
.repeated()
.collect::<Vec<_>>()
.then(expr.clone()
.map(boxspan)
.or_not())
.delimited_by(
just(Token::Open(Delim::Brace)),
just(Token::Close(Delim::Brace)),
)
.map(|(mut exprs, end)| {
let void = end.is_none();
if let Some(end) = end {
exprs.push(end);
}
Expr::Block {
exprs,
void,
}
});
let atom = lit
.or(ident)
.or(paren_expr)
.or(lambda)
.or(let_or_define)
.or(if_)
.or(block)
.map_with_span(|e, s| (e, s))
.boxed()
.labelled("(atomic) expression");
let call = atom
.then(
expr.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(
just(Token::Open(Delim::Paren)),
just(Token::Close(Delim::Paren)),
)
.or_not()
)
.map_with_span(|(f, args), s| match args {
Some(args) => (Expr::Call(boxspan(f), args), s),
None => (f.0, f.1),
});
let op = choice((
just(Token::Sub).to(UnaryOp::Neg),
just(Token::Not).to(UnaryOp::Not),
));
let unary = op
.map_with_span(|op, s| (op, s))
.repeated()
.foldr(
call,
|op, expr| {
let span = op.1.start..expr.1.end;
(Expr::Unary(op.0, boxspan(expr)), span.into())
});
let op = choice((
just(Token::Mul).to(BinaryOp::Mul),
just(Token::Div).to(BinaryOp::Div),
just(Token::Rem).to(BinaryOp::Rem),
));
let product = unary.clone()
.foldl(
op.then(unary).repeated(),
|a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::Binary(op, boxspan(a), boxspan(b)), span.into())
}
);
let op = choice((
just(Token::Add).to(BinaryOp::Add),
just(Token::Sub).to(BinaryOp::Sub),
));
let sum = product.clone()
.foldl(
op.then(product).repeated(),
|a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::Binary(op, boxspan(a), boxspan(b)), span.into())
}
);
let op = choice((
just(Token::Eq).to(BinaryOp::Eq),
just(Token::Ne).to(BinaryOp::Ne),
just(Token::Lt).to(BinaryOp::Lt),
just(Token::Le).to(BinaryOp::Le),
just(Token::Gt).to(BinaryOp::Gt),
just(Token::Ge).to(BinaryOp::Ge),
));
let comparison = sum.clone()
.foldl(
op.then(sum).repeated(),
|a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::Binary(op, boxspan(a), boxspan(b)), span.into())
}
);
let op = choice((
just(Token::And).to(BinaryOp::And),
just(Token::Or).to(BinaryOp::Or),
));
let logical = comparison.clone()
.foldl(
op.then(comparison).repeated(),
|a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::Binary(op, boxspan(a), boxspan(b)), span.into())
}
);
let pipe = logical.clone()
.foldl(
just(Token::Pipe).to(BinaryOp::Pipe)
.then(logical).repeated(),
|a, (op, b)| {
let span = a.1.start..b.1.end;
(Expr::Binary(op, boxspan(a), boxspan(b)), span.into())
}
);
pipe
.labelled("expression")
})
}
pub fn type_parser<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Type,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
recursive(|ty| {
let lit_ty = select! {
Token::Ident("Bool") => Type::Bool,
Token::Ident("Int") => Type::Int,
Token::Ident("Str") => Type::Str,
// TODO: Support type variables in both the parser and the type checker.
Token::Ident(_) => Type::Var(69),
Token::Unit => Type::Unit,
}.validate(|tys, span, emitter| {
if let Type::Var(_) = tys {
emitter.emit(Rich::custom(span,
"Type variables are not yet supported.".to_string()
));
}
tys
});
let tys_paren = ty.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(
just(Token::Open(Delim::Paren)),
just(Token::Close(Delim::Paren)),
);
let func = tys_paren.clone()
.then_ignore(just(Token::Arrow))
.then(ty.clone())
.map(|(ta, tr)| Type::Func(ta, Box::new(tr)));
let tuple = tys_paren
.validate(|tys, span, emitter| {
if tys.is_empty() {
emitter.emit(Rich::custom(span,
"Tuple must have at least one element. Use `()` for the unit type."
.to_string()
));
}
tys
})
.map(Type::Tuple);
let array = ty.clone()
.delimited_by(
just(Token::Open(Delim::Brack)),
just(Token::Close(Delim::Brack)),
)
.map(|t| Type::Array(Box::new(t)));
lit_ty
.or(array)
.or(func)
.or(tuple)
.boxed()
.labelled("type")
})
}
pub fn exprs_parser<'tokens, 'src: 'tokens>() -> impl Parser<
'tokens,
ParserInput<'tokens, 'src>,
Vec<Spanned<Expr<'src>>>,
extra::Err<Rich<'tokens, Token<'src>, Span>>,
> + Clone {
expr_parser()
.separated_by(just(Token::Semicolon))
.allow_trailing()
.collect::<Vec<_>>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_parser() {
let input = "(() -> () -> () -> (num)) -> bool";
let (ts, errs) = lexer().parse(input).into_output_errors();
assert!(ts.is_some());
assert!(errs.is_empty());
if let Some(ts) = ts {
let (ast, parse_errs) = type_parser()
.map_with_span(|ty, span| (ty, span))
.parse(ts.as_slice().spanned((input.len()..input.len()).into()))
.into_output_errors();
println!("{:?}", ast);
println!("{:?}", parse_errs);
}
}
#[test]
fn test_expr_parser_atom() {
let input = "
let id : (A) -> A = (\\x -> x) in {
if false
then id(3.14)
else id(true);
}
";
let (ast, errs) = lexer().parse(input).into_output_errors();
assert!(ast.is_some());
assert!(errs.is_empty());
if let Some(ast) = ast {
let (ast, parse_errs) = expr_parser()
.map_with_span(|ty, span| (ty, span))
.parse(ast.as_slice().spanned((input.len()..input.len()).into()))
.into_output_errors();
println!("{:?}", ast);
println!("{:?}", parse_errs);
}
}
}

51
syntax/src/ty.rs Normal file
View file

@ -0,0 +1,51 @@
use std::fmt::{self, Display, Formatter};
// TODO: Introduce lifetime here to reduce cloning.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Type {
Unit, Bool, Int, Str,
Var(usize), // This type is only used during type inference.
Func(Vec<Type>, Box<Type>),
Tuple(Vec<Type>),
Array(Box<Type>),
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Type::Unit => write!(f, "Unit"),
Type::Bool => write!(f, "Bool"),
Type::Int => write!(f, "Int"),
Type::Str => write!(f, "Str"),
Type::Var(id) => write!(f, "{}", itoa(id)),
Type::Func(ref args, ref ret) => {
write!(f, "({}", args[0])?;
for arg in &args[1..] {
write!(f, " {}", arg)?;
}
write!(f, ") -> {}", ret)
}
Type::Tuple(ref tys) => {
write!(f, "({}", tys[0])?;
for ty in &tys[1..] {
write!(f, " {}", ty)?;
}
write!(f, ")")
}
Type::Array(ref ty) => write!(f, "[{}]", ty),
}
}
}
/// Convert a number to a string of lowercase letters
pub fn itoa(i: usize) -> String {
let mut s = String::new();
let mut i = i;
while i >= 26 {
s.push((b'A' + (i % 26) as u8) as char);
i /= 26;
}
s.push((b'A' + i as u8) as char);
s
}

8
typing/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "typing"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = "1.0.0-alpha.3"
syntax = { path = "../syntax" }

744
typing/src/infer.rs Normal file
View file

@ -0,0 +1,744 @@
use std::collections::HashMap;
use chumsky::span::SimpleSpan;
use syntax::{
expr::{
Lit, UnaryOp, BinaryOp,
Expr,
},
ty::*,
};
use crate::rename::{rename_exprs, rename_type};
use super::typed::TExpr;
macro_rules! ok {
($e:expr) => {
($e, vec![])
};
}
macro_rules! unbox {
($e:expr) => {
(*$e.0, $e.1)
};
}
#[derive(Clone, Debug)]
pub enum InferErrorKind {
Error,
Hint,
}
#[derive(Clone, Debug)]
pub struct InferError {
pub title: String,
pub labels: Vec<(String, InferErrorKind, SimpleSpan)>,
pub span: SimpleSpan,
}
impl InferError {
pub fn new<S: Into<String>>(title: S, span: SimpleSpan) -> Self {
Self {
title: title.into(),
labels: Vec::new(),
span,
}
}
pub fn add_error<S: Into<String>>(mut self, reason: S, span: SimpleSpan) -> Self {
self.labels.push((reason.into(), InferErrorKind::Error, span));
self
}
pub fn add_hint<S: Into<String>>(mut self, reason: S, span: SimpleSpan) -> Self {
self.labels.push((reason.into(), InferErrorKind::Hint, span));
self
}
}
#[derive(Clone, Debug, PartialEq)]
struct Constraint {
t1: Type,
t2: Type,
// Where the constraint was generated, for error reporting
span: SimpleSpan,
}
impl Constraint {
fn new(t1: Type, t2: Type, span: SimpleSpan) -> Self {
Self {
t1,
t2,
span,
}
}
}
#[derive(Clone, Debug)]
struct Infer<'src> {
env: HashMap<&'src str, Type>,
subst: Vec<Type>,
constraints: Vec<Constraint>,
}
impl<'src> Infer<'src> {
fn new() -> Self {
Infer {
env: HashMap::new(),
subst: Vec::new(),
constraints: Vec::new(),
}
}
/// Generate a fresh type variable
fn fresh(&mut self) -> Type {
let i = self.subst.len();
self.subst.push(Type::Var(i));
Type::Var(i)
}
/// Get a substitution for a type variable
fn subst(&self, i: usize) -> Option<Type> {
self.subst.get(i).cloned()
}
/// Add new constraint
fn add_constraint(&mut self, c: Constraint) {
self.constraints.push(c);
}
/// Check if a type variable occurs in a type
fn occurs(&self, i: usize, t: Type) -> bool {
use Type::*;
match t {
Unit | Bool | Int | Str => false,
Var(j) => {
if let Some(t) = self.subst(j) {
if t != Var(j) {
return self.occurs(i, t);
}
}
i == j
},
Func(args, ret) => {
args.into_iter().any(|t| self.occurs(i, t)) || self.occurs(i, *ret)
},
Tuple(tys) => tys.into_iter().any(|t| self.occurs(i, t)),
Array(ty) => self.occurs(i, *ty),
}
}
/// Unify two types
fn unify(&mut self, c: Constraint) -> Result<(), InferError> {
macro_rules! constraint {
($t1:expr, $t2:expr) => {
Constraint::new($t1, $t2, c.span)
};
}
use Type::*;
match (c.t1.clone(), c.t2.clone()) {
// Literal types
(Unit, Unit)
| (Bool, Bool)
| (Int, Int)
| (Str, Str) => Ok(()),
// Variable
(Var(i), Var(j)) if i == j => Ok(()), // Same variables can be unified
(Var(i), t2) => {
// If the substitution is not the variable itself,
// unify the substitution with t2
if let Some(t) = self.subst(i) {
if t != Var(i) {
return self.unify(constraint!(t, t2));
}
}
// If the variable occurs in t2
if self.occurs(i, t2.clone()) {
return Err(InferError::new("Infinite type", c.span)
.add_error(format!(
"This type contains itself: {}", rename_type(Var(i))
), c.span));
}
// Set the substitution
self.subst[i] = t2;
Ok(())
},
(t1, Var(i)) => {
if let Some(t) = self.subst(i) {
if t != Var(i) {
return self.unify(constraint!(t1, t));
}
}
if self.occurs(i, t1.clone()) {
return Err(InferError::new("Infinite type", c.span)
.add_error(format!(
"This type contains itself: {}",
rename_type(Var(i))
), c.span));
}
self.subst[i] = t1;
Ok(())
},
// Function
(Func(a1, r1), Func(a2, r2)) => {
// Check the number of arguments
if a1.len() != a2.len() {
let mut e = InferError::new("Argument length mismatch", c.span)
.add_error(format!(
"This function is expected to take {} arguments, found {}",
a2.len(), a1.len()
), c.span);
if a2.len() > a1.len() {
// Get the types of the needed arguments
let mut args = Vec::new();
for i in a1.len()..a2.len() {
args.push(self.substitute(a2[i].clone()).to_string());
}
e = e.add_hint(format!(
"Need arguments of type `{}` to call this function",
args.join(", ")
), c.span);
}
return Err(e);
}
// Unify the arguments
for (a1, a2) in a1.into_iter().zip(a2.into_iter()) {
self.unify(constraint!(a1, a2))?;
}
// Unify the return types
self.unify(constraint!(*r1, *r2))
},
// Tuple
(Tuple(t1), Tuple(t2)) => {
// Check the number of elements
if t1.len() != t2.len() {
return Err(InferError::new("Tuple length mismatch", c.span)
.add_error(format!(
"Expected {} elements, found {}",
t1.len(), t2.len()
), c.span));
}
// Unify the elements
for (t1, t2) in t1.into_iter().zip(t2.into_iter()) {
self.unify(constraint!(t1, t2))?;
}
Ok(())
},
// Array
(Array(t1), Array(t2)) => self.unify(constraint!(*t1, *t2)),
// The rest will be type mismatch
(t1, t2) => Err(InferError::new("Type mismatch", c.span)
.add_error(format!(
"Expected {}, found {}",
rename_type(t1), rename_type(t2)
), c.span)),
}
}
/// Solve the constraints by unifying them
fn solve(&mut self) -> Vec<InferError> {
let mut errors = Vec::new();
for c in self.constraints.clone().into_iter() {
if let Err(e) = self.unify(c) {
errors.push(e);
}
}
errors
}
/// Substitute the type variables with the substitutions
fn substitute(&mut self, t: Type) -> Type {
use Type::*;
match t {
// Only match any type that can contain type variables
Var(i) => {
if let Some(t) = self.subst(i) {
if t != Var(i) {
return self.substitute(t);
}
}
Var(i)
},
Func(args, ret) => {
Func(
args.into_iter().map(|t| self.substitute(t)).collect(),
Box::new(self.substitute(*ret)),
)
},
Tuple(tys) => Tuple(tys.into_iter().map(|t| self.substitute(t)).collect()),
Array(ty) => Array(Box::new(self.substitute(*ty))),
// The rest will be returned as is
_ => t,
}
}
/// Find a type variable in (typed) expression and substitute them
fn substitute_texp(&mut self, e: TExpr<'src>) -> TExpr<'src> {
use TExpr::*;
match e {
Lit(_) | Ident(_) => e,
Unary { op, expr: (e, lspan), ret_ty } => {
Unary {
op,
expr: (Box::new(self.substitute_texp(*e)), lspan),
ret_ty,
}
},
Binary { op, lhs: (lhs, lspan), rhs: (rhs, rspan), ret_ty } => {
let lhst = self.substitute_texp(*lhs);
let rhst = self.substitute_texp(*rhs);
Binary {
op,
lhs: (Box::new(lhst), lspan),
rhs: (Box::new(rhst), rspan),
ret_ty: self.substitute(ret_ty),
}
},
Lambda { params, body: (body, bspan), ret_ty } => {
let bodyt = self.substitute_texp(*body);
let paramst = params.into_iter()
.map(|(name, ty)| (name, self.substitute(ty)))
.collect::<Vec<_>>();
Lambda {
params: paramst,
body: (Box::new(bodyt), bspan),
ret_ty: self.substitute(ret_ty),
}
},
Call { func: (func, fspan), args } => {
let funct = self.substitute_texp(*func);
let argst = args.into_iter()
.map(|(arg, span)| (self.substitute_texp(arg), span))
.collect::<Vec<_>>();
Call {
func: (Box::new(funct), fspan),
args: argst,
}
},
If { cond: (cond, cspan), t: (t, tspan), f: (f, fspan), br_ty } => {
let condt = self.substitute_texp(*cond);
let tt = self.substitute_texp(*t);
let ft = self.substitute_texp(*f);
If {
cond: (Box::new(condt), cspan),
t: (Box::new(tt), tspan),
f: (Box::new(ft), fspan),
br_ty,
}
},
Let { name, ty, value: (v, vspan), body: (b, bspan) } => {
let vt = self.substitute_texp(*v);
let bt = self.substitute_texp(*b);
Let {
name,
ty: self.substitute(ty),
value: (Box::new(vt), vspan),
body: (Box::new(bt), bspan),
}
},
Define { name, ty, value: (v, vspan) } => {
let vt = self.substitute_texp(*v);
Define {
name,
ty: self.substitute(ty),
value: (Box::new(vt), vspan),
}
},
Block { exprs, void, ret_ty } => {
let exprst = exprs.into_iter()
.map(|(e, span)| (self.substitute_texp(e), span))
.collect::<Vec<_>>();
Block {
exprs: exprst,
void,
ret_ty: self.substitute(ret_ty),
}
},
}
}
/// Infer the type of an expression
fn infer(
&mut self, e: (Expr<'src>, SimpleSpan), expected: Type
) -> (TExpr<'src>, Vec<InferError>) {
let span = e.1;
macro_rules! constraint {
($ty:expr) => {
self.add_constraint(Constraint::new(expected, $ty, span))
};
}
match e.0 {
// Literal values
// Push the constraint (expected type to be the literal type) and
// return the typed expression
Expr::Lit(l) => match l {
Lit::Unit => {
constraint!(Type::Unit);
ok!(TExpr::Lit(Lit::Unit))
}
Lit::Bool(b) => {
constraint!(Type::Bool);
ok!(TExpr::Lit(Lit::Bool(b)))
}
Lit::Int(i) => {
constraint!(Type::Int);
ok!(TExpr::Lit(Lit::Int(i)))
}
Lit::Str(s) => {
constraint!(Type::Str);
ok!(TExpr::Lit(Lit::Str(s)))
}
}
// Identifiers
// The same as literals but the type is looked up in the environment
Expr::Ident(ref x) => {
if let Some(t) = self.env.get(x) {
constraint!(t.clone());
ok!(TExpr::Ident(x))
} else {
let kind = match &expected {
Type::Func(_, _) => "function",
_ => "value",
};
(TExpr::Ident(x), vec![
InferError::new(format!("Undefined {}", kind), span)
.add_error(format!("`{}` is not defined", x), span)
])
}
}
// Unary & binary operators
// The type of the left and right hand side are inferred and
// the expected type is determined by the operator
Expr::Unary(op, e) => match op {
// Numeric operators (Int -> Int)
UnaryOp::Neg => {
let (te, err) = self.infer(unbox!(e), Type::Int);
constraint!(Type::Int);
(TExpr::Unary {
op,
expr: (Box::new(te), span),
ret_ty: Type::Int,
}, err)
},
// Boolean operators (Bool -> Bool)
UnaryOp::Not => {
let (te, err) = self.infer(unbox!(e), Type::Bool);
constraint!(Type::Bool);
(TExpr::Unary {
op,
expr: (Box::new(te), span),
ret_ty: Type::Bool,
}, err)
},
}
Expr::Binary(op, lhs, rhs) => match op {
// Numeric operators (Int -> Int -> Int)
BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::Rem
=> {
let (lt, mut errs0) = self.infer(unbox!(lhs), Type::Int);
let (rt, errs1) = self.infer(unbox!(rhs), Type::Int);
errs0.extend(errs1);
constraint!(Type::Int);
(TExpr::Binary {
op,
lhs: (Box::new(lt), lhs.1),
rhs: (Box::new(rt), rhs.1),
ret_ty: Type::Int,
}, errs0)
},
// Boolean operators (Bool -> Bool -> Bool)
BinaryOp::And
| BinaryOp::Or
=> {
let (lt, mut errs0) = self.infer(unbox!(lhs), Type::Bool);
let (rt, errs1) = self.infer(unbox!(rhs), Type::Bool);
errs0.extend(errs1);
constraint!(Type::Bool);
(TExpr::Binary {
op,
lhs: (Box::new(lt), lhs.1),
rhs: (Box::new(rt), rhs.1),
ret_ty: Type::Bool,
}, errs0)
},
// Comparison operators ('a -> 'a -> Bool)
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Le
| BinaryOp::Gt
| BinaryOp::Ge
=> {
// Create a fresh type variable and then use it as the
// expected type for both the left and right hand side
// so the type on both side have to be the same
let t = self.fresh();
let (lt, mut errs0) = self.infer(unbox!(lhs), t.clone());
let (rt, errs1) = self.infer(unbox!(rhs), t);
errs0.extend(errs1);
constraint!(Type::Bool);
(TExpr::Binary {
op,
lhs: (Box::new(lt), lhs.1),
rhs: (Box::new(rt), rhs.1),
ret_ty: Type::Bool,
}, errs0)
},
BinaryOp::Pipe => {
// Since this is parsed with a fold left, the right hand
// side should always be a function
let t = self.fresh();
let (lt, mut errs0) = self.infer(unbox!(lhs), t.clone());
// The right hand side should be a function that takes
// 1 argument with the type of t
let (rt, errs1) = self.infer(
unbox!(rhs),
Type::Func(vec![t.clone()], Box::new(t.clone())),
);
errs0.extend(errs1);
constraint!(t.clone());
(TExpr::Binary {
op,
lhs: (Box::new(lt), lhs.1),
rhs: (Box::new(rt), rhs.1),
ret_ty: t,
}, errs0)
},
}
// Lambda
Expr::Lambda(args, ret, b) => {
// Get the return type or create a fresh type variable
let rt = ret.unwrap_or(self.fresh());
// Fill in the type of the arguments with a fresh type
let xs = args.into_iter()
.map(|(x, t)| (x, t.unwrap_or(self.fresh())))
.collect::<Vec<_>>();
// Create a new environment, and add the arguments to it
// and use the new environment to infer the body
let mut env = self.env.clone();
xs.clone().into_iter().for_each(|(x, t)| { env.insert(x, t); });
let mut inf = self.clone();
inf.env = env;
let (bt, errs) = inf.infer(unbox!(b), rt.clone());
// Add the substitutions & constraints from the body
// if it doesn't already exist
for s in inf.subst {
if !self.subst.contains(&s) {
self.subst.push(s);
}
}
for c in inf.constraints {
if !self.constraints.contains(&c) {
self.constraints.push(c);
}
}
// Push the constraints
constraint!(Type::Func(
xs.clone().into_iter()
.map(|x| x.1)
.collect(),
Box::new(rt.clone()),
));
(TExpr::Lambda {
params: xs,
body: (Box::new(bt), b.1),
ret_ty: rt,
}, errs)
},
// Call
Expr::Call(f, args) => {
// Generate fresh types for the arguments
let freshes = args.clone().into_iter()
.map(|_| self.fresh())
.collect::<Vec<Type>>();
// Create a function type
let fsig = Type::Func(
freshes.clone(),
Box::new(expected),
);
// Expect the function to have the function type
let (ft, mut errs) = self.infer(unbox!(f), fsig);
// Infer the arguments
let (xs, xerrs) = args.into_iter()
.zip(freshes.into_iter())
.map(|(x, t)| {
let span = x.1;
let (xt, err) = self.infer(x, t);
((xt, span), err)
})
// Flatten errors
.fold((vec![], vec![]), |(mut xs, mut errs), ((x, span), err)| {
xs.push((x, span));
errs.extend(err);
(xs, errs)
});
errs.extend(xerrs);
(TExpr::Call {
func: (Box::new(ft), f.1),
args: xs,
}, errs)
},
// If
Expr::If { cond, t, f } => {
// Condition has to be a boolean
let (ct, mut errs) = self.infer(unbox!(cond), Type::Bool);
// The type of the if expression is the same as the
// expected type
let (tt, terrs) = self.infer(unbox!(t), expected.clone());
let (ft, ferrs) = self.infer(unbox!(f), expected.clone());
errs.extend(terrs);
errs.extend(ferrs);
(TExpr::If {
cond: (Box::new(ct), cond.1),
t: (Box::new(tt), t.1),
f: (Box::new(ft), f.1),
br_ty: expected,
}, errs)
},
// Let & define
Expr::Let { name, ty, value, body } => {
// Infer the type of the value
let ty = ty.unwrap_or(self.fresh());
let (vt, mut errs) = self.infer(unbox!(value), ty.clone());
// Create a new environment and add the binding to it
// and then use the new environment to infer the body
let mut env = self.env.clone();
env.insert(name.clone(), ty.clone());
let mut inf = Infer::new();
inf.env = env;
let (bt, berrs) = inf.infer(unbox!(body), expected.clone());
errs.extend(berrs);
for s in inf.subst {
if !self.subst.contains(&s) {
self.subst.push(s);
}
}
for c in inf.constraints {
if !self.constraints.contains(&c) {
self.constraints.push(c);
}
}
(TExpr::Let {
name, ty,
value: (Box::new(vt), value.1),
body: (Box::new(bt), body.1),
}, errs)
},
Expr::Define { name, ty, value } => {
let ty = ty.unwrap_or(self.fresh());
self.env.insert(name.clone(), ty.clone());
let (val_ty, errs) = self.infer(unbox!(value), ty.clone());
constraint!(Type::Unit);
(TExpr::Define {
name,
ty,
value: (Box::new(val_ty), value.1),
}, errs)
},
// Block
Expr::Block { exprs, void } => {
// Infer the type of each expression
let mut last = None;
let len = exprs.len();
let (texprs, errs) = exprs.into_iter()
.enumerate()
.map(|(i, x)| {
let span = x.1;
let t = self.fresh();
let (xt, err) = self.infer(unbox!(x), t.clone());
// Save the type of the last expression
if i == len - 1 {
last = Some(t);
}
((xt, span), err)
})
.fold((vec![], vec![]), |(mut xs, mut errs), ((x, span), err)| {
xs.push((x, span));
errs.extend(err);
(xs, errs)
});
let rt = if void || last.is_none() {
// If the block is void or there is no expression,
// the return type is unit
constraint!(Type::Unit);
Type::Unit
} else {
// Otherwise, the return type is the same as the expected type
// constraint!(last.unwrap());
self.add_constraint(Constraint::new(expected.clone(), last.unwrap(), span));
expected
};
(TExpr::Block {
exprs: texprs,
void,
ret_ty: rt,
}, errs)
},
}
}
}
/// Infer a list of expressions
pub fn infer_exprs(es: Vec<(Expr, SimpleSpan)>) -> (Vec<(TExpr, SimpleSpan)>, Vec<InferError>) {
let mut inf = Infer::new();
// Type expressions
let mut tes = vec![];
// Unsubstituted typed expressions
let mut errors = vec![];
for e in es {
let span = e.1;
let fresh = inf.fresh();
// Infer the types
let (te, err) = inf.infer(e, fresh);
// Push the expression to the list
tes.push((te.clone(), span));
if !err.is_empty() {
errors.extend(err);
}
}
let solve_errors = inf.solve();
if !solve_errors.is_empty() {
errors.extend(solve_errors);
}
(rename_exprs(tes), errors)
}

3
typing/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod infer;
pub mod rename;
pub mod typed;

185
typing/src/rename.rs Normal file
View file

@ -0,0 +1,185 @@
use chumsky::span::SimpleSpan;
use syntax::ty::Type;
use crate::typed::TExpr;
/// A renamer to rename type variables to a "minimized" form for more readable output
pub struct Renamer {
// Type variables encountered so far
vars: Vec<usize>,
}
impl<'src> Renamer {
pub fn new() -> Self {
Self {
vars: vec![],
}
}
fn rename_var(&self, i: usize) -> Type {
let n = self.vars.iter().position(|x| x == &i).unwrap();
Type::Var(n)
}
fn add_var(&mut self, i: usize) {
if !self.vars.contains(&i) {
self.vars.push(i);
}
}
fn find_var(&mut self, t: Type) {
match t {
Type::Var(i) => {
self.add_var(i);
},
Type::Func(args, ret) => {
args.into_iter().for_each(|t| self.find_var(t));
self.find_var(*ret);
},
Type::Tuple(tys) => {
tys.into_iter().for_each(|t| self.find_var(t));
},
Type::Array(ty) => {
self.find_var(*ty);
},
_ => {},
}
}
fn traverse(&mut self, e: TExpr) {
match e {
TExpr::Unary { expr, ret_ty, ..} => {
self.traverse(*expr.0);
self.find_var(ret_ty);
},
TExpr::Binary { lhs, rhs, ret_ty, ..} => {
self.traverse(*lhs.0);
self.traverse(*rhs.0);
self.find_var(ret_ty);
},
TExpr::Lambda { params, body, ret_ty } => {
for (_, t) in params { self.find_var(t); }
self.find_var(ret_ty);
self.traverse(*body.0);
},
TExpr::Call { func, args } => {
self.traverse(*func.0);
for arg in args {
self.traverse(arg.0);
}
},
TExpr::Let { ty, value, body, .. } => {
self.find_var(ty);
self.traverse(*value.0);
self.traverse(*body.0);
},
TExpr::Define { ty, value, .. } => {
self.find_var(ty);
self.traverse(*value.0);
},
TExpr::Block { exprs, ret_ty, .. } => {
for expr in exprs {
self.traverse(expr.0);
}
self.find_var(ret_ty);
},
_ => {},
}
}
fn rename_type(&self, t: Type) -> Type {
match t {
Type::Var(i) => self.rename_var(i),
Type::Func(args, ret) => {
Type::Func(
args.into_iter().map(|x| self.rename_type(x)).collect(),
Box::new(self.rename_type(*ret)),
)
},
Type::Tuple(tys) => {
Type::Tuple(tys.into_iter().map(|x| self.rename_type(x)).collect())
},
Type::Array(ty) => {
Type::Array(Box::new(self.rename_type(*ty)))
},
_ => t,
}
}
fn rename_texp(&self, e: TExpr<'src>) -> TExpr<'src> {
match e {
TExpr::Unary { op, expr, ret_ty } => {
TExpr::Unary {
op,
expr: (Box::new(self.rename_texp(*expr.0)), expr.1),
ret_ty: self.rename_type(ret_ty)
}
},
TExpr::Binary { op, lhs, rhs, ret_ty } => {
TExpr::Binary {
op,
lhs: (Box::new(self.rename_texp(*lhs.0)), lhs.1),
rhs: (Box::new(self.rename_texp(*rhs.0)), rhs.1),
ret_ty: self.rename_type(ret_ty)
}
},
TExpr::Lambda { params, body, ret_ty } => {
TExpr::Lambda {
params: params.into_iter()
.map(|(x, t)| (x, self.rename_type(t)))
.collect(),
body: (Box::new(self.rename_texp(*body.0)), body.1),
ret_ty: self.rename_type(ret_ty)
}
},
TExpr::Call { func, args } => {
TExpr::Call {
func: (Box::new(self.rename_texp(*func.0)), func.1),
args: args.into_iter()
.map(|x| (self.rename_texp(x.0), x.1))
.collect()
}
},
TExpr::Let { name, ty, value, body } => {
TExpr::Let {
name,
ty: self.rename_type(ty),
value: (Box::new(self.rename_texp(*value.0)), value.1),
body: (Box::new(self.rename_texp(*body.0)), body.1)
}
},
TExpr::Define { name, ty, value } => {
TExpr::Define {
name,
ty: self.rename_type(ty),
value: (Box::new(self.rename_texp(*value.0)), value.1)
}
},
TExpr::Block { exprs, void, ret_ty } => {
TExpr::Block {
exprs: exprs.into_iter()
.map(|x| (self.rename_texp(x.0), x.1))
.collect(),
void,
ret_ty: self.rename_type(ret_ty)
}
},
_ => e,
}
}
}
pub fn rename_type(t: Type) -> Type {
let mut renamer = Renamer::new();
renamer.find_var(t.clone());
renamer.rename_type(t)
}
pub fn rename_exprs(es: Vec<(TExpr, SimpleSpan)>) -> Vec<(TExpr, SimpleSpan)> {
let mut renamer = Renamer::new();
es.clone().into_iter()
.for_each(|e| renamer.traverse(e.0));
es.into_iter()
.map(|(e, s)| (renamer.rename_texp(e), s))
.collect()
}

60
typing/src/typed.rs Normal file
View file

@ -0,0 +1,60 @@
use syntax::{
expr::{
BinaryOp,
UnaryOp,
Lit,
Spanned,
},
ty::Type,
};
// Typed version of the expression.
#[derive(Clone, Debug)]
pub enum TExpr<'src> {
Lit(Lit<'src>),
Ident(&'src str),
Unary {
op: UnaryOp,
expr: Spanned<Box<Self>>,
ret_ty: Type,
},
Binary {
op: BinaryOp,
lhs: Spanned<Box<Self>>,
rhs: Spanned<Box<Self>>,
ret_ty: Type,
},
Lambda {
params: Vec<(&'src str, Type)>,
body: Spanned<Box<Self>>,
ret_ty: Type,
},
Call {
func: Spanned<Box<Self>>,
args: Vec<Spanned<Self>>,
},
If {
cond: Spanned<Box<Self>>,
t: Spanned<Box<Self>>,
f: Spanned<Box<Self>>,
br_ty: Type,
},
Let {
name: &'src str,
ty: Type,
value: Spanned<Box<Self>>,
body: Spanned<Box<Self>>,
},
Define {
name: &'src str,
ty: Type,
value: Spanned<Box<Self>>,
},
Block {
exprs: Vec<Spanned<Self>>,
void: bool,
ret_ty: Type,
},
}