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. "dff9e03ea98aaf810945ea7af5d4b6bcfcaf7eca" and "24dbfc23ffe462bc009c47a5776155e297cf3597" have entirely different histories.

23 changed files with 233 additions and 88 deletions

3
.gitignore vendored
View file

@ -10,4 +10,5 @@ target/
*.pdb
# Generated by the compiler
/*.ts
/*.cpp
/*.out

11
Makefile Normal file
View 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"

View file

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

View file

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

View file

@ -1 +1 @@
pub mod ts;
pub mod cpp;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,3 @@
fun main: void = do
fun main: int = do
@writse(); -- Unknown intrinsic
end;

View file

@ -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;

View file

@ -1,4 +1,4 @@
fun main: void = do
fun main: int = do
if true then
@write("True")
else

View file

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

View file

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

@ -0,0 +1,4 @@
fun main: int = do
@time()
|> @write(_);
end;

2
hazure.toml Normal file
View file

@ -0,0 +1,2 @@
[compiler]
compiler = "clang++"

53
lib/io.hpp Normal file
View 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
View 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);
}

View file

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