mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
Compare commits
2 commits
24dbfc23ff
...
dff9e03ea9
Author | SHA1 | Date | |
---|---|---|---|
Natapat Samutpong | dff9e03ea9 | ||
Natapat Samutpong | 71f313dbe1 |
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,5 +10,4 @@ target/
|
|||
*.pdb
|
||||
|
||||
# Generated by the compiler
|
||||
/*.cpp
|
||||
/*.out
|
||||
/*.ts
|
11
Makefile
11
Makefile
|
@ -1,11 +0,0 @@
|
|||
build-debug:
|
||||
@echo "Building executable (debug)... done"
|
||||
cargo build
|
||||
cp ./target/debug/hazure ~/bin/hazure -r
|
||||
@echo "Building executable (debug)... done"
|
||||
|
||||
build-lib:
|
||||
@echo "Building lib..."
|
||||
rm -rf /usr/include/hazure/
|
||||
cp ./lib/. /usr/include/hazure/ -r
|
||||
@echo "Building lib... done"
|
45
README.md
45
README.md
|
@ -1,24 +1,25 @@
|
|||
# Hazure
|
||||
Programming language that compiles to C++!
|
||||
Programming language that compiles to Typescript!
|
||||
|
||||
```sml
|
||||
fun main: int = do
|
||||
@write("Hello, World!\n");
|
||||
return 69;
|
||||
fun main: void = do
|
||||
@write("Hello, World!");
|
||||
end;
|
||||
```
|
||||
or with the pipe operator:
|
||||
```sml
|
||||
fun main: int = do
|
||||
fun main: void = do
|
||||
"Hello, World!\n"
|
||||
|> @write(_);
|
||||
return 69;
|
||||
end;
|
||||
```
|
||||
> The `return 69` is the exit code (like C++), try running `echo $?` to see it!
|
||||
|
||||
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
|
||||
- `deno`
|
||||
- Rust (if you're going to build from source)
|
||||
|
||||
# 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
|
||||
|
||||
|
@ -26,9 +27,8 @@ Wanna see how it works under the hood? see the [How it works](https://github.com
|
|||
|
||||
Steps to build:
|
||||
1) Clone this repo `https://github.com/azur1s/hazure.git`
|
||||
2) Run `sudo make build-lib` to build the library (for the transpiled output)
|
||||
3) Build executable `cargo build`
|
||||
4) Try running some examples! `path/to/executable compile path/to/file.hz`
|
||||
2) Build executable `cargo build`
|
||||
3) Try running some examples! `path/to/executable compile path/to/file.hz`
|
||||
|
||||
# How it works
|
||||
```
|
||||
|
@ -62,25 +62,12 @@ Steps to build:
|
|||
Pass
|
||||
│
|
||||
│
|
||||
Command Codegen produce C++
|
||||
(spawn) │ crates/codegen
|
||||
│ │
|
||||
│ │
|
||||
clang++ ─────┴───── Executable
|
||||
(Command)
|
||||
```
|
||||
|
||||
# Prerequistie
|
||||
- `clang++`(preferred, default) or any C++ compiler
|
||||
- `make` for Makefile
|
||||
- Rust (if you're going to build from source)
|
||||
|
||||
# Configuration
|
||||
You can also configurate Hades compiler (currently you can only change the C++ compiler). Make a new file called `hades.toml` in the current working directory and the compiler will look for it! if there isn't one then it will use the default configuration:
|
||||
```toml
|
||||
[compiler]
|
||||
compiler = "clang++"
|
||||
Codegen produce Typescript
|
||||
│ crates/codegen
|
||||
│
|
||||
Done
|
||||
(filename.ts)
|
||||
```
|
||||
|
||||
# License
|
||||
Hades is licensed under both [MIT license](https://github.com/azur1s/hades/blob/master/LICENSE-MIT) and [Apache License](https://github.com/azur1s/hades/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)
|
|
@ -1 +1 @@
|
|||
pub mod cpp;
|
||||
pub mod ts;
|
|
@ -2,13 +2,6 @@ use std::fmt::Display;
|
|||
|
||||
use hir::{IR, IRKind, Value};
|
||||
|
||||
const MODULE_INCLUDES: [&str; 4] = [
|
||||
"\"hazure/io.hpp\"",
|
||||
"\"hazure/time.hpp\"",
|
||||
"<stdbool.h>",
|
||||
"<string>",
|
||||
];
|
||||
|
||||
pub struct Codegen {
|
||||
pub emitted: String,
|
||||
}
|
||||
|
@ -25,23 +18,23 @@ impl Codegen {
|
|||
pub fn gen(&mut self, irs: Vec<IR>) {
|
||||
self.emit(format!("// Auto-generated by hazure compiler version {}\n", env!("CARGO_PKG_VERSION")));
|
||||
|
||||
for module in MODULE_INCLUDES {
|
||||
self.emit(format!("#include {}\n", module));
|
||||
}
|
||||
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 { name, type_hint, value } => {
|
||||
format!(
|
||||
"{} {} = {}{}\n",
|
||||
type_hint,
|
||||
"const {}: {} = {}{}\n",
|
||||
name,
|
||||
type_hint,
|
||||
self.gen_ir(value, false),
|
||||
semicolon!()
|
||||
)
|
||||
|
@ -63,13 +56,8 @@ impl Codegen {
|
|||
|
||||
IRKind::Intrinsic { name, args } => {
|
||||
match name.as_str() {
|
||||
"write" => { format!("hazure_write({}){}\n", self.gen_ir(&args[0], false), semicolon!()) },
|
||||
"read" => { format!("hazure_read(){}\n", semicolon!()) },
|
||||
|
||||
"write_file" => { format!("hazure_write({}){}\n", self.gen_ir(&args[0], false), semicolon!()) },
|
||||
"read_file" => { format!("hazure_read_file({}){}\n", self.gen_ir(&args[0], false), semicolon!()) },
|
||||
|
||||
"time" => { format!("hazure_get_time(){}\n", semicolon!()) },
|
||||
"write" => { format!("console.log({}){}\n", self.gen_ir(&args[0], false), semicolon!()) },
|
||||
"read" => { todo!() },
|
||||
_ => unreachable!(format!("Unknown intrinsic: {}", name)) // Shoul be handled by lowering
|
||||
}
|
||||
},
|
||||
|
@ -77,14 +65,14 @@ impl Codegen {
|
|||
IRKind::Fun { name, return_type_hint, args, body } => {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| format!("{} {}", arg.1, arg.0))
|
||||
.map(|arg| format!("{}: {}", arg.0, arg.1))
|
||||
.collect::<Vec<_>>().
|
||||
join(", ");
|
||||
format!(
|
||||
"{} {}({}) {{\n{}\n}}\n",
|
||||
return_type_hint,
|
||||
"const {} = ({}): {} => {{\n{}\n}};\n",
|
||||
name,
|
||||
args,
|
||||
return_type_hint,
|
||||
self.gen_ir(body, false)
|
||||
)
|
||||
},
|
|
@ -1,12 +1,9 @@
|
|||
use std::ops::Range;
|
||||
use parser::Expr;
|
||||
|
||||
const INTRINSICS: [&str; 5] = [
|
||||
const INTRINSICS: [&str; 2] = [
|
||||
"write",
|
||||
"read",
|
||||
"write_file",
|
||||
"read_file",
|
||||
"time",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -285,9 +282,10 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
|||
|
||||
fn gen_type_hint(type_hint: &str) -> String {
|
||||
match type_hint {
|
||||
"int" => "int".to_string(),
|
||||
"bool" => "bool".to_string(),
|
||||
"string" => "std::string".to_string(),
|
||||
"int" => "number".to_string(),
|
||||
"bool" => "boolean".to_string(),
|
||||
"string" => "string".to_string(),
|
||||
"void" => "void".to_string(),
|
||||
_ => { dbg!(type_hint); todo!() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ pub enum Token {
|
|||
String(String), Identifier(String),
|
||||
|
||||
// Operators
|
||||
Plus, Minus, Multiply, Divide,
|
||||
Plus, Minus, Multiply, Divide, Modulus,
|
||||
Not, Equal, NotEqual, Less, Greater,
|
||||
Pipe,
|
||||
|
||||
|
@ -47,6 +47,7 @@ impl std::fmt::Display for Token {
|
|||
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, "!="),
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
use serde_derive::Deserialize;
|
||||
use toml::{from_str, de::Error};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub compiler: CompilerOptions,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CompilerOptions {
|
||||
pub compiler: String,
|
||||
}
|
||||
|
||||
pub fn default_config() -> Config {
|
||||
Config {
|
||||
compiler: CompilerOptions {
|
||||
compiler: "clang++".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_config(toml_content: &str) -> Result<Config, Error> {
|
||||
let config: Config = from_str(toml_content)?;
|
||||
Ok(config)
|
||||
}
|
|
@ -6,43 +6,22 @@ use lexer::lex;
|
|||
use parser::parse;
|
||||
use diagnostic::Diagnostics;
|
||||
use hir::ast_to_ir;
|
||||
use codegen::cpp;
|
||||
use codegen::ts;
|
||||
|
||||
pub mod args;
|
||||
use args::{Args, Options};
|
||||
|
||||
pub mod config;
|
||||
|
||||
pub mod util;
|
||||
use crate::util::log;
|
||||
|
||||
fn main() {
|
||||
let config_file = fs::read_to_string("./hazure.toml");
|
||||
let config: config::Config;
|
||||
match config_file {
|
||||
Ok(content) => {
|
||||
let parsed = config::parse_config(&content);
|
||||
match parsed {
|
||||
Ok(c) => config = c,
|
||||
Err(e) => {
|
||||
log(2, format!("{}", e));
|
||||
config = config::default_config();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log(1, format!("Error reading config file: {}, using default config", e));
|
||||
config = config::default_config();
|
||||
}
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
match args.options {
|
||||
Options::Compile {
|
||||
input: file_name,
|
||||
ast: print_ast,
|
||||
log: should_log,
|
||||
output,
|
||||
output: _output, // TODO: Custom output file
|
||||
} => {
|
||||
// Macro to only log if `should_log` is true
|
||||
macro_rules! logif {
|
||||
|
@ -93,37 +72,26 @@ fn main() {
|
|||
}
|
||||
|
||||
// Generate code
|
||||
let mut codegen = cpp::Codegen::new();
|
||||
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("cpp").file_name().unwrap().to_os_string().into();
|
||||
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");
|
||||
|
||||
// Compile the generate code
|
||||
let compiler = &config.compiler.compiler;
|
||||
Command::new(compiler)
|
||||
.arg(&output_path)
|
||||
.arg(format!("-o{}", match output {
|
||||
Some(o) => o.display().to_string(),
|
||||
None => file_name
|
||||
.with_extension("out")
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
}))
|
||||
.spawn()
|
||||
.expect("Failed to run compiler");
|
||||
|
||||
// End timer
|
||||
let duration = start.elapsed().as_millis();
|
||||
|
||||
logif!(0, format!("Compilation took {}ms", duration));
|
||||
logif!(0, format!("Wrote output to `{}`", output_path.display()));
|
||||
|
||||
Command::new("chmod")
|
||||
.arg("+x")
|
||||
.arg(&output_path)
|
||||
.spawn()
|
||||
.expect("Failed to chmod file");
|
||||
},
|
||||
None => { unreachable!(); }
|
||||
}
|
||||
|
|
|
@ -124,7 +124,8 @@ fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>
|
|||
.then(
|
||||
choice((
|
||||
just(Token::Multiply),
|
||||
just(Token::Divide)))
|
||||
just(Token::Divide),
|
||||
just(Token::Modulus)))
|
||||
.then(unary)
|
||||
.repeated())
|
||||
.foldl(|lhs, (op, rhs)| {
|
||||
|
|
|
@ -2,10 +2,9 @@ fun add2 (lhs: int) (rhs: int): int = do
|
|||
return lhs + rhs;
|
||||
end;
|
||||
|
||||
fun main: int = do
|
||||
fun main: void = do
|
||||
let result: int = add2(34, 35);
|
||||
@write(result);
|
||||
@write("\n");
|
||||
if result == 69 then
|
||||
@write("big cool")
|
||||
else
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
fun main: int = do
|
||||
fun main: void = do
|
||||
@writse(); -- Unknown intrinsic
|
||||
end;
|
|
@ -1,5 +1,3 @@
|
|||
fun main: int = do
|
||||
-- Normal way of hello world
|
||||
@write("Hello, World!\n");
|
||||
return 69;
|
||||
fun main: void = do
|
||||
@write("Hello, World!");
|
||||
end;
|
|
@ -1,4 +1,4 @@
|
|||
fun main: int = do
|
||||
fun main: void = do
|
||||
if true then
|
||||
@write("True")
|
||||
else
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
fun foo (xs: int): int = return xs + 1;
|
||||
fun bar (xs: int) (x: int): int = return xs - x;
|
||||
|
||||
fun main: int = do
|
||||
fun main: void = do
|
||||
foo(69) -- 69 + 1 => 70
|
||||
|> bar(_, 1) -- '70 - 1 => 69
|
||||
|> @write(_); -- '69 => stdout
|
||||
|
||||
@write("\n");
|
||||
|
||||
foo(60) -- 60 + 1 => 61
|
||||
|> bar(130, _) -- 130 - '61 => 69
|
||||
|> @write(_); -- '69 => stdout
|
||||
|
|
15
example/pipe2.hz
Normal file
15
example/pipe2.hz
Normal file
|
@ -0,0 +1,15 @@
|
|||
fun foo (xs: int) : int = return xs + 1;
|
||||
fun bar (xs: int) (ys: int) : int = return xs + ys;
|
||||
fun baz (xs: int) (ys: int) (zs: int): int = return xs + ys + zs;
|
||||
fun qux (xs: int) : int = return xs - 1;
|
||||
fun quux (xs: int) (xy: int) : int = return xs - 2 + xy;
|
||||
|
||||
fun main: void = do
|
||||
66
|
||||
|> foo(_)
|
||||
|> bar(_, 1)
|
||||
|> baz(1, 2, _)
|
||||
|> qux(_)
|
||||
|> quux(1, _)
|
||||
|> @write(_);
|
||||
end;
|
|
@ -1,4 +0,0 @@
|
|||
fun main: int = do
|
||||
@read_file("./example/quine.hz") -- Run this from root folder
|
||||
|> @write(_);
|
||||
end;
|
|
@ -1,9 +0,0 @@
|
|||
fun main: int = do
|
||||
@write("Enter your name: ");
|
||||
let name: string = "";
|
||||
@read(name);
|
||||
|
||||
@write("Hello ");
|
||||
@write(name);
|
||||
@write("!");
|
||||
end;
|
|
@ -1,4 +0,0 @@
|
|||
fun main: int = do
|
||||
@time()
|
||||
|> @write(_);
|
||||
end;
|
|
@ -1,2 +0,0 @@
|
|||
[compiler]
|
||||
compiler = "clang++"
|
53
lib/io.hpp
53
lib/io.hpp
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
template<typename T>
|
||||
/**
|
||||
* Read the value from stdin and return it.
|
||||
*/
|
||||
T hazure_read() {
|
||||
T x;
|
||||
std::cin >> x;
|
||||
return x;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
/**
|
||||
* Prints the value of the variable to the stdout.
|
||||
*
|
||||
* @param value The value to print.
|
||||
*/
|
||||
void hazure_write(T x) {
|
||||
std::cout << x;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the value from the file and return it.
|
||||
*
|
||||
* @param file_name The name of the file to read from.
|
||||
* @return std::string The value read from the file.
|
||||
*/
|
||||
std::string hazure_read_file(std::string filename) {
|
||||
std::ifstream file(filename);
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
return content;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write string to file.
|
||||
*
|
||||
* @param filename The file name to write to.
|
||||
* @param content The content to write.
|
||||
*/
|
||||
void hazure_write_file(std::string filename, std::string content) {
|
||||
std::ofstream file(filename);
|
||||
if (file.is_open()) {
|
||||
file << content;
|
||||
file.close();
|
||||
} else {
|
||||
std::cerr << "Unable to open " << filename << std::endl;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
#include <ctime>
|
||||
|
||||
/*
|
||||
* @brief Get time in seconds since the Epoch.
|
||||
*/
|
||||
int hazure_get_time() {
|
||||
return std::time(0);
|
||||
}
|
20
std/io.ts
Normal file
20
std/io.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes text to the file.
|
||||
* @param text The text to write. Can be any type.
|
||||
* @param path The path to the file.
|
||||
*/
|
||||
export function write_file(text: any, path: string): void {
|
||||
const bytes: Uint8Array = new TextEncoder().encode(text);
|
||||
Deno.writeFileSync(path, bytes);
|
||||
}
|
Loading…
Reference in a new issue