:trollface:

pull/4/head
azur 2022-12-13 23:02:41 +07:00
parent 3768562c52
commit 89db9e8b1e
27 changed files with 121 additions and 1072 deletions

136
Cargo.lock generated
View File

@ -12,14 +12,12 @@ dependencies = [
]
[[package]]
name = "atty"
version = "0.2.14"
name = "ariadne"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
dependencies = [
"hermit-abi",
"libc",
"winapi",
"yansi",
]
[[package]]
@ -28,13 +26,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "checker"
version = "0.1.0"
dependencies = [
"syntax",
]
[[package]]
name = "chumsky"
version = "0.8.0"
@ -45,28 +36,18 @@ dependencies = [
]
[[package]]
name = "codegen"
name = "compiler"
version = "0.1.0"
dependencies = [
"syntax",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
"parser",
"vm",
]
[[package]]
name = "const-random"
version = "0.1.13"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
dependencies = [
"const-random-macro",
"proc-macro-hack",
@ -74,12 +55,12 @@ dependencies = [
[[package]]
name = "const-random-macro"
version = "0.1.13"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
dependencies = [
"getrandom",
"lazy_static",
"once_cell",
"proc-macro-hack",
"tiny-keccak",
]
@ -91,10 +72,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "getrandom"
version = "0.2.6"
name = "entry"
version = "0.1.0"
dependencies = [
"compiler",
"parser",
"vm",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
@ -102,49 +98,31 @@ dependencies = [
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
name = "libc"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "hzc"
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "parser"
version = "0.1.0"
dependencies = [
"checker",
"codegen",
"colored",
"syntax",
"ariadne",
"chumsky",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "syntax"
version = "0.1.0"
dependencies = [
"chumsky",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@ -155,36 +133,20 @@ dependencies = [
]
[[package]]
name = "transformer"
name = "vm"
version = "0.1.0"
dependencies = [
"syntax",
"fnv",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
name = "yansi"
version = "0.5.1"
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-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View File

@ -1,8 +1,7 @@
[workspace]
members = [
"core",
"syntax",
"checker",
"transformer",
"codegen",
]
"entry",
"parser",
"compiler",
"vm",
]

View File

@ -1,34 +0,0 @@
# Hazure
Programming language that compiles to Typescript!
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
- 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
```
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
Found a bug? Found a better way to do something? Make a pull request or tell me in the issues tab! Anything contributions helps :D
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`
# 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)

View File

@ -1,61 +0,0 @@
#!/usr/bin/env bash
# Exit if subprocess return non-zero exit code
set -e
# Log function
log () {
echo -e "\033[0;32m[LOG]\033[0m $1"
}
err () {
echo -e "\033[0;31m[ERR]\033[0m $1"
}
# This will always be true unless there is
# missing executable that we need to use
install_pass=true
# Check if $1 is installed
check_installed () {
if ! command -v $1 -h &> /dev/null
then
err "$1 is not installed"
if [ install_pass ]; then
install_pass=false
fi
fi
}
check_installed cargo
check_installed git
check_installed deno # deno is required for running transpiled program
# If all of the above is installed
if [ ! install_pass ]; then
exit 1
fi
log "Dependencies is installed. Cloning..."
rm -rf ~/.cache/hazure/build/
git clone https://github.com/azur1s/hazure.git ~/.cache/hazure/build/
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
else
log "Building..."
cargo build --release
rm ~/bin/hzc -f
mv ~/.cache/hazure/build/target/release/hzc ~/bin/hzc
fi
log "Build done. Cleaning up..."
rm -rf ~/.cache/hazure/build/
log "Done."
hzc -v

View File

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

View File

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

View File

View File

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

View File

@ -1,192 +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>,
/// Finalized code in bytes
pub finalized: Vec<u8>,
}
impl Default for Codegen { fn default() -> Self { Self::new() } }
impl Codegen {
pub fn new() -> Codegen {
Codegen { emitted: Vec::new(), finalized: 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) | 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(" | "),
}
}
/// Finalize the code generation.
pub fn finalize(&mut self) {
self.finalized = self.emitted.join("\n").as_bytes().to_vec();
}
}

View File

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

View File

@ -1,48 +0,0 @@
use std::{fs::File, io::Write};
use syntax::{lex::lex, parse::parse};
use codegen::Codegen;
pub mod util;
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;
}
println!("{:#?}", ast.as_ref().unwrap());
info!("Parsed in {}ms", time.elapsed().as_millis());
//
// Codegen
//
let mut codegen = Codegen::new();
codegen.gen(ast.unwrap());
codegen.finalize();
let mut file = File::create("out.ts").unwrap();
file.write_all(&codegen.finalized).unwrap();
info!("Generated {} bytes in {} ms", codegen.finalized.len(), time.elapsed().as_millis());
}

View File

@ -1,38 +0,0 @@
use colored::Colorize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogLevel { Debug, Info, Warn, Error }
fn prefix (level: LogLevel) -> String {
match level {
LogLevel::Debug => "DEBG ".bright_black(),
LogLevel::Info => "INFO ".blue(),
LogLevel::Warn => "WARN ".yellow(),
LogLevel::Error => "ERRO ".red(),
}.to_string()
}
pub fn log <S: Into<String>>(level: LogLevel, message: S) {
println!("{}{}", prefix(level), message.into());
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
$crate::util::log( $crate::util::LogLevel::Info, format!($($arg)*) );
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::util::log( $crate::util::LogLevel::Warn, format!($($arg)*) );
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::util::log( $crate::util::LogLevel::Error, format!($($arg)*) );
};
}

16
examples/a.sial Normal file
View File

@ -0,0 +1,16 @@
fun succ x = do
let one = 1
let y = x + one in y
end
fun double x = x * 2
fun main = do
let add = \x y -> x + y in
succ(34)
|> \x -> add(34, x)
|> \x -> println(x)
let result = add(34, 35)
println(result)
end

View File

@ -1,7 +0,0 @@
factorial (n : int) : int =
if n == 0
| return 1
| return n * factorial(n - 1)
result : int = factorial(5)
@write(result)

View File

@ -1,6 +0,0 @@
print T (x : T) : void = @write(x)
a : int = 123
print(a)
b : string = "Hello, World!"
print(b)

42
examples/ht.sial Normal file
View File

@ -0,0 +1,42 @@
import http from "http"
-- Define a custom type to represent a user
type User =
id: number,
name: string,
end
-- Define a function to handle incoming HTTP requests
fun handle_request
req: http.IncomingMessage,
res: http.ServerResponse,
= do
let user_id = req.url.split("/")[1]
let name =
match user_id
| 12345 -> Some("John Smith")
| 727 -> Some("Foo Bar")
else None
match name
| Some name -> do
let user = User(user_id, name)
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.write(JSON.stringify(user))
end
| None -> do
res.statusCode = 404
res.write("User not found")
end
res.end()
end
fun main = do
let
port = 8080,
server = http.createServer(handle_request),
in
server.listen(port, fun -> println("HTTP server listening on port 8080"))
end

8
examples/sim.sial Normal file
View File

@ -0,0 +1,8 @@
fun foo = \x -> x
fun main = do
foo(1)
[1, 2, 3]
true
print("Hello, World")
end

View File

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

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
version = "Two"

View File

@ -1,7 +0,0 @@
[package]
name = "syntax"
version = "0.1.0"
edition = "2021"
[dependencies]
chumsky = "0.8.0"

View File

@ -1,111 +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>>
},
Do {
body: Spanned<Vec<Spanned<Self>>>
},
Return(Box<Spanned<Self>>),
}
#[derive(Clone, Debug)]
pub enum Stmt {
}

View File

@ -1,121 +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, Hash,
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),
just('#').to(Token::Hash),
));
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,311 +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::Hash)
.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::Colon)
.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 = identifier()
// Generic
.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