mirror of
https://github.com/azur1s/bobbylisp.git
synced 2024-10-16 02:37:40 -05:00
Compare commits
No commits in common. "dff9e03ea98aaf810945ea7af5d4b6bcfcaf7eca" and "24dbfc23ffe462bc009c47a5776155e297cf3597" have entirely different histories.
dff9e03ea9
...
24dbfc23ff
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,4 +10,5 @@ target/
|
|||
*.pdb
|
||||
|
||||
# Generated by the compiler
|
||||
/*.ts
|
||||
/*.cpp
|
||||
/*.out
|
11
Makefile
Normal file
11
Makefile
Normal file
|
@ -0,0 +1,11 @@
|
|||
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,25 +1,24 @@
|
|||
# Hazure
|
||||
Programming language that compiles to Typescript!
|
||||
Programming language that compiles to C++!
|
||||
|
||||
```sml
|
||||
fun main: void = do
|
||||
@write("Hello, World!");
|
||||
fun main: int = do
|
||||
@write("Hello, World!\n");
|
||||
return 69;
|
||||
end;
|
||||
```
|
||||
or with the pipe operator:
|
||||
```sml
|
||||
fun main: void = do
|
||||
fun main: int = 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
|
||||
|
||||
|
@ -27,8 +26,9 @@ 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) Build executable `cargo build`
|
||||
3) Try running some examples! `path/to/executable compile path/to/file.hz`
|
||||
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`
|
||||
|
||||
# How it works
|
||||
```
|
||||
|
@ -62,12 +62,25 @@ Steps to build:
|
|||
Pass
|
||||
│
|
||||
│
|
||||
Codegen produce Typescript
|
||||
│ crates/codegen
|
||||
│
|
||||
Done
|
||||
(filename.ts)
|
||||
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++"
|
||||
```
|
||||
|
||||
# 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)
|
||||
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)
|
|
@ -2,6 +2,13 @@ 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,
|
||||
}
|
||||
|
@ -18,23 +25,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!(
|
||||
"const {}: {} = {}{}\n",
|
||||
name,
|
||||
"{} {} = {}{}\n",
|
||||
type_hint,
|
||||
name,
|
||||
self.gen_ir(value, false),
|
||||
semicolon!()
|
||||
)
|
||||
|
@ -56,8 +63,13 @@ impl Codegen {
|
|||
|
||||
IRKind::Intrinsic { name, args } => {
|
||||
match name.as_str() {
|
||||
"write" => { format!("console.log({}){}\n", self.gen_ir(&args[0], false), semicolon!()) },
|
||||
"read" => { todo!() },
|
||||
"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!()) },
|
||||
_ => unreachable!(format!("Unknown intrinsic: {}", name)) // Shoul be handled by lowering
|
||||
}
|
||||
},
|
||||
|
@ -65,14 +77,14 @@ impl Codegen {
|
|||
IRKind::Fun { name, return_type_hint, args, body } => {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| format!("{}: {}", arg.0, arg.1))
|
||||
.map(|arg| format!("{} {}", arg.1, arg.0))
|
||||
.collect::<Vec<_>>().
|
||||
join(", ");
|
||||
format!(
|
||||
"const {} = ({}): {} => {{\n{}\n}};\n",
|
||||
"{} {}({}) {{\n{}\n}}\n",
|
||||
return_type_hint,
|
||||
name,
|
||||
args,
|
||||
return_type_hint,
|
||||
self.gen_ir(body, false)
|
||||
)
|
||||
},
|
|
@ -1 +1 @@
|
|||
pub mod ts;
|
||||
pub mod cpp;
|
|
@ -1,9 +1,12 @@
|
|||
use std::ops::Range;
|
||||
use parser::Expr;
|
||||
|
||||
const INTRINSICS: [&str; 2] = [
|
||||
const INTRINSICS: [&str; 5] = [
|
||||
"write",
|
||||
"read",
|
||||
"write_file",
|
||||
"read_file",
|
||||
"time",
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -282,10 +285,9 @@ pub fn expr_to_ir(expr: &Expr) -> (Option<IRKind>, Option<LoweringError>) {
|
|||
|
||||
fn gen_type_hint(type_hint: &str) -> String {
|
||||
match type_hint {
|
||||
"int" => "number".to_string(),
|
||||
"bool" => "boolean".to_string(),
|
||||
"string" => "string".to_string(),
|
||||
"void" => "void".to_string(),
|
||||
"int" => "int".to_string(),
|
||||
"bool" => "bool".to_string(),
|
||||
"string" => "std::string".to_string(),
|
||||
_ => { dbg!(type_hint); todo!() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ pub enum Token {
|
|||
String(String), Identifier(String),
|
||||
|
||||
// Operators
|
||||
Plus, Minus, Multiply, Divide, Modulus,
|
||||
Plus, Minus, Multiply, Divide,
|
||||
Not, Equal, NotEqual, Less, Greater,
|
||||
Pipe,
|
||||
|
||||
|
@ -47,7 +47,6 @@ 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, "!="),
|
||||
|
|
25
crates/main/src/config.rs
Normal file
25
crates/main/src/config.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
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,22 +6,43 @@ use lexer::lex;
|
|||
use parser::parse;
|
||||
use diagnostic::Diagnostics;
|
||||
use hir::ast_to_ir;
|
||||
use codegen::ts;
|
||||
use codegen::cpp;
|
||||
|
||||
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, // TODO: Custom output file
|
||||
output,
|
||||
} => {
|
||||
// Macro to only log if `should_log` is true
|
||||
macro_rules! logif {
|
||||
|
@ -72,26 +93,37 @@ fn main() {
|
|||
}
|
||||
|
||||
// Generate code
|
||||
let mut codegen = ts::Codegen::new();
|
||||
let mut codegen = cpp::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 output_path: PathBuf = file_name.with_extension("cpp").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,8 +124,7 @@ fn expr_parser() -> impl Parser<Token, Vec<Spanned<Expr>>, Error = Simple<Token>
|
|||
.then(
|
||||
choice((
|
||||
just(Token::Multiply),
|
||||
just(Token::Divide),
|
||||
just(Token::Modulus)))
|
||||
just(Token::Divide)))
|
||||
.then(unary)
|
||||
.repeated())
|
||||
.foldl(|lhs, (op, rhs)| {
|
||||
|
|
|
@ -2,9 +2,10 @@ fun add2 (lhs: int) (rhs: int): int = do
|
|||
return lhs + rhs;
|
||||
end;
|
||||
|
||||
fun main: void = do
|
||||
fun main: int = 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: void = do
|
||||
fun main: int = do
|
||||
@writse(); -- Unknown intrinsic
|
||||
end;
|
|
@ -1,3 +1,5 @@
|
|||
fun main: void = do
|
||||
@write("Hello, World!");
|
||||
fun main: int = do
|
||||
-- Normal way of hello world
|
||||
@write("Hello, World!\n");
|
||||
return 69;
|
||||
end;
|
|
@ -1,4 +1,4 @@
|
|||
fun main: void = do
|
||||
fun main: int = do
|
||||
if true then
|
||||
@write("True")
|
||||
else
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
fun foo (xs: int): int = return xs + 1;
|
||||
fun bar (xs: int) (x: int): int = return xs - x;
|
||||
|
||||
fun main: void = do
|
||||
fun main: int = 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
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
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;
|
4
example/quine.hz
Normal file
4
example/quine.hz
Normal file
|
@ -0,0 +1,4 @@
|
|||
fun main: int = do
|
||||
@read_file("./example/quine.hz") -- Run this from root folder
|
||||
|> @write(_);
|
||||
end;
|
9
example/readwrite.hz
Normal file
9
example/readwrite.hz
Normal file
|
@ -0,0 +1,9 @@
|
|||
fun main: int = do
|
||||
@write("Enter your name: ");
|
||||
let name: string = "";
|
||||
@read(name);
|
||||
|
||||
@write("Hello ");
|
||||
@write(name);
|
||||
@write("!");
|
||||
end;
|
4
example/time.hz
Normal file
4
example/time.hz
Normal file
|
@ -0,0 +1,4 @@
|
|||
fun main: int = do
|
||||
@time()
|
||||
|> @write(_);
|
||||
end;
|
2
hazure.toml
Normal file
2
hazure.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[compiler]
|
||||
compiler = "clang++"
|
53
lib/io.hpp
Normal file
53
lib/io.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#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;
|
||||
}
|
||||
}
|
9
lib/time.hpp
Normal file
9
lib/time.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include <ctime>
|
||||
|
||||
/*
|
||||
* @brief Get time in seconds since the Epoch.
|
||||
*/
|
||||
int hazure_get_time() {
|
||||
return std::time(0);
|
||||
}
|
20
std/io.ts
20
std/io.ts
|
@ -1,20 +0,0 @@
|
|||
import { writeAllSync } from "https://deno.land/std@0.129.0/streams/conversion.ts";
|
||||
|
||||
/**
|
||||
* Writes text to the stdout stream.
|
||||
* @param text The text to write. Can be any type.
|
||||
*/
|
||||
export function write(text: any): void {
|
||||
const bytes: Uint8Array = new TextEncoder().encode(text);
|
||||
writeAllSync(Deno.stdout, bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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