1
1
Fork 0
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.

42 changed files with 2419 additions and 835 deletions

317
Cargo.lock generated
View file

@ -11,6 +11,38 @@ dependencies = [
"const-random", "const-random",
] ]
[[package]]
name = "ariadne"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
dependencies = [
"yansi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -26,11 +58,41 @@ dependencies = [
"ahash", "ahash",
] ]
[[package]]
name = "clap"
version = "3.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "codegen" name = "codegen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"syntax", "hir",
] ]
[[package]] [[package]]
@ -61,11 +123,22 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "diagnostic"
version = "0.1.0"
dependencies = [
"ariadne",
"chumsky",
"hir",
"lexer",
"typecheck",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.6" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -73,11 +146,57 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hzc" name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hazure"
version = "0.1.1"
dependencies = [
"clap",
"codegen",
"diagnostic",
"hir",
"lexer",
"parser",
"serde",
"serde_derive",
"toml",
"typecheck",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hir"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"codegen", "parser",
"syntax", ]
[[package]]
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
] ]
[[package]] [[package]]
@ -86,11 +205,65 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexer"
version = "0.1.0"
dependencies = [
"chumsky",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.124" version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "parser"
version = "0.1.0"
dependencies = [
"chumsky",
"lexer",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro-hack" name = "proc-macro-hack"
@ -99,12 +272,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "syntax" name = "proc-macro2"
version = "0.1.0" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [ dependencies = [
"chumsky", "unicode-xid",
] ]
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "tiny-keccak" name = "tiny-keccak"
version = "2.0.2" version = "2.0.2"
@ -115,14 +348,72 @@ dependencies = [
] ]
[[package]] [[package]]
name = "transformer" name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "typecheck"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"syntax", "hir",
] ]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.2+wasi-snapshot-preview1" version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"

View file

@ -1,7 +1,10 @@
[workspace] [workspace]
members = [ members = [
"core", "crates/main",
"syntax", "crates/lexer",
"transformer", "crates/parser",
"codegen", "crates/diagnostic",
] "crates/hir",
"crates/typecheck",
"crates/codegen",
]

View file

@ -1,25 +1,34 @@
# Hazure # Hazure
Programming language that compiles to Typescript! Programming language that compiles to Typescript!
```sml
fun main : void = do
@write("Hello, World!")
end
```
or with the pipeline operator:
```sml
fun main : void = do
"Hello, World!\n"
|> @write(_)
end
```
Note: Everything in this project can be changed at anytime! (I'm still finding out what work best for lots of thing) if you have an idea, feel free to create an issues about it, or even create a PR! (I'd be very happy) Note: Everything in this project can be changed at anytime! (I'm still finding out what work best for lots of thing) if you have an idea, feel free to create an issues about it, or even create a PR! (I'd be very happy)
# Prerequistie # Prerequistie
- `node`/`deno` for running Typescript - `deno` for running Typescript
- Rust (if you're going to build from source) - Rust (if you're going to build from source)
- (Optional) if you use Vim, you can get the syntax highlighting [here](https://github.com/azur1s/hazure.vim) - (Optional) if you use Vim, you can get the syntax highlighting [here](https://github.com/azur1s/hazure.vim)
# Installing # Installing
Currently there is only a build script on linux: Currently there is only a build script on linux:
``` ```
$ curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s
...
$ hzc --help
``` ```
or if you want to build in debug mode: or if you want to build in debug mode:
``` ```
curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s d curl -s https://raw.githubusercontent.com/azur1s/hazure/master/build.sh | bash -s d
...
$ hzc --help
``` ```
# Contributing # Contributing
@ -28,7 +37,7 @@ Found a bug? Found a better way to do something? Make a pull request or tell me
Steps to build: Steps to build:
1) Clone this repo `https://github.com/azur1s/hazure.git` 1) Clone this repo `https://github.com/azur1s/hazure.git`
2) Build executable `cargo build` 2) Build executable `cargo build`
3) Try running some examples! `hzc compile path/to/file.hz` 3) Try running some examples! `path/to/executable compile path/to/file.hz`
# License # License
Hazure is licensed under both [MIT license](https://github.com/azur1s/hazure/blob/master/LICENSE-MIT) and [Apache License](https://github.com/azur1s/hazure/blob/master/LICENSE-APACHE) Hazure is licensed under both [MIT license](https://github.com/azur1s/hazure/blob/master/LICENSE-MIT) and [Apache License](https://github.com/azur1s/hazure/blob/master/LICENSE-APACHE)

View file

@ -44,13 +44,13 @@ cd ~/.cache/hazure/build/
if [[ $1 == *"d"* ]]; then if [[ $1 == *"d"* ]]; then
log "Building in debug..." log "Building in debug..."
cargo build cargo build
rm ~/bin/hzc -f rm ~/bin/hazure -f
mv ~/.cache/hazure/build/target/debug/hzc ~/bin/hzc mv ~/.cache/hazure/build/target/debug/hazure ~/bin/hazure
else else
log "Building..." log "Building..."
cargo build --release cargo build --release
rm ~/bin/hzc -f rm ~/bin/hazure -f
mv ~/.cache/hazure/build/target/release/hzc ~/bin/hzc mv ~/.cache/hazure/build/target/release/hazure ~/bin/hazure
fi fi
log "Build done. Cleaning up..." log "Build done. Cleaning up..."
@ -58,4 +58,4 @@ log "Build done. Cleaning up..."
rm -rf ~/.cache/hazure/build/ rm -rf ~/.cache/hazure/build/
log "Done." log "Done."
hzc -v hazure -h

View file

@ -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(" | "),
}
}
}

View file

@ -1,8 +0,0 @@
[package]
name = "hzc"
version = "0.1.0"
edition = "2021"
[dependencies]
syntax = { path = "../syntax" }
codegen = { path = "../codegen" }

View file

@ -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();
}

View file

@ -1,7 +1,8 @@
[package] [package]
name = "codegen" name = "codegen"
license = "MIT OR Apache-2.0"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
syntax = { path = "../syntax" } hir = { path = "../hir" }

View file

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

186
crates/codegen/src/ts.rs Normal file
View 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!() },
}
}
}

View 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" }

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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),
}
}

View file

@ -1,7 +1,9 @@
[package] [package]
name = "syntax" name = "parser"
license = "MIT OR Apache-2.0"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
lexer = { path = "../lexer" }
chumsky = "0.8.0" chumsky = "0.8.0"

389
crates/parser/src/lib.rs Normal file
View 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)
}

View 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)
}

View 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
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
fun main: void = do
@write("Hello, World!")
end

23
example/map.hz Normal file
View 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
View 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
View 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

View file

@ -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
View 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
View 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);
}

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View file

@ -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 {
}

View file

@ -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)
}

View file

@ -1,5 +0,0 @@
#![feature(trait_alias)]
pub mod lex;
pub mod parse;
pub mod ast;

View file

@ -1,3 +0,0 @@
pub mod lex;
pub mod parse;
pub mod ast;

View file

@ -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)
}

View file

@ -1,7 +0,0 @@
[package]
name = "transformer"
version = "0.1.0"
edition = "2021"
[dependencies]
syntax = { path = "../syntax" }

View file