mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
rewrite xdxdxdxdxdx
This commit is contained in:
parent
a5f5725cdf
commit
ffd922759e
317
Cargo.lock
generated
317
Cargo.lock
generated
|
@ -11,38 +11,6 @@ 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"
|
||||||
|
@ -58,41 +26,11 @@ 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 = [
|
||||||
"hir",
|
"syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -123,22 +61,11 @@ 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.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -146,57 +73,11 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hzc"
|
||||||
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 = [
|
||||||
"parser",
|
"codegen",
|
||||||
]
|
"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]]
|
||||||
|
@ -205,65 +86,11 @@ 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.119"
|
version = "0.2.124"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
|
||||||
|
|
||||||
[[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"
|
||||||
|
@ -272,72 +99,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "syntax"
|
||||||
version = "1.0.36"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"chumsky",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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"
|
||||||
|
@ -348,72 +115,14 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "transformer"
|
||||||
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 = [
|
||||||
"hir",
|
"syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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"
|
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,10 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/main",
|
"core",
|
||||||
"crates/lexer",
|
"syntax",
|
||||||
"crates/parser",
|
"transformer",
|
||||||
"crates/diagnostic",
|
"codegen",
|
||||||
"crates/hir",
|
|
||||||
"crates/typecheck",
|
|
||||||
"crates/codegen",
|
|
||||||
]
|
]
|
23
README.md
23
README.md
|
@ -1,34 +1,25 @@
|
||||||
# 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
|
||||||
- `deno` for running Typescript
|
- `node`/`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
|
||||||
|
@ -37,7 +28,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! `path/to/executable compile path/to/file.hz`
|
3) Try running some examples! `hzc 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/hazure -f
|
rm ~/bin/hzc -f
|
||||||
mv ~/.cache/hazure/build/target/debug/hazure ~/bin/hazure
|
mv ~/.cache/hazure/build/target/debug/hzc ~/bin/hzc
|
||||||
else
|
else
|
||||||
log "Building..."
|
log "Building..."
|
||||||
cargo build --release
|
cargo build --release
|
||||||
rm ~/bin/hazure -f
|
rm ~/bin/hzc -f
|
||||||
mv ~/.cache/hazure/build/target/release/hazure ~/bin/hazure
|
mv ~/.cache/hazure/build/target/release/hzc ~/bin/hzc
|
||||||
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."
|
||||||
hazure -h
|
hzc -v
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
[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]
|
||||||
hir = { path = "../hir" }
|
syntax = { path = "../syntax" }
|
186
codegen/src/lib.rs
Normal file
186
codegen/src/lib.rs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
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(" | "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
core/Cargo.toml
Normal file
8
core/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "hzc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syntax = { path = "../syntax" }
|
||||||
|
codegen = { path = "../codegen" }
|
42
core/src/main.rs
Normal file
42
core/src/main.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::{fs::File, io::Write};
|
||||||
|
|
||||||
|
use syntax::{lex::lex, parse::parse};
|
||||||
|
use codegen::Codegen;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let time = std::time::Instant::now();
|
||||||
|
|
||||||
|
let input = "
|
||||||
|
fun len T (vec : [T]) : int = return ;vec.length
|
||||||
|
|
||||||
|
@write(len([1, 2, 3]))
|
||||||
|
";
|
||||||
|
//
|
||||||
|
// 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.rs").unwrap();
|
||||||
|
file.write_all(codegen.emitted.join("\n").as_bytes()).unwrap();
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
pub mod ts;
|
|
|
@ -1,186 +0,0 @@
|
||||||
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!() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,180 +0,0 @@
|
||||||
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();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,480 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "lexer"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
chumsky = "0.8.0"
|
|
|
@ -1,172 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,33 +0,0 @@
|
||||||
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>,
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,145 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
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,389 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
[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" }
|
|
|
@ -1,123 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
|
@ -1,3 +0,0 @@
|
||||||
fun main: void = do
|
|
||||||
@write("Hello, World!")
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
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
|
|
|
@ -1,113 +0,0 @@
|
||||||
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
|
|
|
@ -1,33 +0,0 @@
|
||||||
-- 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
|
|
11
examples/test.hz
Normal file
11
examples/test.hz
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fun foo (a : int) (b : int) : int = do
|
||||||
|
c : int = a + b
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
bar = foo(34, 35)
|
||||||
|
@println(bar)
|
||||||
|
|
||||||
|
if bar == 69
|
||||||
|
| @println("That's 69")
|
||||||
|
| @println("Ok")
|
15
examples/type.hz
Normal file
15
examples/type.hz
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
type Left E = { tag: "Left", value: E }
|
||||||
|
type Right T = { tag: "Right", value: T }
|
||||||
|
|
||||||
|
type Either T E = Left E
|
||||||
|
| Right T
|
||||||
|
|
||||||
|
it : Either<Int, String> = { tag: "Left", value: "Uninit" }
|
||||||
|
|
||||||
|
fun rand_it : void = if @random() > 0.5
|
||||||
|
| set it = { tag: "Left", value: "Error" }
|
||||||
|
| set it = { tag: "Right", value: 42 }
|
||||||
|
|
||||||
|
rand()
|
||||||
|
@println(it.tag, it.value)
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export function eqTuples(a: any[], b: any[]) {
|
|
||||||
if (a.length !== b.length) return false;
|
|
||||||
return a.every((elem, i) => b[i] === elem);
|
|
||||||
}
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
|
@ -1,9 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "parser"
|
name = "syntax"
|
||||||
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"
|
116
syntax/src/ast.rs
Normal file
116
syntax/src/ast.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
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 {
|
||||||
|
}
|
120
syntax/src/lex.rs
Normal file
120
syntax/src/lex.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
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)
|
||||||
|
}
|
5
syntax/src/lib.rs
Normal file
5
syntax/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#![feature(trait_alias)]
|
||||||
|
|
||||||
|
pub mod lex;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod ast;
|
3
syntax/src/mod.rs
Normal file
3
syntax/src/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod lex;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod ast;
|
309
syntax/src/parse.rs
Normal file
309
syntax/src/parse.rs
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
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)
|
||||||
|
}
|
7
transformer/Cargo.toml
Normal file
7
transformer/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "transformer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syntax = { path = "../syntax" }
|
0
transformer/src/lib.rs
Normal file
0
transformer/src/lib.rs
Normal file
Loading…
Reference in a new issue