mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
Compare commits
No commits in common. "d8fbc17a33dc697402653fa0ed95735cb143a9be" and "a5f5725cdffe2ffdc5720f7ef6fbd130f6e5453d" have entirely different histories.
d8fbc17a33
...
a5f5725cdf
317
Cargo.lock
generated
317
Cargo.lock
generated
|
@ -11,6 +11,38 @@ dependencies = [
|
||||||
"const-random",
|
"const-random",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ariadne"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
|
||||||
|
dependencies = [
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -26,11 +58,41 @@ dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"indexmap",
|
||||||
|
"lazy_static",
|
||||||
|
"os_str_bytes",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "3.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codegen"
|
name = "codegen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"syntax",
|
"hir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -61,11 +123,22 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diagnostic"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
|
"chumsky",
|
||||||
|
"hir",
|
||||||
|
"lexer",
|
||||||
|
"typecheck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.6"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -73,11 +146,57 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hzc"
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hazure"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"codegen",
|
||||||
|
"diagnostic",
|
||||||
|
"hir",
|
||||||
|
"lexer",
|
||||||
|
"parser",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"toml",
|
||||||
|
"typecheck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hir"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"codegen",
|
"parser",
|
||||||
"syntax",
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -86,11 +205,65 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lexer"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chumsky",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.124"
|
version = "0.2.119"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
|
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chumsky",
|
||||||
|
"lexer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
|
@ -99,12 +272,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syntax"
|
name = "proc-macro2"
|
||||||
version = "0.1.0"
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chumsky",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-keccak"
|
name = "tiny-keccak"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
@ -115,14 +348,72 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "transformer"
|
name = "toml"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typecheck"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"syntax",
|
"hir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -1,7 +1,10 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"core",
|
"crates/main",
|
||||||
"syntax",
|
"crates/lexer",
|
||||||
"transformer",
|
"crates/parser",
|
||||||
"codegen",
|
"crates/diagnostic",
|
||||||
]
|
"crates/hir",
|
||||||
|
"crates/typecheck",
|
||||||
|
"crates/codegen",
|
||||||
|
]
|
||||||
|
|
23
README.md
23
README.md
|
@ -1,25 +1,34 @@
|
||||||
# Hazure
|
# Hazure
|
||||||
Programming language that compiles to Typescript!
|
Programming language that compiles to Typescript!
|
||||||
|
|
||||||
|
```sml
|
||||||
|
fun main : void = do
|
||||||
|
@write("Hello, World!")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
or with the pipeline operator:
|
||||||
|
```sml
|
||||||
|
fun main : void = do
|
||||||
|
"Hello, World!\n"
|
||||||
|
|> @write(_)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
Note: Everything in this project can be changed at anytime! (I'm still finding out what work best for lots of thing) if you have an idea, feel free to create an issues about it, or even create a PR! (I'd be very happy)
|
Note: Everything in this project can be changed at anytime! (I'm still finding out what work best for lots of thing) if you have an idea, feel free to create an issues about it, or even create a PR! (I'd be very happy)
|
||||||
|
|
||||||
# Prerequistie
|
# Prerequistie
|
||||||
- `node`/`deno` for running Typescript
|
- `deno` for running Typescript
|
||||||
- Rust (if you're going to build from source)
|
- Rust (if you're going to build from source)
|
||||||
- (Optional) if you use Vim, you can get the syntax highlighting [here](https://github.com/azur1s/hazure.vim)
|
- (Optional) if you use Vim, you can get the syntax highlighting [here](https://github.com/azur1s/hazure.vim)
|
||||||
|
|
||||||
# Installing
|
# Installing
|
||||||
Currently there is only a build script on linux:
|
Currently there is only a build script on linux:
|
||||||
```
|
```
|
||||||
$ curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s
|
curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s
|
||||||
...
|
|
||||||
$ hzc --help
|
|
||||||
```
|
```
|
||||||
or if you want to build in debug mode:
|
or if you want to build in debug mode:
|
||||||
```
|
```
|
||||||
curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s d
|
curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s d
|
||||||
...
|
|
||||||
$ hzc --help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
@ -28,7 +37,7 @@ Found a bug? Found a better way to do something? Make a pull request or tell me
|
||||||
Steps to build:
|
Steps to build:
|
||||||
1) Clone this repo `https://github.com/azur1s/hazure.git`
|
1) Clone this repo `https://github.com/azur1s/hazure.git`
|
||||||
2) Build executable `cargo build`
|
2) Build executable `cargo build`
|
||||||
3) Try running some examples! `hzc compile path/to/file.hz`
|
3) Try running some examples! `path/to/executable compile path/to/file.hz`
|
||||||
|
|
||||||
# License
|
# License
|
||||||
Hazure is licensed under both [MIT license](https://github.com/azur1s/hazure/blob/master/LICENSE-MIT) and [Apache License](https://github.com/azur1s/hazure/blob/master/LICENSE-APACHE)
|
Hazure is licensed under both [MIT license](https://github.com/azur1s/hazure/blob/master/LICENSE-MIT) and [Apache License](https://github.com/azur1s/hazure/blob/master/LICENSE-APACHE)
|
||||||
|
|
10
build.sh
10
build.sh
|
@ -44,13 +44,13 @@ cd ~/.cache/hazure/build/
|
||||||
if [[ $1 == *"d"* ]]; then
|
if [[ $1 == *"d"* ]]; then
|
||||||
log "Building in debug..."
|
log "Building in debug..."
|
||||||
cargo build
|
cargo build
|
||||||
rm ~/bin/hzc -f
|
rm ~/bin/hazure -f
|
||||||
mv ~/.cache/hazure/build/target/debug/hzc ~/bin/hzc
|
mv ~/.cache/hazure/build/target/debug/hazure ~/bin/hazure
|
||||||
else
|
else
|
||||||
log "Building..."
|
log "Building..."
|
||||||
cargo build --release
|
cargo build --release
|
||||||
rm ~/bin/hzc -f
|
rm ~/bin/hazure -f
|
||||||
mv ~/.cache/hazure/build/target/release/hzc ~/bin/hzc
|
mv ~/.cache/hazure/build/target/release/hazure ~/bin/hazure
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Build done. Cleaning up..."
|
log "Build done. Cleaning up..."
|
||||||
|
@ -58,4 +58,4 @@ log "Build done. Cleaning up..."
|
||||||
rm -rf ~/.cache/hazure/build/
|
rm -rf ~/.cache/hazure/build/
|
||||||
|
|
||||||
log "Done."
|
log "Done."
|
||||||
hzc -v
|
hazure -h
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
use syntax::{ast::*, lex::Span};
|
|
||||||
|
|
||||||
/// A struct that contains emitted code.
|
|
||||||
pub struct Codegen {
|
|
||||||
/// The emitted code.
|
|
||||||
/// When the codegen is done, this will be joined into a single string
|
|
||||||
pub emitted: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Codegen { fn default() -> Self { Self::new() } }
|
|
||||||
|
|
||||||
impl Codegen {
|
|
||||||
pub fn new() -> Codegen {
|
|
||||||
Codegen { emitted: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emit a string to the output.
|
|
||||||
pub fn emit<S: Into<String>>(&mut self, s: S) {
|
|
||||||
self.emitted.push(s.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen(&mut self, ast: Vec<(Expr, Span)>) {
|
|
||||||
for (expr, _) in ast {
|
|
||||||
self.emit(self.gen_expr(&expr, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_expr(&self, expr: &Expr, semicolon: bool) -> String {
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! semicolon { () => { if semicolon { ";" } else { "" } }; }
|
|
||||||
|
|
||||||
match expr {
|
|
||||||
Expr::Literal(lit) => self.gen_literal(&lit.0),
|
|
||||||
Expr::Identifier(name) => { format!("_{}{}", name.0, semicolon!()) },
|
|
||||||
Expr::Tuple(elems) => { format!("({}{})", elems.iter().map(|e| self.gen_expr(&e.0, false)).collect::<Vec<_>>().join(", "), semicolon!()) },
|
|
||||||
Expr::Vector(elems) => { format!("[{}{}]", elems.iter().map(|e| self.gen_expr(&e.0, false)).collect::<Vec<_>>().join(", "), semicolon!()) },
|
|
||||||
Expr::Object { fields } => {
|
|
||||||
format!("{{{}}}",
|
|
||||||
fields.iter().map(|(name, expr)| format!("{}: {}", name.0, self.gen_expr(&expr.0, false))).collect::<Vec<_>>().join(",\n "))
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Unary { op, rhs } => { format!("{}{}", op, self.gen_expr(&rhs.0, false)) },
|
|
||||||
Expr::Binary { op, lhs, rhs } => {
|
|
||||||
format!("{}{}{}{}", self.gen_expr(&lhs.0, false), op, self.gen_expr(&rhs.0, false), semicolon!())
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Call { name, args } => {
|
|
||||||
format!(
|
|
||||||
"{}({}){}",
|
|
||||||
self.gen_expr(&name.0, false),
|
|
||||||
args
|
|
||||||
.iter()
|
|
||||||
.map(|arg| self.gen_expr(&arg.0, false))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
semicolon!())
|
|
||||||
},
|
|
||||||
Expr::Method { obj, name, args } => {
|
|
||||||
format!(
|
|
||||||
"{}.{}({}){}",
|
|
||||||
self.gen_expr(&obj.0, false),
|
|
||||||
self.gen_expr(&name.0, false).trim_start_matches('_'),
|
|
||||||
args
|
|
||||||
.iter()
|
|
||||||
.map(|arg| self.gen_expr(&arg.0, false))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
semicolon!())
|
|
||||||
},
|
|
||||||
Expr::Access { obj, name } => {
|
|
||||||
format!("{}.{}", self.gen_expr(&obj.0, false), self.gen_expr(&name.0, false).trim_start_matches('_'))
|
|
||||||
},
|
|
||||||
Expr::Intrinsic { name, args } => {
|
|
||||||
if let Expr::Identifier(name) = &name.0 {
|
|
||||||
match name.0.as_str() {
|
|
||||||
"write" => { format!("console.log({})", args.iter().map(|arg| self.gen_expr(&arg.0, false)).collect::<Vec<_>>().join(", ")) },
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Expected identifier for intrinsic name");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Define { name, typehint, value } => {
|
|
||||||
format!(
|
|
||||||
"let _{} : {} = {}{}",
|
|
||||||
name.0,
|
|
||||||
self.gen_typehint(&typehint.0),
|
|
||||||
self.gen_expr(&value.0, false),
|
|
||||||
semicolon!())
|
|
||||||
},
|
|
||||||
Expr::Redefine { name, value } => {
|
|
||||||
format!(
|
|
||||||
"_{} = {}{}",
|
|
||||||
name.0,
|
|
||||||
self.gen_expr(&value.0, false),
|
|
||||||
semicolon!())
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Function { name, generics, args, typehint, body } => {
|
|
||||||
format!(
|
|
||||||
"const _{} = {}({}): {} => {{{}}}{}\n",
|
|
||||||
name.0,
|
|
||||||
if generics.is_empty() { "".to_string() } else {
|
|
||||||
format!("<{}>",
|
|
||||||
generics.iter().map(|g| g.0.clone()).collect::<Vec<_>>().join(", "))
|
|
||||||
},
|
|
||||||
args
|
|
||||||
.iter()
|
|
||||||
.map(|arg| format!("_{}: {}", arg.0.0, self.gen_typehint(&arg.1.0)))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
self.gen_typehint(&typehint.0),
|
|
||||||
self.gen_expr(&body.0, false),
|
|
||||||
semicolon!())
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::If { cond, t, f } => {
|
|
||||||
format!(
|
|
||||||
"if ({}) {{{}}} else {{{}}}",
|
|
||||||
self.gen_expr(&cond.0, false),
|
|
||||||
self.gen_expr(&t.0, false),
|
|
||||||
self.gen_expr(&f.0, false))
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Do { body } => {
|
|
||||||
format!(
|
|
||||||
"{{\n{}}}\n",
|
|
||||||
body.0.iter().map(|e| self.gen_expr(&e.0, false)).collect::<Vec<_>>().join("\n"))
|
|
||||||
},
|
|
||||||
|
|
||||||
Expr::Return(expr) => {
|
|
||||||
format!("return {}\n", self.gen_expr(&expr.0, true))
|
|
||||||
},
|
|
||||||
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => { dbg!(expr); todo!() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_literal(&self, lit: &Literal) -> String {
|
|
||||||
match lit {
|
|
||||||
Literal::Int(i) => format!("{}", i),
|
|
||||||
Literal::String(s) => format!("\"{}\"", s),
|
|
||||||
Literal::Boolean(b) => format!("{}", b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_typehint(&self, typehint: &Typehint) -> String {
|
|
||||||
match typehint {
|
|
||||||
Typehint::Builtin(ty) => {
|
|
||||||
match ty {
|
|
||||||
BuiltinType::Any => "any",
|
|
||||||
BuiltinType::Null => "null",
|
|
||||||
BuiltinType::Undefined => "undefined",
|
|
||||||
BuiltinType::Boolean => "boolean",
|
|
||||||
BuiltinType::Int => "number",
|
|
||||||
BuiltinType::String => "string",
|
|
||||||
}.to_string()
|
|
||||||
},
|
|
||||||
Typehint::Single(ty) => ty.clone(),
|
|
||||||
|
|
||||||
Typehint::Tuple(tys) => format!("[{}]", tys
|
|
||||||
.iter()
|
|
||||||
.map(|ty| self.gen_typehint(&ty.0)).collect::<Vec<_>>().join(", ")),
|
|
||||||
Typehint::Vector(ty) => format!("{}[]", self.gen_typehint(&ty.0)),
|
|
||||||
|
|
||||||
Typehint::Function(args, ret) => {
|
|
||||||
let args_ty = args.iter().map(|arg| self.gen_typehint(&arg.0)).collect::<Vec<_>>();
|
|
||||||
let return_ty = self.gen_typehint(&ret.0);
|
|
||||||
format!( "({}) => {}",
|
|
||||||
args_ty
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, arg)| format!("__{}: {}", i, arg)) // Maybe use this in the future
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
return_ty)
|
|
||||||
},
|
|
||||||
|
|
||||||
Typehint::Union(tys) => tys
|
|
||||||
.iter()
|
|
||||||
.map(|ty| self.gen_typehint(&ty.0)).collect::<Vec<_>>().join(" | "),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "hzc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syntax = { path = "../syntax" }
|
|
||||||
codegen = { path = "../codegen" }
|
|
|
@ -1,40 +0,0 @@
|
||||||
use std::{fs::File, io::Write};
|
|
||||||
|
|
||||||
use syntax::{lex::lex, parse::parse};
|
|
||||||
use codegen::Codegen;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let path = std::env::args().nth(1).expect("No file specified");
|
|
||||||
let input = std::fs::read_to_string(path).expect("Failed to read file");
|
|
||||||
|
|
||||||
let time = std::time::Instant::now();
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lex
|
|
||||||
//
|
|
||||||
let (tokens, lex_errs) = lex(input.to_string());
|
|
||||||
|
|
||||||
if !lex_errs.is_empty() {
|
|
||||||
println!("Lex error(s): {:#?}", lex_errs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Parse
|
|
||||||
//
|
|
||||||
let (ast, parse_errs) = parse(tokens.unwrap(), input.chars().count());
|
|
||||||
|
|
||||||
if !parse_errs.is_empty() || ast.is_none() {
|
|
||||||
println!("Parse error(s): {:#?}", parse_errs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Codegen
|
|
||||||
//
|
|
||||||
let mut codegen = Codegen::new();
|
|
||||||
codegen.gen(ast.unwrap());
|
|
||||||
|
|
||||||
let mut file = File::create("out.ts").unwrap();
|
|
||||||
file.write_all(codegen.emitted.join("\n").as_bytes()).unwrap();
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "codegen"
|
name = "codegen"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syntax = { path = "../syntax" }
|
hir = { path = "../hir" }
|
1
crates/codegen/src/lib.rs
Normal file
1
crates/codegen/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod ts;
|
186
crates/codegen/src/ts.rs
Normal file
186
crates/codegen/src/ts.rs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use hir::{IR, IRKind, Value};
|
||||||
|
|
||||||
|
pub struct Codegen {
|
||||||
|
pub emitted: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Codegen {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Codegen {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { emitted: String::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit<T: Display>(&mut self, t: T) {
|
||||||
|
self.emitted.push_str(&t.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen(&mut self, irs: Vec<IR>) {
|
||||||
|
self.emit(format!("// Auto-generated by hazure compiler version {}\n", env!("CARGO_PKG_VERSION")));
|
||||||
|
self.emit("import { read, write, readFile, writeFile } from \"https://raw.githubusercontent.com/azur1s/hazure/master/runtime/io.ts\"\n");
|
||||||
|
|
||||||
|
for ir in irs {
|
||||||
|
self.emit(&self.gen_ir(&ir.kind, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit("_main();");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_ir(&self, ir: &IRKind, should_gen_semicolon: bool) -> String {
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! semicolon { () => { if should_gen_semicolon { ";" } else { "" } }; }
|
||||||
|
|
||||||
|
match ir {
|
||||||
|
IRKind::Define { public, name, type_hint, value, mutable, .. } => {
|
||||||
|
format!(
|
||||||
|
"{} {} _{}: {} = {}{}\n",
|
||||||
|
if *public { "export" } else { "" },
|
||||||
|
if *mutable { "let" } else { "const" },
|
||||||
|
name,
|
||||||
|
type_hint,
|
||||||
|
self.gen_ir(value, false),
|
||||||
|
semicolon!()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Call { name, args, .. } => {
|
||||||
|
format!(
|
||||||
|
"_{}({}){}",
|
||||||
|
name,
|
||||||
|
args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| self.gen_ir(arg, false))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
.trim_end_matches(";\n"),
|
||||||
|
semicolon!(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Intrinsic { name, args, .. } => {
|
||||||
|
match name.as_str() {
|
||||||
|
"write" => { format!("write({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) },
|
||||||
|
"write_file" => { format!("writeFile({}, {}){}\n", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false), semicolon!()) },
|
||||||
|
"read" => { format!("read({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) },
|
||||||
|
"read_file" => { format!("readFile({}){}\n" , self.gen_ir(&args[0], false), semicolon!()) },
|
||||||
|
"emit" => { self.gen_ir(&args[0], false).trim_start_matches('"').trim_end_matches('"').to_string() },
|
||||||
|
|
||||||
|
"get" => { format!("{}[{}]", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false)) },
|
||||||
|
"len" => { format!("{}.length", self.gen_ir(&args[0], false)) },
|
||||||
|
"insert" => { format!("{}.push({})", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false)) },
|
||||||
|
"concat" => { format!("{}.concat({})", self.gen_ir(&args[0], false), self.gen_ir(&args[1], false)) },
|
||||||
|
|
||||||
|
"throw" => { format!("throw new Error({}){}", self.gen_ir(&args[0], false), semicolon!()) },
|
||||||
|
_ => unreachable!("{}", format!("Unknown intrinsic: {}", name)) // Shoul be handled by lowering
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Fun { public, name, return_type_hint, args, body, .. } => {
|
||||||
|
let args = args
|
||||||
|
.iter()
|
||||||
|
.map(|arg| format!("_{}: {}", arg.0, arg.1))
|
||||||
|
.collect::<Vec<_>>().
|
||||||
|
join(", ");
|
||||||
|
format!(
|
||||||
|
"{} const _{} = ({}): {} => {{{}}};\n",
|
||||||
|
if *public { "export" } else { "" },
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
return_type_hint,
|
||||||
|
self.gen_ir(body, false)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Return { value, .. } => {
|
||||||
|
format!(
|
||||||
|
"return {};\n",
|
||||||
|
self.gen_ir(value, false)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Do { body, .. } => {
|
||||||
|
let mut out = "{\n".to_string();
|
||||||
|
for expr in body {
|
||||||
|
out.push_str(&self.gen_ir(expr, true));
|
||||||
|
}
|
||||||
|
out.push_str("}\n");
|
||||||
|
out
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::If { cond, body, else_body, .. } => {
|
||||||
|
format!(
|
||||||
|
"if ({}) {{\n{}}} else {{\n{}}}\n",
|
||||||
|
self.gen_ir(cond, true),
|
||||||
|
self.gen_ir(body, true),
|
||||||
|
self.gen_ir(else_body, true),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Case { cond, cases, default, .. } => {
|
||||||
|
format!(
|
||||||
|
"switch ({}) {{\n{}{}\n}}\n",
|
||||||
|
self.gen_ir(cond, true),
|
||||||
|
cases
|
||||||
|
.iter()
|
||||||
|
.map(|(pattern, body)| format!(
|
||||||
|
"case {}: {}\nbreak;\n",
|
||||||
|
self.gen_ir(pattern, true),
|
||||||
|
self.gen_ir(body, true)))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
|
format!(
|
||||||
|
"default: {}\nbreak;\n",
|
||||||
|
self.gen_ir(default, true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Unary { op, right, .. } => {
|
||||||
|
format!("{}{}", op, self.gen_ir(right, false))
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Binary { left, op, right, .. } => {
|
||||||
|
format!("{} {} {}", self.gen_ir(left, false), op, self.gen_ir(right, false))
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Value { value } => {
|
||||||
|
match value {
|
||||||
|
Value::Int(value) => format!("{}", value),
|
||||||
|
Value::Boolean(value) => format!("{}", value),
|
||||||
|
Value::String(value) => format!("\"{}\"", value),
|
||||||
|
Value::Ident(value) => format!("_{}", value),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
IRKind::Tuple { values } => {
|
||||||
|
format!(
|
||||||
|
"[{}]",
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.map(|value| self.gen_ir(value, false))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
IRKind::Vector { values } => {
|
||||||
|
format!(
|
||||||
|
"[{}]",
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.map(|value| self.gen_ir(value, false))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => { dbg!(ir); todo!() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
crates/diagnostic/Cargo.toml
Normal file
13
crates/diagnostic/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "diagnostic"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chumsky = "0.8.0"
|
||||||
|
ariadne = "0.1.5"
|
||||||
|
lexer = { path = "../lexer" }
|
||||||
|
hir = { path = "../hir" }
|
||||||
|
typecheck = { path = "../typecheck" }
|
180
crates/diagnostic/src/lib.rs
Normal file
180
crates/diagnostic/src/lib.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use lexer::Token;
|
||||||
|
use chumsky::prelude::Simple;
|
||||||
|
use ariadne::{Report, ReportKind, Label, Source, Color, Fmt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Diagnostics {
|
||||||
|
pub errors: Vec<Kind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Kind {
|
||||||
|
LexError(Simple<char>),
|
||||||
|
ParseError(Simple<Token>),
|
||||||
|
LoweringError(hir::LoweringError),
|
||||||
|
TypecheckError(typecheck::TypecheckError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Diagnostics {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Diagnostics {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
errors: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_error(&self) -> bool {
|
||||||
|
!self.errors.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_lex_error(&mut self, error: Simple<char>) {
|
||||||
|
self.errors.push(Kind::LexError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_parse_error(&mut self, error: Simple<Token>) {
|
||||||
|
self.errors.push(Kind::ParseError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_lowering_error(&mut self, error: hir::LoweringError) {
|
||||||
|
self.errors.push(Kind::LoweringError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_typecheck_error(&mut self, error: typecheck::TypecheckError) {
|
||||||
|
self.errors.push(Kind::TypecheckError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display(&self, src: String) {
|
||||||
|
let lex_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
|
Kind::LexError(error) => Some(error.clone()), // Using clone() to remove reference
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let parse_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
|
Kind::ParseError(error) => Some(error.clone()), // Same case as above
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
lex_error.into_iter()
|
||||||
|
.map(|e| e.map(|e| e.to_string()))
|
||||||
|
.chain(parse_error.into_iter().map(|e| e.map(|tok| tok.to_string())))
|
||||||
|
.for_each(|e| {
|
||||||
|
let report = Report::build(ReportKind::Error, (), e.span().start);
|
||||||
|
|
||||||
|
let report = match e.reason() {
|
||||||
|
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
|
||||||
|
.with_message(format!(
|
||||||
|
"Unclosed delimiter {}",
|
||||||
|
delimiter.fg(Color::Yellow)
|
||||||
|
))
|
||||||
|
.with_label(
|
||||||
|
Label::new(span.clone())
|
||||||
|
.with_message(format!(
|
||||||
|
"Expected closing delimiter {}",
|
||||||
|
delimiter.fg(Color::Yellow)
|
||||||
|
))
|
||||||
|
.with_color(Color::Yellow)
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
Label::new(e.span())
|
||||||
|
.with_message(format!(
|
||||||
|
"Must be closed before this {}",
|
||||||
|
e.found()
|
||||||
|
.unwrap_or(&"end of file".to_string())
|
||||||
|
.fg(Color::Red)
|
||||||
|
))
|
||||||
|
.with_color(Color::Red)
|
||||||
|
),
|
||||||
|
|
||||||
|
chumsky::error::SimpleReason::Unexpected => report
|
||||||
|
.with_message(format!(
|
||||||
|
"{}, expected {}",
|
||||||
|
|
||||||
|
if e.found().is_some() { "Unexpected token in input" }
|
||||||
|
else { "Unexpected end of input" },
|
||||||
|
|
||||||
|
if e.expected().len() == 0 { "something else".to_string().fg(Color::Green) }
|
||||||
|
else {
|
||||||
|
e.expected()
|
||||||
|
.map(|expected| match expected {
|
||||||
|
Some(expected) => expected.to_string(),
|
||||||
|
None => "end of input".to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
.fg(Color::Green)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.with_label(
|
||||||
|
Label::new(e.span())
|
||||||
|
.with_message(format!(
|
||||||
|
"Unexpected token {}",
|
||||||
|
e.found()
|
||||||
|
.unwrap_or(&"EOF".to_string())
|
||||||
|
.fg(Color::Red)
|
||||||
|
))
|
||||||
|
.with_color(Color::Red)
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
println!("{:?}", e);
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
report.finish().print(Source::from(&src)).unwrap();
|
||||||
|
}); // End errors reporting
|
||||||
|
|
||||||
|
let lower_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
|
Kind::LoweringError(error) => Some(error.clone()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let typecheck_error = self.errors.iter().filter_map(|kind| match kind {
|
||||||
|
Kind::TypecheckError(error) => Some(<&typecheck::TypecheckError>::clone(&error)),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
// TODO: so many .iter(), maybe collapse them into one?
|
||||||
|
|
||||||
|
lower_error.into_iter()
|
||||||
|
.for_each(|e| {
|
||||||
|
let span = &e.span;
|
||||||
|
let message = &e.message;
|
||||||
|
|
||||||
|
let report = Report::build(ReportKind::Error, (), span.start)
|
||||||
|
.with_message(
|
||||||
|
message.to_string()
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
Label::new(span.clone())
|
||||||
|
.with_message(
|
||||||
|
message.to_string()
|
||||||
|
)
|
||||||
|
.with_color(Color::Red)
|
||||||
|
);
|
||||||
|
|
||||||
|
report.finish().print(Source::from(&src)).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
typecheck_error.into_iter()
|
||||||
|
.for_each(|e| {
|
||||||
|
let span = &e.span;
|
||||||
|
let message = &e.kind;
|
||||||
|
|
||||||
|
let report = Report::build(ReportKind::Error, (), span.start)
|
||||||
|
.with_message(
|
||||||
|
format!("{}", message)
|
||||||
|
)
|
||||||
|
.with_label(
|
||||||
|
Label::new(span.clone())
|
||||||
|
.with_message(
|
||||||
|
format!("{}", message)
|
||||||
|
)
|
||||||
|
.with_color(Color::Red)
|
||||||
|
);
|
||||||
|
|
||||||
|
report.finish().print(Source::from(&src)).unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
10
crates/hir/Cargo.toml
Normal file
10
crates/hir/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "hir"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parser = { path = "../parser" }
|
480
crates/hir/src/lib.rs
Normal file
480
crates/hir/src/lib.rs
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
use parser::types::{Expr, Typehint};
|
||||||
|
|
||||||
|
const INTRINSICS: [&str; 10] = [
|
||||||
|
"write",
|
||||||
|
"read",
|
||||||
|
"write_file",
|
||||||
|
"read_file",
|
||||||
|
"emit",
|
||||||
|
"get",
|
||||||
|
"len",
|
||||||
|
"insert",
|
||||||
|
"concat",
|
||||||
|
"throw",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Value { Int(i64), Boolean(bool), String(String), Ident(String) }
|
||||||
|
|
||||||
|
impl std::fmt::Display for Value {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Int(i) => write!(f, "{}", i),
|
||||||
|
Value::Boolean(b) => write!(f, "{}", b),
|
||||||
|
Value::String(s) => write!(f, "\"{}\"", s),
|
||||||
|
Value::Ident(s) => write!(f, "{}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum IRKind {
|
||||||
|
Value { value: Value },
|
||||||
|
Vector { values: Vec<Self> },
|
||||||
|
Tuple { values: Vec<Self> },
|
||||||
|
|
||||||
|
Unary {
|
||||||
|
op: String,
|
||||||
|
right: Box<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Binary {
|
||||||
|
op: String,
|
||||||
|
left: Box<Self>,
|
||||||
|
right: Box<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
name: String,
|
||||||
|
args: Vec<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
Intrinsic {
|
||||||
|
name: String,
|
||||||
|
args: Vec<Self>,
|
||||||
|
span: Range<usize>
|
||||||
|
},
|
||||||
|
|
||||||
|
Define {
|
||||||
|
public: bool,
|
||||||
|
name: String,
|
||||||
|
type_hint: String,
|
||||||
|
value: Box<Self>,
|
||||||
|
mutable: bool,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
Fun {
|
||||||
|
public: bool,
|
||||||
|
name: String,
|
||||||
|
return_type_hint: String,
|
||||||
|
args: Vec<(String, String)>,
|
||||||
|
body: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
|
||||||
|
If {
|
||||||
|
cond: Box<Self>,
|
||||||
|
body: Box<Self>,
|
||||||
|
else_body: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
Case {
|
||||||
|
cond: Box<Self>,
|
||||||
|
cases: Vec<(Box<Self>, Box<Self>)>,
|
||||||
|
default: Box<Self>,
|
||||||
|
span: Range<usize>,
|
||||||
|
},
|
||||||
|
Do { body: Vec<Self>, span: Range<usize> },
|
||||||
|
|
||||||
|
Return { value: Box<Self>, span: Range<usize> },
|
||||||
|
// Error { message: String, note: Option<String>, span: Range<usize> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IR {
|
||||||
|
pub kind: IRKind,
|
||||||
|
pub span: Range<usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LoweringError {
|
||||||
|
pub span: Range<usize>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ast_to_ir(ast: Vec<(Expr, Range<usize>)>) -> (Vec<IR>, Vec<LoweringError>) {
|
||||||
|
let mut irs = Vec::new();
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for expr in ast {
|
||||||
|
let ir_kind = expr_to_ir(&expr.0);
|
||||||
|
match ir_kind {
|
||||||
|
(Some(ir), None) => {
|
||||||
|
irs.push(IR { kind: ir, span: expr.1 });
|
||||||
|
},
|
||||||
|
(None, Some(err)) => {
|
||||||
|
errors.push(err);
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(irs, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! if_err_return {
|
||||||
|
($value:expr) => {
|
||||||
|
if let Some(err) = $value { return (None, Some(err)); };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_ok {
|
||||||
|
($value:expr) => {
|
||||||
|
return (Some($value), None)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_err {
|
||||||
|
($value:expr) => {
|
||||||
|
return (None, Some($value))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
||||||
|
match expr {
|
||||||
|
Expr::Unary { op, rhs } => {
|
||||||
|
let rhs_ir = expr_to_ir(&rhs.0);
|
||||||
|
if_err_return!(rhs_ir.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Unary {
|
||||||
|
op: op.to_string(),
|
||||||
|
right: Box::new(rhs_ir.0.unwrap()),
|
||||||
|
span: rhs.1.clone()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Binary { lhs, op, rhs } => {
|
||||||
|
let lhs_ir = expr_to_ir(&lhs.0);
|
||||||
|
if_err_return!(lhs_ir.1);
|
||||||
|
|
||||||
|
let rhs_ir = expr_to_ir(&rhs.0);
|
||||||
|
if_err_return!(rhs_ir.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Binary {
|
||||||
|
op: op.to_string(),
|
||||||
|
left: Box::new(lhs_ir.0.unwrap()),
|
||||||
|
right: Box::new(rhs_ir.0.unwrap()),
|
||||||
|
span: lhs.1.start..rhs.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Call { name, args } => {
|
||||||
|
let lname = match &name.0 {
|
||||||
|
Expr::Identifier(s) => s.clone(),
|
||||||
|
// Should never happen because the parser should have caught this
|
||||||
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
|
};
|
||||||
|
let mut largs = Vec::new(); // `largs` stand for lowered args
|
||||||
|
// Iterate over args
|
||||||
|
for arg in &args.0 {
|
||||||
|
// Lower each argument, if there is an error then return early
|
||||||
|
let arg = expr_to_ir(&arg.0);
|
||||||
|
if_err_return!(arg.1);
|
||||||
|
largs.push(arg.0.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
return_ok!(IRKind::Call {
|
||||||
|
name: lname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Pipeline { lhs, rhs } => {
|
||||||
|
let lhs_ir = expr_to_ir(&lhs.0);
|
||||||
|
if_err_return!(lhs_ir.1);
|
||||||
|
|
||||||
|
match &rhs.0 {
|
||||||
|
call @ Expr::Call { name, args }
|
||||||
|
| call @ Expr::Intrinsic { name, args } => {
|
||||||
|
let cname = match &name.0 {
|
||||||
|
Expr::Identifier(s) => s.clone(),
|
||||||
|
// Should never happen because the parser should have caught this
|
||||||
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all the `Hole` indexes
|
||||||
|
let mut indexes = Vec::new();
|
||||||
|
for (i, arg) in args.0.iter().enumerate() {
|
||||||
|
if let Expr::Hole(..) = &arg.0 {
|
||||||
|
indexes.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no `Hole` in the args then return early
|
||||||
|
// if indexes.is_empty() {
|
||||||
|
// return_err!(LoweringError {
|
||||||
|
// span: rhs.1.clone(),
|
||||||
|
// message: "Expected hole in piping".to_string(),
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Remove the `Hole` from the args
|
||||||
|
let mut new_args = args.0.clone();
|
||||||
|
for index in indexes.iter().rev() {
|
||||||
|
new_args.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new call expression with the new args
|
||||||
|
let new_call = match call {
|
||||||
|
Expr::Call { name, args } => Expr::Call{
|
||||||
|
name: name.clone(),
|
||||||
|
args: (new_args, args.1.clone())
|
||||||
|
},
|
||||||
|
Expr::Intrinsic { name, args } => Expr::Intrinsic {
|
||||||
|
name: name.clone(),
|
||||||
|
args: (new_args, args.1.clone())
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
let new_call = expr_to_ir(&new_call);
|
||||||
|
if_err_return!(new_call.1);
|
||||||
|
|
||||||
|
// Lower all args
|
||||||
|
let mut largs = Vec::new();
|
||||||
|
for arg in &args.0 {
|
||||||
|
match arg.0 {
|
||||||
|
// If the arg is a `Hole` then replace it with the lowered IR
|
||||||
|
Expr::Hole(..) => {
|
||||||
|
largs.push(lhs_ir.0.clone().unwrap());
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let arg = expr_to_ir(&arg.0);
|
||||||
|
if_err_return!(arg.1);
|
||||||
|
largs.push(arg.0.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match the call to the right IRKind
|
||||||
|
let ir_kind = match new_call.0.unwrap() {
|
||||||
|
IRKind::Call { .. } => IRKind::Call {
|
||||||
|
name: cname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
},
|
||||||
|
IRKind::Intrinsic { .. } => IRKind::Intrinsic {
|
||||||
|
name: cname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
return_ok!(ir_kind);
|
||||||
|
},
|
||||||
|
_ => return_err!(LoweringError {
|
||||||
|
span: rhs.1.clone(),
|
||||||
|
message: "Expected call".to_string()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Let { public, name, type_hint, value, mutable } => {
|
||||||
|
let lvalue = expr_to_ir(&value.0);
|
||||||
|
if_err_return!(lvalue.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Define {
|
||||||
|
public: *public,
|
||||||
|
name: name.0.clone(),
|
||||||
|
type_hint: gen_type_hint(&type_hint.0),
|
||||||
|
value: Box::new(lvalue.0.unwrap()),
|
||||||
|
mutable: *mutable,
|
||||||
|
span: value.1.clone()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Intrinsic { name, args } => {
|
||||||
|
let lname = match &name.0 {
|
||||||
|
Expr::Identifier(s) => {
|
||||||
|
if INTRINSICS.contains(&s.as_str()) { s.clone() }
|
||||||
|
else {
|
||||||
|
return_err!(LoweringError {
|
||||||
|
span: name.1.clone(),
|
||||||
|
message: format!("Unknown intrinsic: `{}`", s),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return_err!(LoweringError { span: name.1.clone(), message: "Expected identifier".to_string()})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut largs = Vec::new();
|
||||||
|
for arg in &args.0 {
|
||||||
|
let larg = expr_to_ir(&arg.0);
|
||||||
|
if_err_return!(larg.1);
|
||||||
|
|
||||||
|
// Check if the args is string
|
||||||
|
if let IRKind::Value{ .. } = larg.0.clone().unwrap() {
|
||||||
|
largs.push(larg.0.clone().unwrap());
|
||||||
|
} else {
|
||||||
|
return_err!(LoweringError { span: arg.1.clone(), message: "Expected string".to_string()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return_ok!(IRKind::Intrinsic {
|
||||||
|
name: lname,
|
||||||
|
args: largs,
|
||||||
|
span: name.1.start..args.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Fun { public, name, type_hint, args, body } => {
|
||||||
|
// Iterate each argument and give it a type hint
|
||||||
|
let largs = args.0.iter().map(|arg| (arg.0.0.clone(), gen_type_hint(&arg.1.0))).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let lbody = expr_to_ir(&body.0);
|
||||||
|
if_err_return!(lbody.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Fun {
|
||||||
|
public: *public,
|
||||||
|
name: name.0.clone(),
|
||||||
|
return_type_hint: gen_type_hint(&type_hint.0),
|
||||||
|
args: largs,
|
||||||
|
body: Box::new(lbody.0.unwrap()),
|
||||||
|
span: name.1.start..body.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Return { expr } => {
|
||||||
|
let lexpr = expr_to_ir(&expr.0);
|
||||||
|
if_err_return!(lexpr.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Return {
|
||||||
|
value: Box::new(lexpr.0.unwrap()),
|
||||||
|
span: expr.1.clone()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Do { body } => {
|
||||||
|
let mut lbody = Vec::new();
|
||||||
|
|
||||||
|
for expr in &body.0 {
|
||||||
|
let expr = expr_to_ir(&expr.0);
|
||||||
|
if_err_return!(expr.1);
|
||||||
|
lbody.push(expr.0.unwrap());
|
||||||
|
};
|
||||||
|
|
||||||
|
return_ok!(IRKind::Do {
|
||||||
|
body: lbody,
|
||||||
|
span: body.1.clone()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::If { cond, body, else_body } => {
|
||||||
|
let lcond = expr_to_ir(&cond.0);
|
||||||
|
if_err_return!(lcond.1);
|
||||||
|
|
||||||
|
let lbody = expr_to_ir(&body.0);
|
||||||
|
if_err_return!(lbody.1);
|
||||||
|
|
||||||
|
let lelse_body = expr_to_ir(&else_body.0);
|
||||||
|
if_err_return!(lelse_body.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::If {
|
||||||
|
cond: Box::new(lcond.0.unwrap()),
|
||||||
|
body: Box::new(lbody.0.unwrap()),
|
||||||
|
else_body: Box::new(lelse_body.0.unwrap()),
|
||||||
|
span: cond.1.start..else_body.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
Expr::Case { expr, cases, default } => {
|
||||||
|
let lexpr = expr_to_ir(&expr.0);
|
||||||
|
if_err_return!(lexpr.1);
|
||||||
|
|
||||||
|
let mut lcases = Vec::new();
|
||||||
|
for case in &cases.0 {
|
||||||
|
let lcond = expr_to_ir(&case.0.0);
|
||||||
|
if_err_return!(lcond.1);
|
||||||
|
|
||||||
|
let lcase = expr_to_ir(&case.1.0);
|
||||||
|
if_err_return!(lcase.1);
|
||||||
|
|
||||||
|
lcases.push(
|
||||||
|
(Box::new(lcond.0.unwrap()), Box::new(lcase.0.unwrap()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ldefault = expr_to_ir(&default.0);
|
||||||
|
if_err_return!(ldefault.1);
|
||||||
|
|
||||||
|
return_ok!(IRKind::Case {
|
||||||
|
cond: Box::new(lexpr.0.unwrap()),
|
||||||
|
cases: lcases,
|
||||||
|
default: Box::new(ldefault.0.unwrap()),
|
||||||
|
span: expr.1.start..default.1.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Handle primitive types error (e.g. overflow)
|
||||||
|
// For now it just leaves the value as is and let the target compiler handle it
|
||||||
|
Expr::Int(value) => return_ok!(IRKind::Value { value: Value::Int(*value) }),
|
||||||
|
Expr::Boolean(value) => return_ok!(IRKind::Value { value: Value::Boolean(*value) }),
|
||||||
|
Expr::String(value) => return_ok!(IRKind::Value { value: Value::String(value.clone()) }),
|
||||||
|
Expr::Identifier(value) => return_ok!(IRKind::Value { value: Value::Ident(value.clone()) }),
|
||||||
|
|
||||||
|
v @ Expr::Vector(values) | v @ Expr::Tuple(values) => {
|
||||||
|
let mut lvalues = Vec::new();
|
||||||
|
for value in values {
|
||||||
|
let value = expr_to_ir(&value.0);
|
||||||
|
if_err_return!(value.1);
|
||||||
|
|
||||||
|
lvalues.push(value.0.unwrap());
|
||||||
|
}
|
||||||
|
match v {
|
||||||
|
Expr::Vector(..) => return_ok!(IRKind::Vector { values: lvalues }),
|
||||||
|
Expr::Tuple(..) => return_ok!(IRKind::Tuple { values: lvalues }),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Probably will never happen because it is catched in parser
|
||||||
|
Expr::Hole(start, end) => (None, Some(LoweringError {
|
||||||
|
span: *start..*end,
|
||||||
|
message: "Hole can only be used in piping, it is not allowed here.".to_string()
|
||||||
|
})),
|
||||||
|
_ => { dbg!(expr); todo!() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_type_hint(type_hint: &Typehint) -> String {
|
||||||
|
match type_hint {
|
||||||
|
Typehint::Single(t) => match t.as_str() {
|
||||||
|
"any" => "any".to_string(),
|
||||||
|
"int" => "number".to_string(),
|
||||||
|
"bool" => "boolean".to_string(),
|
||||||
|
_ => t.to_string()
|
||||||
|
},
|
||||||
|
Typehint::Tuple(ts) => {
|
||||||
|
let types = ts.iter().map(|arg| gen_type_hint(&arg.0)).collect::<Vec<_>>();
|
||||||
|
format!("[{}]", types.join(", "))
|
||||||
|
},
|
||||||
|
Typehint::Vector(t) => format!("{}[]", gen_type_hint(&t.0)),
|
||||||
|
Typehint::Function(args, ret) => {
|
||||||
|
let args_ty = args.iter().map(|arg| gen_type_hint(&arg.0)).collect::<Vec<_>>();
|
||||||
|
let return_ty = gen_type_hint(&ret.0);
|
||||||
|
format!(
|
||||||
|
"({}) => {}",
|
||||||
|
args_ty
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, arg)| format!("__{}: {}", i, arg))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
return_ty
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
8
crates/lexer/Cargo.toml
Normal file
8
crates/lexer/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "lexer"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chumsky = "0.8.0"
|
172
crates/lexer/src/lib.rs
Normal file
172
crates/lexer/src/lib.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
use chumsky::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Token {
|
||||||
|
// Keywords
|
||||||
|
KwLet, KwMut, KwFun,
|
||||||
|
KwDo, KwEnd,
|
||||||
|
KwIf, KwThen, KwElse,
|
||||||
|
KwCase, KwOf,
|
||||||
|
KwReturn,
|
||||||
|
KwPub,
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
Int(i64), Boolean(bool),
|
||||||
|
String(String), Identifier(String),
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
Plus, Minus, Multiply, Divide, Modulus,
|
||||||
|
Pipe,
|
||||||
|
Not, Equal, NotEqual, Less, Greater,
|
||||||
|
Pipeline, Arrow,
|
||||||
|
|
||||||
|
// Symbols & Delimiters
|
||||||
|
Assign,
|
||||||
|
Dot, Comma,
|
||||||
|
Colon, SemiColon,
|
||||||
|
OpenParen, CloseParen,
|
||||||
|
OpenBracket, CloseBracket,
|
||||||
|
At,
|
||||||
|
Hole,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Token {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::KwLet => write!(f, "let"),
|
||||||
|
Token::KwMut => write!(f, "mut"),
|
||||||
|
Token::KwFun => write!(f, "fun"),
|
||||||
|
Token::KwDo => write!(f, "do"),
|
||||||
|
Token::KwEnd => write!(f, "end"),
|
||||||
|
Token::KwIf => write!(f, "if"),
|
||||||
|
Token::KwThen => write!(f, "then"),
|
||||||
|
Token::KwElse => write!(f, "else"),
|
||||||
|
Token::KwCase => write!(f, "case"),
|
||||||
|
Token::KwOf => write!(f, "of"),
|
||||||
|
Token::KwReturn => write!(f, "return"),
|
||||||
|
Token::KwPub => write!(f, "pub"),
|
||||||
|
|
||||||
|
Token::Int(i) => write!(f, "{}", i),
|
||||||
|
Token::Boolean(b) => write!(f, "{}", b),
|
||||||
|
Token::String(s) => write!(f, "{}", s),
|
||||||
|
Token::Identifier(s) => write!(f, "{}", s),
|
||||||
|
|
||||||
|
Token::Plus => write!(f, "+"),
|
||||||
|
Token::Minus => write!(f, "-"),
|
||||||
|
Token::Multiply => write!(f, "*"),
|
||||||
|
Token::Divide => write!(f, "/"),
|
||||||
|
Token::Modulus => write!(f, "%"),
|
||||||
|
Token::Not => write!(f, "!"),
|
||||||
|
Token::Equal => write!(f, "=="),
|
||||||
|
Token::NotEqual => write!(f, "!="),
|
||||||
|
Token::Less => write!(f, "<"),
|
||||||
|
Token::Greater => write!(f, ">"),
|
||||||
|
Token::Pipeline => write!(f, "|>"),
|
||||||
|
Token::Pipe => write!(f, "|"),
|
||||||
|
Token::Arrow => write!(f, "->"),
|
||||||
|
|
||||||
|
Token::Assign => write!(f, "="),
|
||||||
|
Token::Dot => write!(f, "."),
|
||||||
|
Token::Comma => write!(f, ","),
|
||||||
|
Token::Colon => write!(f, ":"),
|
||||||
|
Token::SemiColon => write!(f, ";"),
|
||||||
|
|
||||||
|
Token::OpenParen => write!(f, "("),
|
||||||
|
Token::CloseParen => write!(f, ")"),
|
||||||
|
Token::OpenBracket => write!(f, "["),
|
||||||
|
Token::CloseBracket => write!(f, "]"),
|
||||||
|
|
||||||
|
Token::At => write!(f, "@"),
|
||||||
|
Token::Hole => write!(f, "_"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Span = std::ops::Range<usize>;
|
||||||
|
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
|
||||||
|
let int = text::int(10)
|
||||||
|
.map(|s: String| Token::Int(s.parse().unwrap()));
|
||||||
|
|
||||||
|
let string = just('"')
|
||||||
|
.ignore_then(filter(|c| *c != '"').repeated())
|
||||||
|
.then_ignore(just('"'))
|
||||||
|
.collect::<String>()
|
||||||
|
.map(Token::String);
|
||||||
|
|
||||||
|
let symbol = choice((
|
||||||
|
just("->").to(Token::Arrow),
|
||||||
|
|
||||||
|
just('+').to(Token::Plus),
|
||||||
|
just('-').to(Token::Minus),
|
||||||
|
just('*').to(Token::Multiply),
|
||||||
|
just('/').to(Token::Divide),
|
||||||
|
|
||||||
|
just('!').to(Token::Not),
|
||||||
|
just("==").to(Token::Equal),
|
||||||
|
|
||||||
|
just("|>").to(Token::Pipeline),
|
||||||
|
just("|").to(Token::Pipe),
|
||||||
|
|
||||||
|
just('<').to(Token::Less),
|
||||||
|
just('>').to(Token::Greater),
|
||||||
|
|
||||||
|
just('=').to(Token::Assign),
|
||||||
|
just('.').to(Token::Dot),
|
||||||
|
just(',').to(Token::Comma),
|
||||||
|
just(':').to(Token::Colon),
|
||||||
|
just(';').to(Token::SemiColon),
|
||||||
|
just('(').to(Token::OpenParen),
|
||||||
|
just(')').to(Token::CloseParen),
|
||||||
|
just('[').to(Token::OpenBracket),
|
||||||
|
just(']').to(Token::CloseBracket),
|
||||||
|
just('@').to(Token::At),
|
||||||
|
just('_').to(Token::Hole),
|
||||||
|
));
|
||||||
|
|
||||||
|
let keyword = text::ident().map(|s: String| match s.as_str() {
|
||||||
|
"true" => Token::Boolean(true),
|
||||||
|
"false" => Token::Boolean(false),
|
||||||
|
|
||||||
|
"let" => Token::KwLet,
|
||||||
|
"fun" => Token::KwFun,
|
||||||
|
"do" => Token::KwDo,
|
||||||
|
"end" => Token::KwEnd,
|
||||||
|
"if" => Token::KwIf,
|
||||||
|
"then" => Token::KwThen,
|
||||||
|
"else" => Token::KwElse,
|
||||||
|
"case" => Token::KwCase,
|
||||||
|
"of" => Token::KwOf,
|
||||||
|
"return" => Token::KwReturn,
|
||||||
|
"pub" => Token::KwPub,
|
||||||
|
_ => Token::Identifier(s),
|
||||||
|
});
|
||||||
|
|
||||||
|
let token = int
|
||||||
|
.or(string)
|
||||||
|
.or(symbol)
|
||||||
|
.or(keyword)
|
||||||
|
.recover_with(skip_then_retry_until([]));
|
||||||
|
|
||||||
|
// let comment = just("--").then(take_until(just('\n'))).padded();
|
||||||
|
let comment = just('-')
|
||||||
|
.then_ignore(just('{')
|
||||||
|
.ignore_then(none_of('}').ignored().repeated())
|
||||||
|
.then_ignore(just("}-"))
|
||||||
|
.or(just('-').ignore_then(none_of('\n').ignored().repeated()))
|
||||||
|
)
|
||||||
|
.padded()
|
||||||
|
.ignored()
|
||||||
|
.repeated();
|
||||||
|
|
||||||
|
token
|
||||||
|
.padded_by(comment)
|
||||||
|
.map_with_span(|token, span| (token, span))
|
||||||
|
.padded()
|
||||||
|
.repeated()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn lex(src: String) -> (Option<Vec<(Token, std::ops::Range<usize>)>>, Vec<Simple<char>>) {
|
||||||
|
let (tokens, lex_error) = lexer().parse_recovery(src.as_str());
|
||||||
|
(tokens, lex_error)
|
||||||
|
}
|
26
crates/main/Cargo.toml
Normal file
26
crates/main/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "hazure"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Argument handling
|
||||||
|
clap = { version = "3.0.14", features = ["derive"] }
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
toml = "0.5"
|
||||||
|
serde = "1.0.136"
|
||||||
|
serde_derive = "1.0.136"
|
||||||
|
|
||||||
|
# Parsing
|
||||||
|
lexer = { path = "../lexer" }
|
||||||
|
parser = { path = "../parser" }
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
typecheck = { path = "../typecheck" }
|
||||||
|
diagnostic = { path = "../diagnostic" }
|
||||||
|
|
||||||
|
# Codegen
|
||||||
|
hir = { path = "../hir" }
|
||||||
|
codegen = { path = "../codegen" }
|
33
crates/main/src/args.rs
Normal file
33
crates/main/src/args.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use clap::{ Parser, Subcommand };
|
||||||
|
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
/// Hazure compiler
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(
|
||||||
|
version = VERSION,
|
||||||
|
long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub options: Options,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Options {
|
||||||
|
#[clap(about = "Compile an input file.")]
|
||||||
|
Compile {
|
||||||
|
/// The input file to compile
|
||||||
|
#[clap(parse(from_os_str))]
|
||||||
|
input: PathBuf,
|
||||||
|
/// Print parsed AST and exit (for debugging)
|
||||||
|
#[clap(short, long)]
|
||||||
|
ast: bool,
|
||||||
|
/// Log process
|
||||||
|
#[clap(short, long)]
|
||||||
|
log: bool,
|
||||||
|
/// Output file path
|
||||||
|
#[clap(short, long, parse(from_os_str))]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
145
crates/main/src/main.rs
Normal file
145
crates/main/src/main.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use std::{fs, io::Write, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::Parser as ArgParser;
|
||||||
|
|
||||||
|
use lexer::lex;
|
||||||
|
use parser::parse;
|
||||||
|
use diagnostic::Diagnostics;
|
||||||
|
use hir::ast_to_ir;
|
||||||
|
use typecheck::check;
|
||||||
|
use codegen::ts;
|
||||||
|
|
||||||
|
pub mod args;
|
||||||
|
use args::{Args, Options};
|
||||||
|
|
||||||
|
pub mod util;
|
||||||
|
use crate::util::log;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
match args.options {
|
||||||
|
Options::Compile {
|
||||||
|
input: file_name,
|
||||||
|
ast: print_ast,
|
||||||
|
log: should_log,
|
||||||
|
output: _output, // TODO: Custom output file
|
||||||
|
} => {
|
||||||
|
// Macro to only log if `should_log` is true
|
||||||
|
macro_rules! logif {
|
||||||
|
($level:expr, $msg:expr) => { if should_log { log($level, $msg); } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timer
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Get file contents
|
||||||
|
logif!(0, format!("Reading {}", &file_name.display()));
|
||||||
|
let src = fs::read_to_string(&file_name).expect("Failed to read file");
|
||||||
|
|
||||||
|
// Lex the file
|
||||||
|
let (tokens, lex_error) = lex(src.clone());
|
||||||
|
let (ast, parse_error) = parse(tokens.unwrap(), src.chars().count());
|
||||||
|
|
||||||
|
let mut diagnostics = Diagnostics::new();
|
||||||
|
for err in lex_error { diagnostics.add_lex_error(err); }
|
||||||
|
for err in parse_error { diagnostics.add_parse_error(err); }
|
||||||
|
|
||||||
|
// Report syntax errors if any
|
||||||
|
if diagnostics.has_error() {
|
||||||
|
diagnostics.display(src);
|
||||||
|
logif!(0, "Epic parsing fail");
|
||||||
|
std::process::exit(1);
|
||||||
|
} else {
|
||||||
|
logif!(0, format!("Parsing took {}ms", start.elapsed().as_millis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ast {
|
||||||
|
Some(ast) => {
|
||||||
|
// Convert the AST to HIR
|
||||||
|
let (ir, lowering_error) = ast_to_ir(ast);
|
||||||
|
for err in lowering_error { diagnostics.add_lowering_error(err); }
|
||||||
|
|
||||||
|
if print_ast { log(0, format!("IR\n{:#?}", ir)); }
|
||||||
|
|
||||||
|
// Typecheck the HIR
|
||||||
|
match check(&ir) {
|
||||||
|
Ok(_) => {
|
||||||
|
logif!(0, format!("Typechecking took {}ms", start.elapsed().as_millis()));
|
||||||
|
},
|
||||||
|
Err(errs) => {
|
||||||
|
for err in errs {
|
||||||
|
diagnostics.add_typecheck_error(err);
|
||||||
|
}
|
||||||
|
diagnostics.display(src);
|
||||||
|
logif!(2, "Typechecking failed");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report lowering errors if any
|
||||||
|
if diagnostics.has_error() {
|
||||||
|
diagnostics.display(src);
|
||||||
|
logif!(0, "Epic Lowering(HIR) fail");
|
||||||
|
std::process::exit(1);
|
||||||
|
} else {
|
||||||
|
logif!(0, format!("Lowering took {}ms", start.elapsed().as_millis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate code
|
||||||
|
let mut codegen = ts::Codegen::new();
|
||||||
|
codegen.gen(ir);
|
||||||
|
logif!(0, "Successfully generated code.");
|
||||||
|
|
||||||
|
// Write code to file
|
||||||
|
let output_path: PathBuf = file_name.with_extension("ts").file_name().unwrap().to_os_string().into();
|
||||||
|
let mut file = fs::File::create(&output_path).expect("Failed to create file");
|
||||||
|
file.write_all(codegen.emitted.as_bytes()).expect("Failed to write to file");
|
||||||
|
|
||||||
|
// End timer
|
||||||
|
let duration = start.elapsed().as_millis();
|
||||||
|
|
||||||
|
logif!(0, format!("Compilation took {}ms", duration));
|
||||||
|
logif!(0, format!("Wrote output to `{}`", output_path.display()));
|
||||||
|
},
|
||||||
|
None => { unreachable!(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lexer() {
|
||||||
|
let src = "
|
||||||
|
let x: int = 1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let (tokens, lex_error) = lex(src.to_string());
|
||||||
|
assert!(lex_error.is_empty());
|
||||||
|
|
||||||
|
assert_eq!(tokens.unwrap().len(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parser() {
|
||||||
|
let src = "
|
||||||
|
fun main (foo: int) (bar: bool): string = do
|
||||||
|
do
|
||||||
|
let x: int = foo + 1;
|
||||||
|
end;
|
||||||
|
let y: bool = bar;
|
||||||
|
end;
|
||||||
|
";
|
||||||
|
|
||||||
|
let (tokens, lex_error) = lex(src.to_string());
|
||||||
|
assert!(lex_error.is_empty());
|
||||||
|
|
||||||
|
let (ast, parse_error) = parse(tokens.unwrap(), src.chars().count());
|
||||||
|
assert!(parse_error.is_empty());
|
||||||
|
|
||||||
|
assert!(ast.is_some());
|
||||||
|
}
|
||||||
|
}
|
10
crates/main/src/util.rs
Normal file
10
crates/main/src/util.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
pub fn log<T: Display>(level: i8, msg: T) {
|
||||||
|
match level {
|
||||||
|
0 => println!("\x1b[92m[INFO]\x1b[0m {}", msg),
|
||||||
|
1 => println!("\x1b[93m[WARN]\x1b[0m {}", msg),
|
||||||
|
2 => println!("\x1b[91m[ERRS]\x1b[0m {}", msg),
|
||||||
|
_ => println!("{}", msg),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "syntax"
|
name = "parser"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
lexer = { path = "../lexer" }
|
||||||
chumsky = "0.8.0"
|
chumsky = "0.8.0"
|
389
crates/parser/src/lib.rs
Normal file
389
crates/parser/src/lib.rs
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
use chumsky::{prelude::*, Stream};
|
||||||
|
use lexer::Token;
|
||||||
|
|
||||||
|
pub mod types;
|
||||||
|
use types::{Expr, Spanned, Typehint};
|
||||||
|
|
||||||
|
fn typehint_parser() -> impl Parser<Token, Spanned<Typehint>, Error = Simple<Token>> + Clone {
|
||||||
|
recursive(|ty| {
|
||||||
|
let single = filter_map(|span, token| match token {
|
||||||
|
Token::Identifier(s) => Ok((Typehint::Single(s), span)),
|
||||||
|
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
||||||
|
});
|
||||||
|
|
||||||
|
let tuple = single
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen),
|
||||||
|
)
|
||||||
|
.map_with_span(|args, span| {
|
||||||
|
(Typehint::Tuple(args), span)
|
||||||
|
});
|
||||||
|
|
||||||
|
let vector = single
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenBracket),
|
||||||
|
just(Token::CloseBracket),
|
||||||
|
)
|
||||||
|
.map_with_span(|arg, span| {
|
||||||
|
(Typehint::Vector(Box::new(arg)), span)
|
||||||
|
});
|
||||||
|
|
||||||
|
let function = ty.clone()
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::Pipe),
|
||||||
|
just(Token::Pipe),
|
||||||
|
)
|
||||||
|
.then_ignore(just(Token::Arrow))
|
||||||
|
.then(ty)
|
||||||
|
.map_with_span(|(args, ret), span| {
|
||||||
|
(Typehint::Function(args, Box::new(ret)), span)
|
||||||
|
});
|
||||||
|
|
||||||
|
single
|
||||||
|
.or(tuple)
|
||||||
|
.or(vector)
|
||||||
|
.or(function)
|
||||||
|
.labelled("type hint")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>> + Clone {
|
||||||
|
let identifier = filter_map(|span, token| match token {
|
||||||
|
Token::Identifier(s) => Ok((s, span)),
|
||||||
|
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
||||||
|
}).labelled("identifier");
|
||||||
|
|
||||||
|
let literal = filter_map(|span: std::ops::Range<usize>, token| match token {
|
||||||
|
Token::Int(i) => Ok((Expr::Int(i), span)),
|
||||||
|
Token::Boolean(b) => Ok((Expr::Boolean(b), span)),
|
||||||
|
Token::String(s) => Ok((Expr::String(s), span)),
|
||||||
|
Token::Hole => Ok((Expr::Hole(span.start, span.end), span)),
|
||||||
|
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
||||||
|
}).labelled("literal");
|
||||||
|
|
||||||
|
let expr = recursive(|expr| {
|
||||||
|
let args = expr.clone()
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing();
|
||||||
|
|
||||||
|
let vector = expr.clone()
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenBracket),
|
||||||
|
just(Token::CloseBracket),
|
||||||
|
)
|
||||||
|
.map_with_span(|args, span| {
|
||||||
|
(
|
||||||
|
Expr::Vector(args),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let tuple = expr.clone()
|
||||||
|
.separated_by(just(Token::Comma))
|
||||||
|
.allow_trailing()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen),
|
||||||
|
)
|
||||||
|
.map_with_span(|args, span| {
|
||||||
|
(
|
||||||
|
Expr::Tuple(args),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let atom = literal
|
||||||
|
.or(identifier.map(|(s, span)| (Expr::Identifier(s), span)))
|
||||||
|
.or(vector)
|
||||||
|
.or(tuple)
|
||||||
|
.labelled("atom");
|
||||||
|
|
||||||
|
let call = atom.clone()
|
||||||
|
.then(
|
||||||
|
args.clone()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen),
|
||||||
|
)
|
||||||
|
.repeated()
|
||||||
|
)
|
||||||
|
.foldl(|name, args| {
|
||||||
|
(
|
||||||
|
Expr::Call {
|
||||||
|
name: Box::new(name.clone()),
|
||||||
|
args: (args, name.1.clone()),
|
||||||
|
},
|
||||||
|
name.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let intrinsic = just(Token::At)
|
||||||
|
.ignore_then(atom)
|
||||||
|
.then(
|
||||||
|
args.clone()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen),
|
||||||
|
)
|
||||||
|
.repeated()
|
||||||
|
)
|
||||||
|
.foldl(|name, args| {
|
||||||
|
(
|
||||||
|
Expr::Intrinsic {
|
||||||
|
name: Box::new(name.clone()),
|
||||||
|
args: (args, name.1.clone()),
|
||||||
|
},
|
||||||
|
name.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let unary = choice((
|
||||||
|
just(Token::Plus),
|
||||||
|
just(Token::Minus)))
|
||||||
|
.repeated()
|
||||||
|
.then(call.or(intrinsic))
|
||||||
|
.foldr(|op, rhs| {
|
||||||
|
(
|
||||||
|
Expr::Unary {
|
||||||
|
op: op.to_string(),
|
||||||
|
rhs: Box::new(rhs.clone()),
|
||||||
|
},
|
||||||
|
rhs.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let factor = unary.clone()
|
||||||
|
.then(
|
||||||
|
choice((
|
||||||
|
just(Token::Multiply),
|
||||||
|
just(Token::Divide),
|
||||||
|
just(Token::Modulus)))
|
||||||
|
.then(unary)
|
||||||
|
.repeated())
|
||||||
|
.foldl(|lhs, (op, rhs)| {
|
||||||
|
(
|
||||||
|
Expr::Binary {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op: op.to_string(),
|
||||||
|
rhs: Box::new(rhs.clone()),
|
||||||
|
},
|
||||||
|
rhs.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let term = factor.clone()
|
||||||
|
.then(
|
||||||
|
choice((
|
||||||
|
just(Token::Plus),
|
||||||
|
just(Token::Minus)))
|
||||||
|
.then(factor)
|
||||||
|
.repeated())
|
||||||
|
.foldl(|lhs, (op, rhs)| {
|
||||||
|
(
|
||||||
|
Expr::Binary {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op: op.to_string(),
|
||||||
|
rhs: Box::new(rhs.clone()),
|
||||||
|
},
|
||||||
|
rhs.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let compare = term.clone()
|
||||||
|
.then(
|
||||||
|
choice((
|
||||||
|
just(Token::Less),
|
||||||
|
just(Token::Greater),
|
||||||
|
just(Token::Equal),
|
||||||
|
just(Token::NotEqual)))
|
||||||
|
.then(term)
|
||||||
|
.repeated())
|
||||||
|
.foldl(|lhs, (op, rhs)| {
|
||||||
|
(
|
||||||
|
Expr::Binary {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
op: op.to_string(),
|
||||||
|
rhs: Box::new(rhs.clone()),
|
||||||
|
},
|
||||||
|
rhs.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = compare.clone()
|
||||||
|
.then(
|
||||||
|
just(Token::Pipeline)
|
||||||
|
.ignore_then(compare)
|
||||||
|
.repeated())
|
||||||
|
.foldl(|lhs, rhs| {
|
||||||
|
(
|
||||||
|
Expr::Pipeline {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
rhs: Box::new(rhs.clone()),
|
||||||
|
},
|
||||||
|
rhs.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let let_ = just(Token::KwPub).or_not()
|
||||||
|
.then_ignore(just(Token::KwLet))
|
||||||
|
.then(just(Token::KwMut).or_not())
|
||||||
|
.then(identifier)
|
||||||
|
.then_ignore(just(Token::Colon))
|
||||||
|
.then(typehint_parser())
|
||||||
|
.then_ignore(just(Token::Assign))
|
||||||
|
.then(expr.clone())
|
||||||
|
.map(|((((public, mutable), name), type_hint), value)| {
|
||||||
|
(
|
||||||
|
Expr::Let {
|
||||||
|
public: public.is_some(),
|
||||||
|
name: name.clone(),
|
||||||
|
type_hint,
|
||||||
|
value: Box::new(value.clone()),
|
||||||
|
mutable: mutable.is_some(),
|
||||||
|
},
|
||||||
|
name.1.start..value.1.end,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let fun = just(Token::KwPub).or_not()
|
||||||
|
.then_ignore(just(Token::KwFun))
|
||||||
|
.then(identifier)
|
||||||
|
.then(
|
||||||
|
identifier
|
||||||
|
.then_ignore(just(Token::Colon))
|
||||||
|
.then(typehint_parser())
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen),
|
||||||
|
)
|
||||||
|
.repeated()
|
||||||
|
)
|
||||||
|
.then_ignore(just(Token::Colon))
|
||||||
|
.then(typehint_parser())
|
||||||
|
.then_ignore(just(Token::Assign))
|
||||||
|
.then(expr.clone())
|
||||||
|
.map(|((((public, name), args), type_hint), body)| {
|
||||||
|
(
|
||||||
|
Expr::Fun {
|
||||||
|
public: public.is_some(),
|
||||||
|
name: name.clone(),
|
||||||
|
type_hint,
|
||||||
|
args: (args, name.1.clone()),
|
||||||
|
body: Box::new(body.clone()),
|
||||||
|
},
|
||||||
|
name.1.start..body.1.end,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let return_ = just(Token::KwReturn)
|
||||||
|
.ignore_then(expr.clone())
|
||||||
|
.map(|(expr, span)| {
|
||||||
|
(
|
||||||
|
Expr::Return {
|
||||||
|
expr: Box::new((expr, span.clone())),
|
||||||
|
},
|
||||||
|
span.start..span.end,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let do_block = expr.clone()
|
||||||
|
.repeated()
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::KwDo),
|
||||||
|
just(Token::KwEnd),
|
||||||
|
)
|
||||||
|
.map_with_span(|body, span| {
|
||||||
|
(
|
||||||
|
Expr::Do {
|
||||||
|
body: (body, span.clone()),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let if_block = just(Token::KwIf)
|
||||||
|
.ignore_then(expr.clone())
|
||||||
|
.then_ignore(just(Token::KwThen))
|
||||||
|
|
||||||
|
.then(
|
||||||
|
do_block.clone()
|
||||||
|
.or(expr.clone())
|
||||||
|
)
|
||||||
|
|
||||||
|
.then_ignore(just(Token::KwElse))
|
||||||
|
.then(
|
||||||
|
do_block.clone()
|
||||||
|
.or(expr.clone())
|
||||||
|
)
|
||||||
|
|
||||||
|
.then_ignore(just(Token::KwEnd))
|
||||||
|
.map(|((cond, then), else_)| {
|
||||||
|
(
|
||||||
|
Expr::If {
|
||||||
|
cond: Box::new(cond.clone()),
|
||||||
|
body: Box::new(then),
|
||||||
|
else_body: Box::new(else_.clone()),
|
||||||
|
},
|
||||||
|
cond.1.start..else_.1.end,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let case = just(Token::KwCase)
|
||||||
|
.ignore_then(expr.clone())
|
||||||
|
.then_ignore(just(Token::KwOf))
|
||||||
|
.then(
|
||||||
|
just(Token::Pipe)
|
||||||
|
.ignore_then(expr.clone())
|
||||||
|
.then_ignore(just(Token::Arrow))
|
||||||
|
.then(expr.clone())
|
||||||
|
.repeated()
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
just(Token::Pipe)
|
||||||
|
.ignore_then(just(Token::KwElse))
|
||||||
|
.ignore_then(expr.clone())
|
||||||
|
)
|
||||||
|
.map(|((expr, cases), default)| {
|
||||||
|
(
|
||||||
|
Expr::Case {
|
||||||
|
expr: Box::new(expr.clone()),
|
||||||
|
cases: (
|
||||||
|
cases.clone(),
|
||||||
|
cases.first().unwrap().1.start()..cases.last().unwrap().1.end()
|
||||||
|
),
|
||||||
|
default: Box::new(default.clone()),
|
||||||
|
},
|
||||||
|
expr.1.start()..default.1.end(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let_
|
||||||
|
.or(fun)
|
||||||
|
.or(return_)
|
||||||
|
.or(do_block)
|
||||||
|
.or(if_block)
|
||||||
|
.or(case)
|
||||||
|
.or(pipeline)
|
||||||
|
}).labelled("expression");
|
||||||
|
|
||||||
|
expr
|
||||||
|
.repeated()
|
||||||
|
.then_ignore(end())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)] // We are going to use this once anyway, why we need to make a type?
|
||||||
|
pub fn parse(tokens: Vec<(Token, std::ops::Range<usize>)>, len: usize) -> (Option<Vec<(Expr, std::ops::Range<usize>)>>, Vec<Simple<Token>>) {
|
||||||
|
let (ast, parse_error) = expr_parser().parse_recovery(Stream::from_iter(
|
||||||
|
len..len + 1,
|
||||||
|
tokens.into_iter(),
|
||||||
|
));
|
||||||
|
|
||||||
|
(ast, parse_error)
|
||||||
|
}
|
58
crates/parser/src/types.rs
Normal file
58
crates/parser/src/types.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
pub type Spanned<T> = (T, std::ops::Range<usize>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Typehint {
|
||||||
|
Single(String), // e.g. `int`, `bool`, `string`
|
||||||
|
Tuple(Vec<Spanned<Self>>), // e.g. `(int, bool)`
|
||||||
|
Vector(Box<Spanned<Self>>), // e.g. `[int]`
|
||||||
|
Function(Vec<Spanned<Self>>, Box<Spanned<Self>>), // e.g. `|A: int, B: bool| -> string`, `|A: int| -> [bool]`
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Expr {
|
||||||
|
Int(i64), Float(f64), Boolean(bool),
|
||||||
|
String(String), Identifier(String),
|
||||||
|
|
||||||
|
Tuple(Vec<Spanned<Self>>), // Wait, its all Vec<Spanned<Self>>?
|
||||||
|
Vector(Vec<Spanned<Self>>), // Always have been
|
||||||
|
|
||||||
|
Unary { op: String, rhs: Box<Spanned<Self>> },
|
||||||
|
Binary { lhs: Box<Spanned<Self>>, op: String, rhs: Box<Spanned<Self>> },
|
||||||
|
Call { name: Box<Spanned<Self>>, args: Spanned<Vec<Spanned<Self>>> },
|
||||||
|
Pipeline { lhs: Box<Spanned<Self>>, rhs: Box<Spanned<Self>> },
|
||||||
|
Intrinsic { name: Box<Spanned<Self>>, args: Spanned<Vec<Spanned<Self>>> },
|
||||||
|
|
||||||
|
Let {
|
||||||
|
public: bool,
|
||||||
|
name: Spanned<String>,
|
||||||
|
type_hint: Spanned<Typehint>,
|
||||||
|
value: Box<Spanned<Self>>,
|
||||||
|
mutable: bool,
|
||||||
|
},
|
||||||
|
Fun {
|
||||||
|
public: bool,
|
||||||
|
name: Spanned<String>,
|
||||||
|
type_hint: Spanned<Typehint>,
|
||||||
|
args: Spanned<Vec<(Spanned<String>, Spanned<Typehint>)>>,
|
||||||
|
body: Box<Spanned<Self>>
|
||||||
|
},
|
||||||
|
Return { expr: Box<Spanned<Self>> },
|
||||||
|
|
||||||
|
If {
|
||||||
|
cond: Box<Spanned<Self>>,
|
||||||
|
body: Box<Spanned<Self>>,
|
||||||
|
else_body: Box<Spanned<Self>>
|
||||||
|
},
|
||||||
|
Case {
|
||||||
|
expr: Box<Spanned<Self>>,
|
||||||
|
cases: Spanned<Vec<(Spanned<Self>, Spanned<Self>)>>,
|
||||||
|
default: Box<Spanned<Self>>
|
||||||
|
},
|
||||||
|
Do {
|
||||||
|
body: Spanned<Vec<Spanned<Self>>>
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hole for positional argument(s) in piping
|
||||||
|
Hole(usize, usize), // The usize is the span of the hole (prob should be single but whatever)
|
||||||
|
}
|
10
crates/typecheck/Cargo.toml
Normal file
10
crates/typecheck/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "typecheck"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hir = { path = "../hir" }
|
123
crates/typecheck/src/lib.rs
Normal file
123
crates/typecheck/src/lib.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
use hir::{IR, IRKind, Value};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TypecheckErrorKind {
|
||||||
|
DefinitionTypeMismatch {
|
||||||
|
type_specified: String,
|
||||||
|
type_found: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TypecheckErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TypecheckErrorKind::DefinitionTypeMismatch {
|
||||||
|
type_specified,
|
||||||
|
type_found,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"expected type `{}`, found type `{}`",
|
||||||
|
type_specified, type_found
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypecheckError {
|
||||||
|
pub kind: TypecheckErrorKind,
|
||||||
|
pub span: std::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(irs: &[IR]) -> Result<(), Vec<TypecheckError>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for ir in irs {
|
||||||
|
match &ir.kind {
|
||||||
|
ir @ IRKind::Define { .. } => {
|
||||||
|
match check_define(&ir) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => errors.push(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.is_empty() { Ok(()) }
|
||||||
|
else { Err(errors) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! return_err {
|
||||||
|
($kind:expr, $span:expr) => {{
|
||||||
|
return Err(TypecheckError {
|
||||||
|
kind: $kind,
|
||||||
|
span: $span.clone()
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the types of the definitions.
|
||||||
|
/// This is done by checking the type of the value against the type hint.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```sml
|
||||||
|
/// let x: int = 1; -- Correct
|
||||||
|
///
|
||||||
|
/// let x: string = 1; -- Incorrect
|
||||||
|
/// ```
|
||||||
|
fn check_define(ir: &IRKind) -> Result<(), TypecheckError> {
|
||||||
|
match ir {
|
||||||
|
IRKind::Define {
|
||||||
|
type_hint,
|
||||||
|
value,
|
||||||
|
span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
match &**value {
|
||||||
|
IRKind::Value { value } => {
|
||||||
|
match value {
|
||||||
|
Value::Int(_) => {
|
||||||
|
if type_hint != "number" {
|
||||||
|
return_err!(
|
||||||
|
TypecheckErrorKind::DefinitionTypeMismatch {
|
||||||
|
type_specified: type_hint.to_string(),
|
||||||
|
type_found: "number".to_string(),
|
||||||
|
},
|
||||||
|
span.clone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String(_) => {
|
||||||
|
if type_hint != "string" {
|
||||||
|
return_err!(
|
||||||
|
TypecheckErrorKind::DefinitionTypeMismatch {
|
||||||
|
type_specified: type_hint.to_string(),
|
||||||
|
type_found: "string".to_string(),
|
||||||
|
},
|
||||||
|
span.clone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Boolean(_) => {
|
||||||
|
if type_hint != "boolean" {
|
||||||
|
return_err!(
|
||||||
|
TypecheckErrorKind::DefinitionTypeMismatch {
|
||||||
|
type_specified: type_hint.to_string(),
|
||||||
|
type_found: "boolean".to_string(),
|
||||||
|
},
|
||||||
|
span.clone()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: other types
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: other (right-hand side) IRKinds
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
10
example/factorial.hz
Normal file
10
example/factorial.hz
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
fun factorial (n: int) : int = do
|
||||||
|
case n of
|
||||||
|
| 0 -> return 1
|
||||||
|
| else return n * factorial(n - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
fun main : void = do
|
||||||
|
let result : int = factorial(5)
|
||||||
|
@write(result)
|
||||||
|
end
|
10
example/fibonacci.hz
Normal file
10
example/fibonacci.hz
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
fun fib (n: int): int = do
|
||||||
|
case n of
|
||||||
|
| 1 -> return 1
|
||||||
|
| 2 -> return 1
|
||||||
|
| else return fib(n - 1) + fib(n - 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
fun main: void = do
|
||||||
|
fib(5) |> @write(_)
|
||||||
|
end
|
3
example/hello.hz
Normal file
3
example/hello.hz
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fun main: void = do
|
||||||
|
@write("Hello, World!")
|
||||||
|
end
|
23
example/map.hz
Normal file
23
example/map.hz
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
fun map_ (vec: [int]) (fn: |int| -> int) (current: int): [int] = do
|
||||||
|
if current == @len(vec) then
|
||||||
|
return []
|
||||||
|
else
|
||||||
|
do
|
||||||
|
let new : [int] = []
|
||||||
|
let x : int = @get(vec, current) |> fn(_)
|
||||||
|
@insert(new, x)
|
||||||
|
|
||||||
|
let y : [int] = map_(vec, fn, current + 1)
|
||||||
|
return @concat(new, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fun map (vec: [int]) (fn: |int| -> int): [int] = return map_(vec, fn, 0)
|
||||||
|
|
||||||
|
fun mul10 (x: int): int = return x * 10
|
||||||
|
|
||||||
|
fun main: void = do
|
||||||
|
let foo: [int] = [69, 420, 727, 1337, 42069, 69420]
|
||||||
|
map(foo, mul10) |> @write(_)
|
||||||
|
end
|
113
example/rule110.hz
Normal file
113
example/rule110.hz
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
fun print_single (cell: bool): void = do
|
||||||
|
case cell of
|
||||||
|
| true -> @write("█")
|
||||||
|
| else @write(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
fun print_vec (state: vec_bool) (length: int) (curr: int): void = do
|
||||||
|
if curr == length then @write("") else
|
||||||
|
do
|
||||||
|
@get(state, curr) |> print_single(_)
|
||||||
|
print_vec(state, length, curr + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fun cell_merger (a: bool) (b: bool) (c: bool): bool = do
|
||||||
|
if a then
|
||||||
|
if b then
|
||||||
|
if c then
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if c then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if b then
|
||||||
|
if c then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if c then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fun next (state: vec_bool) (pointer: int): bool = do
|
||||||
|
case pointer of
|
||||||
|
| 0 -> do
|
||||||
|
let a: bool = false
|
||||||
|
let b: bool = @get(state, 1)
|
||||||
|
let c: bool = @get(state, 2)
|
||||||
|
return cell_merger(a, b, c)
|
||||||
|
end
|
||||||
|
| 1 -> do
|
||||||
|
let a: bool = @get(state, 0)
|
||||||
|
let b: bool = @get(state, 1)
|
||||||
|
let c: bool = @get(state, 2)
|
||||||
|
return cell_merger(a, b, c)
|
||||||
|
end
|
||||||
|
| 2 -> do
|
||||||
|
let a: bool = @get(state, 1)
|
||||||
|
let b: bool = @get(state, 2)
|
||||||
|
let c: bool = @get(state, 3)
|
||||||
|
return cell_merger(a, b, c)
|
||||||
|
end
|
||||||
|
| 3 -> do
|
||||||
|
let a: bool = @get(state, 2)
|
||||||
|
let b: bool = @get(state, 3)
|
||||||
|
let c: bool = @get(state, 4)
|
||||||
|
return cell_merger(a, b, c)
|
||||||
|
end
|
||||||
|
| 4 -> do
|
||||||
|
let a: bool = @get(state, 3)
|
||||||
|
let b: bool = @get(state, 4)
|
||||||
|
let c: bool = @get(state, 5)
|
||||||
|
return cell_merger(a, b, c)
|
||||||
|
end
|
||||||
|
| 5 -> do
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
| else return false
|
||||||
|
end
|
||||||
|
|
||||||
|
fun iter (state: vec_bool) (for: int) (curr: int): void = do
|
||||||
|
if curr == for then
|
||||||
|
@write("")
|
||||||
|
else
|
||||||
|
do
|
||||||
|
let next_state: vec_bool = [
|
||||||
|
next(state, 0),
|
||||||
|
next(state, 1),
|
||||||
|
next(state, 2),
|
||||||
|
next(state, 3),
|
||||||
|
next(state, 4),
|
||||||
|
next(state, 5)
|
||||||
|
]
|
||||||
|
|
||||||
|
print_vec(next_state, 6, 0)
|
||||||
|
@write("\n")
|
||||||
|
iter(next_state, for, curr + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fun main: void = do
|
||||||
|
let vec: vec_bool = [false, false, false, false, false, true]
|
||||||
|
print_vec(vec, 6, 0)
|
||||||
|
@write("\n")
|
||||||
|
|
||||||
|
iter(vec, 20, 0)
|
||||||
|
end
|
33
example/tuples.hz
Normal file
33
example/tuples.hz
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
-- This is a rewrite of runtime/tuples.ts in itself
|
||||||
|
fun every_ (a : [any]) (b : [any]) (current : int) : bool = do
|
||||||
|
if @len(a) == current
|
||||||
|
then return true
|
||||||
|
else
|
||||||
|
do
|
||||||
|
let aa : any = @get(a, current)
|
||||||
|
let bb : any = @get(b, current)
|
||||||
|
if aa == bb
|
||||||
|
then return every_(a, b, current + 1)
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fun every (a : [any]) (b : [any]): bool = return every_(a, b, 0)
|
||||||
|
|
||||||
|
fun eqTuples (a : [any]) (b : [any]) : bool =
|
||||||
|
case @len(a) == @len(b) of
|
||||||
|
| true -> return every(a, b)
|
||||||
|
| else return false
|
||||||
|
end
|
||||||
|
|
||||||
|
fun main : void = do
|
||||||
|
let foo : (int, int) = (1, 3)
|
||||||
|
let bar : (int, int) = (2, 1)
|
||||||
|
let baz : (int, int, int) = (1, 3, 1)
|
||||||
|
eqT(foo, bar) |> @write(_)
|
||||||
|
@write("\n")
|
||||||
|
eqT(foo, baz) |> @write(_)
|
||||||
|
@write("\n")
|
||||||
|
eqT(foo, (1, 3)) |> @write(_)
|
||||||
|
end
|
|
@ -1,7 +0,0 @@
|
||||||
fun factorial (n : int) : int =
|
|
||||||
if n == 0
|
|
||||||
| return 1
|
|
||||||
| return n * factorial(n - 1)
|
|
||||||
|
|
||||||
result : int = factorial(5)
|
|
||||||
@write(result)
|
|
41
runtime/io.ts
Normal file
41
runtime/io.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { writeAllSync } from "https://deno.land/std@0.129.0/streams/conversion.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes text to the stdout stream.
|
||||||
|
* @param text The text to write. Can be any type.
|
||||||
|
*/
|
||||||
|
export function write(text: any): void {
|
||||||
|
const bytes: Uint8Array = new TextEncoder().encode(text);
|
||||||
|
writeAllSync(Deno.stdout, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read text from the stdin stream.
|
||||||
|
* @param prompt_str The prompt string to display.
|
||||||
|
* @returns The text read from the stdin stream.
|
||||||
|
*/
|
||||||
|
export function read(prompt_str: string): string {
|
||||||
|
const input = prompt(prompt_str, "");
|
||||||
|
return input ? input : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes text to the file.
|
||||||
|
* @param text The text to write. Can be any type.
|
||||||
|
* @param path The path to the file.
|
||||||
|
*/
|
||||||
|
export function writeFile(text: any, path: string): void {
|
||||||
|
const bytes: Uint8Array = new TextEncoder().encode(text);
|
||||||
|
Deno.writeFileSync(path, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read text from the file.
|
||||||
|
* @param path The path to the file.
|
||||||
|
* @returns The text read from the file.
|
||||||
|
*/
|
||||||
|
export function readFile(path: string): string {
|
||||||
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
const text = decoder.decode(Deno.readFileSync(path));
|
||||||
|
return text;
|
||||||
|
}
|
4
runtime/tuples.ts
Normal file
4
runtime/tuples.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export function eqTuples(a: any[], b: any[]) {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
return a.every((elem, i) => b[i] === elem);
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "nightly"
|
|
|
@ -1,116 +0,0 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
pub type Spanned<T> = (T, std::ops::Range<usize>);
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum BuiltinType {
|
|
||||||
Any, Null, Undefined,
|
|
||||||
Boolean, Int, String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Typehint {
|
|
||||||
Builtin(BuiltinType),
|
|
||||||
Single(String),
|
|
||||||
Tuple(Vec<Spanned<Self>>),
|
|
||||||
Vector(Box<Spanned<Self>>),
|
|
||||||
Function(Vec<Spanned<Self>>, Box<Spanned<Self>>),
|
|
||||||
Union(Vec<Spanned<Self>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Literal {
|
|
||||||
Int(i64),
|
|
||||||
String(String),
|
|
||||||
Boolean(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum UnaryOp { Minus, Not }
|
|
||||||
|
|
||||||
impl Display for UnaryOp {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
UnaryOp::Minus => write!(f, "-"),
|
|
||||||
UnaryOp::Not => write!(f, "!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum BinaryOp {
|
|
||||||
Plus, Minus, Multiply, Divide, Modulus,
|
|
||||||
Equal, NotEqual, Less, Greater,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BinaryOp {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
BinaryOp::Plus => write!(f, "+"),
|
|
||||||
BinaryOp::Minus => write!(f, "-"),
|
|
||||||
BinaryOp::Multiply => write!(f, "*"),
|
|
||||||
BinaryOp::Divide => write!(f, "/"),
|
|
||||||
BinaryOp::Modulus => write!(f, "%"),
|
|
||||||
BinaryOp::Equal => write!(f, "==="),
|
|
||||||
BinaryOp::NotEqual => write!(f, "!=="),
|
|
||||||
BinaryOp::Less => write!(f, "<"),
|
|
||||||
BinaryOp::Greater => write!(f, ">"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Expr {
|
|
||||||
Literal(Spanned<Literal>),
|
|
||||||
Identifier(Spanned<String>),
|
|
||||||
Tuple(Vec<Spanned<Self>>),
|
|
||||||
Vector(Vec<Spanned<Self>>),
|
|
||||||
Object {
|
|
||||||
fields: Vec<(Spanned<String>, Spanned<Self>)>,
|
|
||||||
},
|
|
||||||
|
|
||||||
Unary { op: UnaryOp, rhs: Box<Spanned<Self>> },
|
|
||||||
Binary { op: BinaryOp, lhs: Box<Spanned<Self>>, rhs: Box<Spanned<Self>> },
|
|
||||||
|
|
||||||
Call { name: Box<Spanned<Self>>, args: Vec<Spanned<Self>> },
|
|
||||||
Method { obj: Box<Spanned<Self>>, name: Box<Spanned<Self>>, args: Vec<Spanned<Self>> },
|
|
||||||
Access { obj: Box<Spanned<Self>>, name: Box<Spanned<Self>> },
|
|
||||||
Intrinsic { name: Box<Spanned<Self>>, args: Vec<Spanned<Self>> },
|
|
||||||
|
|
||||||
Define {
|
|
||||||
name: Spanned<String>,
|
|
||||||
typehint: Spanned<Typehint>,
|
|
||||||
value: Box<Spanned<Self>>
|
|
||||||
},
|
|
||||||
Redefine {
|
|
||||||
name: Spanned<String>,
|
|
||||||
value: Box<Spanned<Self>>
|
|
||||||
},
|
|
||||||
Function {
|
|
||||||
name: Spanned<String>,
|
|
||||||
generics: Vec<Spanned<String>>,
|
|
||||||
args: Vec<(Spanned<String>, Spanned<Typehint>)>,
|
|
||||||
typehint: Spanned<Typehint>,
|
|
||||||
body: Box<Spanned<Self>>
|
|
||||||
},
|
|
||||||
|
|
||||||
If {
|
|
||||||
cond: Box<Spanned<Self>>,
|
|
||||||
t: Box<Spanned<Self>>,
|
|
||||||
f: Box<Spanned<Self>>
|
|
||||||
},
|
|
||||||
Case {
|
|
||||||
cond: Box<Spanned<Self>>,
|
|
||||||
cases: Spanned<Vec<(Spanned<Self>, Spanned<Self>)>>,
|
|
||||||
default: Box<Spanned<Self>>
|
|
||||||
},
|
|
||||||
Do {
|
|
||||||
body: Spanned<Vec<Spanned<Self>>>
|
|
||||||
},
|
|
||||||
|
|
||||||
Return(Box<Spanned<Self>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Stmt {
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
use chumsky::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Delimiter { Paren, Bracket, Brace }
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Token {
|
|
||||||
// Keywords
|
|
||||||
KwFun, KwSet,
|
|
||||||
KwDo, KwEnd,
|
|
||||||
KwIf,
|
|
||||||
KwCase, KwOf,
|
|
||||||
KwReturn,
|
|
||||||
|
|
||||||
// Literals
|
|
||||||
Int(i64), Boolean(bool),
|
|
||||||
String(String), Identifier(String),
|
|
||||||
|
|
||||||
// Operators
|
|
||||||
Plus, Minus, Multiply, Divide, Modulus,
|
|
||||||
Not, Equal, NotEqual, Less, Greater,
|
|
||||||
Arrow, And, Or,
|
|
||||||
|
|
||||||
// Symbols & Delimiters
|
|
||||||
Assign, Dot, Comma, Colon, Semicolon, At,
|
|
||||||
Open(Delimiter), Close(Delimiter),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Span = std::ops::Range<usize>;
|
|
||||||
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
|
|
||||||
let int = text::int(10)
|
|
||||||
.map(|s: String| Token::Int(s.parse().unwrap()));
|
|
||||||
|
|
||||||
let string = just('"')
|
|
||||||
.ignore_then(filter(|c| *c != '"').repeated())
|
|
||||||
.then_ignore(just('"'))
|
|
||||||
.collect::<String>()
|
|
||||||
.map(Token::String);
|
|
||||||
|
|
||||||
let symbol = choice((
|
|
||||||
just("->").to(Token::Arrow),
|
|
||||||
|
|
||||||
just('+').to(Token::Plus),
|
|
||||||
just('-').to(Token::Minus),
|
|
||||||
just('*').to(Token::Multiply),
|
|
||||||
just('/').to(Token::Divide),
|
|
||||||
just('%').to(Token::Modulus),
|
|
||||||
|
|
||||||
just('&').to(Token::And),
|
|
||||||
just('|').to(Token::Or),
|
|
||||||
|
|
||||||
just("!=").to(Token::NotEqual),
|
|
||||||
just('!').or(just('¬')).to(Token::Not),
|
|
||||||
just("==").to(Token::Equal),
|
|
||||||
|
|
||||||
just('<').to(Token::Less),
|
|
||||||
just('>').to(Token::Greater),
|
|
||||||
|
|
||||||
just('=').to(Token::Assign),
|
|
||||||
just('.').to(Token::Dot),
|
|
||||||
just(',').to(Token::Comma),
|
|
||||||
just(':').to(Token::Colon),
|
|
||||||
just(';').to(Token::Semicolon),
|
|
||||||
just('@').to(Token::At),
|
|
||||||
));
|
|
||||||
|
|
||||||
let delim = choice((
|
|
||||||
just('(').to(Token::Open(Delimiter::Paren)),
|
|
||||||
just(')').to(Token::Close(Delimiter::Paren)),
|
|
||||||
just('[').to(Token::Open(Delimiter::Bracket)),
|
|
||||||
just(']').to(Token::Close(Delimiter::Bracket)),
|
|
||||||
just('{').to(Token::Open(Delimiter::Brace)),
|
|
||||||
just('}').to(Token::Close(Delimiter::Brace)),
|
|
||||||
));
|
|
||||||
|
|
||||||
let keyword = text::ident().map(|s: String| match s.as_str() {
|
|
||||||
"true" => Token::Boolean(true),
|
|
||||||
"false" => Token::Boolean(false),
|
|
||||||
|
|
||||||
"fun" => Token::KwFun,
|
|
||||||
"set" => Token::KwSet,
|
|
||||||
"do" => Token::KwDo,
|
|
||||||
"end" => Token::KwEnd,
|
|
||||||
"if" => Token::KwIf,
|
|
||||||
"case" => Token::KwCase,
|
|
||||||
"of" => Token::KwOf,
|
|
||||||
"return" => Token::KwReturn,
|
|
||||||
_ => Token::Identifier(s),
|
|
||||||
});
|
|
||||||
|
|
||||||
let token = int
|
|
||||||
.or(string)
|
|
||||||
.or(symbol)
|
|
||||||
.or(delim)
|
|
||||||
.or(keyword)
|
|
||||||
.recover_with(skip_then_retry_until([]));
|
|
||||||
|
|
||||||
// let comment = just("--").then(take_until(just('\n'))).padded();
|
|
||||||
let comment = just('-')
|
|
||||||
.then_ignore(just('{')
|
|
||||||
.ignore_then(none_of('}').ignored().repeated())
|
|
||||||
.then_ignore(just("}-"))
|
|
||||||
.or(just('-').ignore_then(none_of('\n').ignored().repeated()))
|
|
||||||
)
|
|
||||||
.padded()
|
|
||||||
.ignored()
|
|
||||||
.repeated();
|
|
||||||
|
|
||||||
token
|
|
||||||
.padded_by(comment)
|
|
||||||
.map_with_span(|token, span| (token, span))
|
|
||||||
.padded()
|
|
||||||
.repeated()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn lex(src: String) -> (Option<Vec<(Token, std::ops::Range<usize>)>>, Vec<Simple<char>>) {
|
|
||||||
let (tokens, lex_error) = lexer().parse_recovery(src.as_str());
|
|
||||||
(tokens, lex_error)
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
#![feature(trait_alias)]
|
|
||||||
|
|
||||||
pub mod lex;
|
|
||||||
pub mod parse;
|
|
||||||
pub mod ast;
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod lex;
|
|
||||||
pub mod parse;
|
|
||||||
pub mod ast;
|
|
|
@ -1,309 +0,0 @@
|
||||||
use super::{*, ast::*, lex::{Token, Delimiter}};
|
|
||||||
use chumsky::{prelude::*, Stream};
|
|
||||||
|
|
||||||
pub trait P<T> = chumsky::Parser<Token, T, Error = Simple<Token>> + Clone;
|
|
||||||
|
|
||||||
fn identifier() -> impl P<Spanned<String>> {
|
|
||||||
filter_map(|span, token| match token {
|
|
||||||
Token::Identifier(s) => Ok((s, span)),
|
|
||||||
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
|
||||||
}).labelled("identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn literal() -> impl P<Spanned<Literal>> {
|
|
||||||
filter_map(|span, token| match token {
|
|
||||||
Token::Int(i) => Ok((ast::Literal::Int(i), span)),
|
|
||||||
Token::Boolean(b) => Ok((ast::Literal::Boolean(b), span)),
|
|
||||||
Token::String(s) => Ok((ast::Literal::String(s), span)),
|
|
||||||
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
|
||||||
}).labelled("literal")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn typehint_parser() -> impl P<Spanned<Typehint>> {
|
|
||||||
recursive(|ty| {
|
|
||||||
|
|
||||||
let single = filter_map(|span, token| match token {
|
|
||||||
Token::Identifier(s) => Ok((
|
|
||||||
match s.as_str() {
|
|
||||||
"any" => ast::Typehint::Builtin(ast::BuiltinType::Any),
|
|
||||||
"null" => ast::Typehint::Builtin(ast::BuiltinType::Null),
|
|
||||||
"undefined" => ast::Typehint::Builtin(ast::BuiltinType::Undefined),
|
|
||||||
"bool" => ast::Typehint::Builtin(ast::BuiltinType::Boolean),
|
|
||||||
"int" => ast::Typehint::Builtin(ast::BuiltinType::Int),
|
|
||||||
"string" => ast::Typehint::Builtin(ast::BuiltinType::String),
|
|
||||||
_ => Typehint::Single(s),
|
|
||||||
}, span)),
|
|
||||||
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
|
|
||||||
});
|
|
||||||
|
|
||||||
let tuple = single
|
|
||||||
.separated_by(just(Token::Comma))
|
|
||||||
.allow_trailing()
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.map_with_span(|args, span| {( Typehint::Tuple(args), span )});
|
|
||||||
|
|
||||||
let vector = single
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Bracket)),
|
|
||||||
just(Token::Close(Delimiter::Bracket)))
|
|
||||||
.map_with_span(|arg, span| {( Typehint::Vector(Box::new(arg)), span )});
|
|
||||||
|
|
||||||
let function = ty.clone()
|
|
||||||
.separated_by(just(Token::Comma))
|
|
||||||
.allow_trailing()
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Or),
|
|
||||||
just(Token::Or))
|
|
||||||
.then_ignore(just(Token::Arrow))
|
|
||||||
.then(ty.clone())
|
|
||||||
.map_with_span(|(args, ret), span| {( Typehint::Function(args, Box::new(ret)), span )});
|
|
||||||
|
|
||||||
let union_ty = ty.clone()
|
|
||||||
.separated_by(just(Token::Or))
|
|
||||||
.allow_trailing()
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.map_with_span(|args, span| {( Typehint::Union(args), span )});
|
|
||||||
|
|
||||||
single
|
|
||||||
.or(tuple)
|
|
||||||
.or(vector)
|
|
||||||
.or(function)
|
|
||||||
.or(union_ty)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr_parser() -> impl P<Spanned<Expr>> {
|
|
||||||
recursive(|expr| {
|
|
||||||
|
|
||||||
// Atom ::= Literal
|
|
||||||
// | Identifier
|
|
||||||
// | Vector
|
|
||||||
// | Tuple
|
|
||||||
// | Object
|
|
||||||
|
|
||||||
let args = expr.clone().separated_by(just(Token::Comma)).allow_trailing();
|
|
||||||
|
|
||||||
let vec = args.clone().delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Bracket)),
|
|
||||||
just(Token::Close(Delimiter::Bracket)))
|
|
||||||
.map_with_span(|args, span| {( Expr::Vector(args), span )});
|
|
||||||
|
|
||||||
let tuple = args.clone().delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.map_with_span(|args, span| {( Expr::Tuple(args), span )});
|
|
||||||
|
|
||||||
let object = identifier()
|
|
||||||
.then_ignore(just(Token::Colon))
|
|
||||||
.then(expr.clone())
|
|
||||||
.separated_by(just(Token::Comma))
|
|
||||||
.allow_trailing()
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Brace)),
|
|
||||||
just(Token::Close(Delimiter::Brace)))
|
|
||||||
.map_with_span(|args, span| {( Expr::Object { fields: args }, span )});
|
|
||||||
|
|
||||||
let atom = literal().map_with_span(|literal, span| {( Expr::Literal(literal), span )})
|
|
||||||
.or(identifier().map_with_span(|ident, span| {( Expr::Identifier(ident), span )}))
|
|
||||||
.or(vec)
|
|
||||||
.or(tuple)
|
|
||||||
.or(object)
|
|
||||||
.labelled("atom");
|
|
||||||
|
|
||||||
// Call ::= Identifier '(' ( Expr ( ',' Expr )* )? ')'
|
|
||||||
// Method ::= Identifier '.' Identifier ( '(' ( Expr ( ',' Expr )* )? ')' )
|
|
||||||
// Access ::= Identifier '.' Idnetifier
|
|
||||||
// Intrinsic ::= '@' Call
|
|
||||||
// Unary ::= UnaryOp ( Call | Intrinsic )
|
|
||||||
// Binary ::= Unary BinaryOp Unary
|
|
||||||
|
|
||||||
let identexpr = identifier().map_with_span(|ident, span| {( Expr::Identifier(ident), span )});
|
|
||||||
|
|
||||||
let call = atom.clone()
|
|
||||||
.then(
|
|
||||||
args.clone().delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.repeated())
|
|
||||||
.foldl(|name, args| {( Expr::Call { name: Box::new(name.clone()), args }, name.1 )}).labelled("call");
|
|
||||||
|
|
||||||
let intrinsic = just(Token::At).ignore_then(atom.clone())
|
|
||||||
.then(
|
|
||||||
args.clone().delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.repeated())
|
|
||||||
.foldl(|name, args| {( Expr::Intrinsic { name: Box::new(name.clone()), args }, name.1 )}).labelled("intrinsic");
|
|
||||||
|
|
||||||
let method = just(Token::Colon)
|
|
||||||
.ignore_then(identexpr.clone())
|
|
||||||
.then_ignore(just(Token::Dot))
|
|
||||||
.then(atom.clone())
|
|
||||||
.then(
|
|
||||||
args.clone().delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.repeated())
|
|
||||||
.map_with_span(|((name, method), args), span| {( Expr::Method {
|
|
||||||
obj: Box::new(name),
|
|
||||||
name: Box::new(method),
|
|
||||||
args: args.into_iter().flatten().collect()
|
|
||||||
}, span )}).labelled("method");
|
|
||||||
|
|
||||||
let access = just(Token::Semicolon)
|
|
||||||
.ignore_then(identexpr)
|
|
||||||
.then_ignore(just(Token::Dot))
|
|
||||||
.then(atom.clone())
|
|
||||||
.map_with_span(|(obj, name), span| {( Expr::Access { obj: Box::new(obj), name: Box::new(name) }, span )}).labelled("access");
|
|
||||||
|
|
||||||
let unary = choice((
|
|
||||||
just(Token::Minus).to(UnaryOp::Minus),
|
|
||||||
just(Token::Not).to(UnaryOp::Not)))
|
|
||||||
.repeated()
|
|
||||||
.then(call.or(intrinsic).or(method).or(access))
|
|
||||||
.foldr(|op, rhs| {( Expr::Unary { op, rhs: Box::new(rhs.clone()) }, rhs.1 )});
|
|
||||||
|
|
||||||
let factor = unary.clone().then(
|
|
||||||
choice((
|
|
||||||
just(Token::Multiply).to(BinaryOp::Multiply),
|
|
||||||
just(Token::Divide).to(BinaryOp::Divide),
|
|
||||||
just(Token::Modulus).to(BinaryOp::Modulus)))
|
|
||||||
.then(unary)
|
|
||||||
.repeated())
|
|
||||||
.foldl(|lhs, (op, rhs)| {(
|
|
||||||
Expr::Binary {
|
|
||||||
lhs: Box::new(lhs), op, rhs: Box::new(rhs.clone()),
|
|
||||||
}, rhs.1)});
|
|
||||||
|
|
||||||
let term = factor.clone().then(
|
|
||||||
choice((
|
|
||||||
just(Token::Plus).to(BinaryOp::Plus),
|
|
||||||
just(Token::Minus).to(BinaryOp::Minus)))
|
|
||||||
.then(factor)
|
|
||||||
.repeated())
|
|
||||||
.foldl(|lhs, (op, rhs)| {(
|
|
||||||
Expr::Binary {
|
|
||||||
lhs: Box::new(lhs), op, rhs: Box::new(rhs.clone()),
|
|
||||||
}, rhs.1)});
|
|
||||||
|
|
||||||
let compare = term.clone().then(
|
|
||||||
choice((
|
|
||||||
just(Token::Equal).to(BinaryOp::Equal),
|
|
||||||
just(Token::NotEqual).to(BinaryOp::NotEqual),
|
|
||||||
just(Token::Less).to(BinaryOp::Less),
|
|
||||||
just(Token::Greater).to(BinaryOp::Greater)))
|
|
||||||
.then(term)
|
|
||||||
.repeated())
|
|
||||||
.foldl(|lhs, (op, rhs)| {(
|
|
||||||
Expr::Binary {
|
|
||||||
lhs: Box::new(lhs), op, rhs: Box::new(rhs.clone()),
|
|
||||||
}, rhs.1)});
|
|
||||||
|
|
||||||
// Do ::= 'do' Expr* 'end'
|
|
||||||
// Define ::= Identifier ':' Typehint '=' Expr
|
|
||||||
// Redefine ::= 'set' Identifier '=' Expr
|
|
||||||
// Function ::= 'fun' Identifier ( Identifier* ) '(' ( Identifier ':' Typehint ( ',' Identifier ':' Typehint )* )? ')' ':' Typehint '=' Expr
|
|
||||||
// If ::= 'if' Expr '|' Expr '|' Expr
|
|
||||||
// Return ::= 'return' Expr
|
|
||||||
// Note: This section's `Expr` might actually mean `Expr | Do`
|
|
||||||
|
|
||||||
let do_block = expr.clone().repeated()
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::KwDo),
|
|
||||||
just(Token::KwEnd))
|
|
||||||
.map_with_span(|body, span| {( Expr::Do {body: (body, span.clone())}, span )});
|
|
||||||
|
|
||||||
let define = identifier()
|
|
||||||
// Type hint
|
|
||||||
.then(just(Token::Colon).ignore_then(typehint_parser()))
|
|
||||||
// Body
|
|
||||||
.then(just(Token::Assign).ignore_then(do_block.clone().or(expr.clone())))
|
|
||||||
.map_with_span(|((ident, typehint), expr), span| {
|
|
||||||
(Expr::Define { name: *Box::new(ident), typehint, value: Box::new(expr) }, span)
|
|
||||||
});
|
|
||||||
|
|
||||||
let redefine = just(Token::KwSet)
|
|
||||||
.ignore_then(identifier())
|
|
||||||
// Body
|
|
||||||
.then(just(Token::Assign).ignore_then(do_block.clone().or(expr.clone())))
|
|
||||||
.map_with_span(|(ident, expr), span| {
|
|
||||||
(Expr::Redefine { name: *Box::new(ident), value: Box::new(expr) }, span)
|
|
||||||
});
|
|
||||||
|
|
||||||
let function = just(Token::KwFun)
|
|
||||||
.ignore_then(identifier())
|
|
||||||
// Generics
|
|
||||||
.then(identifier().repeated())
|
|
||||||
// Arguments
|
|
||||||
.then(
|
|
||||||
identifier()
|
|
||||||
.then_ignore(just(Token::Colon))
|
|
||||||
.then(typehint_parser())
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::Open(Delimiter::Paren)),
|
|
||||||
just(Token::Close(Delimiter::Paren)))
|
|
||||||
.repeated())
|
|
||||||
// Return type hint
|
|
||||||
.then_ignore(just(Token::Colon))
|
|
||||||
.then(typehint_parser())
|
|
||||||
// Body
|
|
||||||
.then_ignore(just(Token::Assign))
|
|
||||||
.then(do_block.clone().or(expr.clone()))
|
|
||||||
.map(|((((name, generics), args), typehint), body)| {
|
|
||||||
( Expr::Function {
|
|
||||||
name: *Box::new(name),
|
|
||||||
generics,
|
|
||||||
args: args.into_iter().map(|(name, typehint)| {
|
|
||||||
(name, *Box::new(typehint))
|
|
||||||
}).collect(),
|
|
||||||
typehint,
|
|
||||||
body: Box::new(body.clone()) }, body.1 )});
|
|
||||||
|
|
||||||
let if_else = just(Token::KwIf)
|
|
||||||
// Condition
|
|
||||||
.ignore_then(expr.clone())
|
|
||||||
// True branch
|
|
||||||
.then_ignore(just(Token::Or))
|
|
||||||
.then(do_block.clone().or(expr.clone()))
|
|
||||||
// False branch
|
|
||||||
.then_ignore(just(Token::Or))
|
|
||||||
.then(do_block.clone().or(expr.clone()))
|
|
||||||
.map_with_span(|((cond, then), else_), span| {
|
|
||||||
(Expr::If { cond: Box::new(cond), t: Box::new(then), f: Box::new(else_) }, span)
|
|
||||||
});
|
|
||||||
|
|
||||||
let return_ = just(Token::KwReturn)
|
|
||||||
.ignore_then(expr.clone())
|
|
||||||
.map_with_span(|expr, span| {( Expr::Return(Box::new(expr)), span )});
|
|
||||||
|
|
||||||
// Expr ::= Define
|
|
||||||
// | Redefine
|
|
||||||
// | Function
|
|
||||||
// | Do
|
|
||||||
// | Return
|
|
||||||
// | If
|
|
||||||
// | Binary
|
|
||||||
|
|
||||||
define
|
|
||||||
.or(redefine)
|
|
||||||
.or(function)
|
|
||||||
.or(do_block)
|
|
||||||
.or(return_)
|
|
||||||
.or(if_else)
|
|
||||||
.or(compare)
|
|
||||||
}).labelled("expression")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn parse(tokens: Vec<(Token, std::ops::Range<usize>)>, len: usize) -> (Option<Vec<(Expr, std::ops::Range<usize>)>>, Vec<Simple<Token>>) {
|
|
||||||
let (ast, parse_error) = expr_parser().repeated().then_ignore(end()).parse_recovery(Stream::from_iter(
|
|
||||||
len..len + 1,
|
|
||||||
tokens.into_iter(),
|
|
||||||
));
|
|
||||||
|
|
||||||
(ast, parse_error)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "transformer"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syntax = { path = "../syntax" }
|
|
Loading…
Reference in a new issue