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