This commit is contained in:
mlokr 2024-06-20 11:23:37 +02:00
commit 499fe34f1d
No known key found for this signature in database
GPG key ID: DEA147DDEE644993
16 changed files with 411 additions and 135 deletions

351
hblang/README.md Normal file
View file

@ -0,0 +1,351 @@
# HERE SHALL THE DOCUMENTATION RESIDE
## Enforced Political Views
- worse is better
- less is more
- embrace `unsafe {}`
- adhere `macro_rules!`
- pessimization == death (put in `std::pin::Pin` and left with hungry crabs)
- importing external dependencies == death (`fn(dependencies) -> ExecutionStrategy`)
- above sell not be disputed, discussed, or questioned
## What hblang is
Holey-Bytes-Language (hblang for short) (*.hb) is the only true language targeting hbvm byte code. hblang is low level, manually managed, and procedural. Its rumored to be better then writing hbasm and you should probably use it for complex applications.
## What hblang isnt't
hblang knows what it isn't, because it knows what it is, hblang computes this by sub...
## Examples
Examples are also used in tests. To add an example that runs during testing add:
<pre>
#### &lt;name&gt
```hb
&lt;example&gt
```
</pre>
and also:
```rs
<name> => README;
```
to the `run_tests` macro at the bottom of the `src/codegen.rs`.
### Tour Examples
Following examples incrementally introduce language features and syntax.
#### main_fn
```hb
main := fn(): int {
return 1;
}
```
#### arithmetic
```hb
main := fn(): int {
return 10 - 20 / 2 + 4 * (2 + 2) - 4 * 4 + 1;
}
```
#### functions
```hb
main := fn(): int {
return add_one(10) + add_two(20);
}
add_two := fn(x: int): int {
return x + 2;
}
add_one := fn(x: int): int {
return x + 1;
}
```
#### if_statements
```hb
main := fn(): int {
return fib(10);
}
fib := fn(x: int): int {
if x <= 2 {
return 1;
} else {
return fib(x - 1) + fib(x - 2);
}
}
```
#### variables
```hb
main := fn(): int {
a := 1;
b := 2;
a = a + 1;
return a - b;
}
```
#### loops
```hb
main := fn(): int {
return fib(10);
}
fib := fn(n: int): int {
a := 0;
b := 1;
loop {
if n == 0 break;
c := a + b;
a = b;
b = c;
n -= 1;
stack_reclamation_edge_case := 0;
continue;
}
return a;
}
```
#### pointers
```hb
main := fn(): int {
a := 1;
b := &a;
modify(b);
drop(a);
stack_reclamation_edge_case := 0;
return *b - 2;
}
modify := fn(a: ^int): void {
*a = 2;
return;
}
drop := fn(a: int): void {
return;
}
```
#### structs
```hb
Ty := struct {
a: int,
b: int,
}
Ty2 := struct {
ty: Ty,
c: int,
}
main := fn(): int {
finst := Ty2.{ ty: Ty.{ a: 4, b: 1 }, c: 3 };
inst := odher_pass(finst);
if inst.c == 3 {
return pass(&inst.ty);
}
return 0;
}
pass := fn(t: ^Ty): int {
return t.a - t.b;
}
odher_pass := fn(t: Ty2): Ty2 {
return t;
}
```
#### struct_operators
```hb
Point := struct {
x: int,
y: int,
}
Rect := struct {
a: Point,
b: Point,
}
main := fn(): int {
a := Point.(1, 2);
b := Point.(3, 4);
d := Rect.(a + b, b - a);
d2 := Rect.(Point.(0, 0) - b, a);
d2 = d2 + d;
c := d2.a + d2.b;
return c.x + c.y;
}
```
#### global_variables
```hb
global_var := 10;
complex_global_var := fib(global_var) - 5;
fib := fn(n: int): int {
if 2 > n {
return n;
}
return fib(n - 1) + fib(n - 2);
}
main := fn(): int {
return complex_global_var;
}
```
note: values of global variables are evaluated at compile time
#### directives
```hb
Type := struct {
brah: int,
blah: int,
}
main := fn(): int {
byte := @as(u8, 10);
same_type_as_byte := @as(@TypeOf(byte), 30);
wide_uint := @as(u32, 40);
truncated_uint := @as(u8, @intcast(wide_uint));
size_of_Type_in_bytes := @sizeof(Type);
align_of_Type_in_bytes := @alignof(Type);
hardcoded_pointer := @as(^u8, @bitcast(10));
ecall_that_returns_int := @eca(int, 1, Type.(10, 20), 5, 6);
return 0;
}
```
- `@TypeOf(<expr>)`: results into literal type of whatever the type of `<expr>` is, `<expr>` is not included in final binary
- `@as(<ty>, <expr>)`: hint to the compiler that `@TypeOf(<expr>) == <ty>`
- `@intcast(<expr>)`: needs to be used when conversion of `@TypeOf(<expr>)` would loose precision (widening of integers is implicit)
- `@sizeof(<ty>), @alignof(<ty>)`: I think explaining this would insult your intelligence
- `@bitcast(<expr>)`: tell compiler to assume `@TypeOf(<expr>)` is whatever is inferred, so long as size and alignment did not change
- `@eca(<ty>, <expr>...)`: invoke `eca` instruction, where `<ty>` is the type this will return and `<expr>...` are arguments passed to the call
### Incomplete Examples
#### generic_types
```hb
Vec := fn($Elem: type): type {
return struct {
data: ^Elem,
len: uint,
cap: uint,
};
}
main := fn(): int {
i := 69;
vec := Vec(int).{
data: &i,
len: 1,
cap: 1,
};
return *vec.data;
}
```
#### fb_driver
```hb
arm_fb_ptr := fn(): int return 100;
x86_fb_ptr := fn(): int return 100;
check_platform := fn(): int {
return x86_fb_ptr();
}
set_pixel := fn(x: int, y: int, width: int): int {
pix_offset := y * width + x;
return 0;
}
main := fn(): int {
fb_ptr := check_platform();
width := 100;
height := 30;
x:= 0;
y:= 0;
loop {
if x <= height + 1 {
set_pixel(x,y,width);
x = x + 1;
} else {
set_pixel(x,y,width);
x = 0;
y = y + 1;
}
if y == width {
break;
}
}
return 0;
}
```
### Purely Testing Examples
#### different_types
```hb
Color := struct {
r: u8,
g: u8,
b: u8,
a: u8,
}
Point := struct {
x: u32,
y: u32,
}
Pixel := struct {
color: Color,
point: Point,
}
main := fn(): int {
pixel := Pixel.{
color: Color.{
r: 255,
g: 0,
b: 0,
a: 255,
},
point: Point.{
x: 0,
y: 2,
},
};
if *(&pixel.point.x + 1) != 2 {
return 0;
}
if *(&pixel.point.y - 1) != 0 {
return 64;
}
return pixel.point.x + pixel.point.y + pixel.color.r
+ pixel.color.g + pixel.color.b + pixel.color.a;
}
```

View file

@ -4,5 +4,13 @@ Type := struct {
}
main := fn(): int {
return @eca(int, 1, Type.(10, 20), @sizeof(Type), @alignof(Type), 5, 6);
byte := @as(u8, 10);
same_type_as_byte := @as(@TypeOf(byte), 30);
wide_uint := @as(u32, 40);
truncated_uint := @as(u8, @intcast(wide_uint));
size_of_Type_in_bytes := @sizeof(Type);
align_of_Type_in_bytes := @alignof(Type);
hardcoded_pointer := @as(^u8, @bitcast(10));
ecall_that_returns_int := @eca(int, 1, Type.(10, 20), 5, 6);
return 0;
}

View file

@ -2367,7 +2367,34 @@ mod tests {
use super::parser;
fn generate(input: &'static str, output: &mut String) {
const README: &str = include_str!("../README.md");
fn generate(ident: &'static str, input: &'static str, output: &mut String) {
fn find_block(mut input: &'static str, test_name: &'static str) -> &'static str {
const CASE_PREFIX: &str = "#### ";
const CASE_SUFFIX: &str = "\n```hb";
loop {
let Some(pos) = input.find(CASE_PREFIX) else {
unreachable!("test {test_name} not found");
};
input = unsafe { input.get_unchecked(pos + CASE_PREFIX.len()..) };
if !input.starts_with(test_name) {
continue;
}
input = unsafe { input.get_unchecked(test_name.len()..) };
if !input.starts_with(CASE_SUFFIX) {
continue;
}
input = unsafe { input.get_unchecked(CASE_SUFFIX.len()..) };
let end = input.find("```").unwrap_or(input.len());
break unsafe { input.get_unchecked(..end) };
}
}
let input = find_block(input, ident);
let path = "test";
let mut codegen = super::Codegen::default();
codegen.files = vec![parser::Ast::new(path, input, &parser::no_loader)];
@ -2406,19 +2433,18 @@ mod tests {
}
crate::run_tests! { generate:
example => include_str!("../examples/main_fn.hb");
arithmetic => include_str!("../examples/arithmetic.hb");
variables => include_str!("../examples/variables.hb");
functions => include_str!("../examples/functions.hb");
if_statements => include_str!("../examples/if_statement.hb");
loops => include_str!("../examples/loops.hb");
fb_driver => include_str!("../examples/fb_driver.hb");
pointers => include_str!("../examples/pointers.hb");
structs => include_str!("../examples/structs.hb");
different_types => include_str!("../examples/different_types.hb");
struct_operators => include_str!("../examples/struct_operators.hb");
directives => include_str!("../examples/directives.hb");
global_variables => include_str!("../examples/global_variables.hb");
geneic_types => include_str!("../examples/generic_types.hb");
arithmetic => README;
variables => README;
functions => README;
if_statements => README;
loops => README;
fb_driver => README;
pointers => README;
structs => README;
different_types => README;
struct_operators => README;
directives => README;
global_variables => README;
generic_types => README;
}
}

View file

@ -388,45 +388,3 @@ impl LineMap {
}
}
}
#[cfg(test)]
mod tests {
fn map_lines(input: &'static str, _: &mut String) {
let line_map = super::LineMap::new(input);
for i in 0..input.len() {
assert_eq!(
line_map.line_col(i as u32),
//line_map.line_col(i as u32),
super::line_col(input.as_bytes(), i as u32)
);
}
}
crate::run_tests! { map_lines:
empty_file => "";
log_line => " ".repeat(1000).leak();
this_file => &include_str!("parser.rs")[..1000];
}
fn lex(input: &'static str, output: &mut String) {
use {
super::{Lexer, TokenKind as T},
std::fmt::Write,
};
let mut lexer = Lexer::new(input);
loop {
let token = lexer.next();
writeln!(output, "{:?} {:?}", token.kind, &input[token.range()],).unwrap();
if token.kind == T::Eof {
break;
}
}
}
crate::run_tests! { lex:
empty => "";
whitespace => " \t\n\r";
example => include_str!("../examples/main_fn.hb");
arithmetic => include_str!("../examples/arithmetic.hb");
}
}

View file

@ -1,4 +1,5 @@
#![feature(vec_pop_if)]
#![feature(pattern)]
#![feature(if_let_guard)]
#![feature(slice_partition_dedup)]
#![feature(noop_waker)]
@ -26,7 +27,7 @@ macro_rules! run_tests {
($runner:path: $($name:ident => $input:expr;)*) => {$(
#[test]
fn $name() {
$crate::tests::run_test(std::any::type_name_of_val(&$name), $input, $runner);
$crate::tests::run_test(std::any::type_name_of_val(&$name), stringify!($name), $input, $runner);
}
)*};
}

View file

@ -1077,21 +1077,3 @@ impl Drop for ArenaChunk {
}
}
}
#[cfg(test)]
mod tests {
fn parse(input: &'static str, output: &mut String) {
use std::fmt::Write;
let arena = super::Arena::default();
let mut symbols = Vec::new();
let mut parser = super::Parser::new(&arena, &mut symbols, &super::no_loader);
for expr in parser.file(input, "test") {
writeln!(output, "{}", expr).unwrap();
}
}
crate::run_tests! { parse:
example => include_str!("../examples/main_fn.hb");
arithmetic => include_str!("../examples/arithmetic.hb");
}
}

View file

@ -1,6 +1,11 @@
#![cfg(test)]
pub fn run_test(name: &'static str, input: &'static str, test: fn(&'static str, &mut String)) {
pub fn run_test(
name: &'static str,
ident: &'static str,
input: &'static str,
test: fn(&'static str, &'static str, &mut String),
) {
use std::{io::Write, path::PathBuf};
let filter = std::env::var("PT_FILTER").unwrap_or_default();
@ -9,7 +14,7 @@ pub fn run_test(name: &'static str, input: &'static str, test: fn(&'static str,
}
let mut output = String::new();
test(input, &mut output);
test(ident, input, &mut output);
let mut root = PathBuf::from(
std::env::var("PT_TEST_ROOT")

View file

@ -1,4 +1,4 @@
ev: Ecall
code size: 182
code size: 235
ret: 0
status: Ok(())

View file

@ -1,3 +0,0 @@
code size: 82
ret: 1
status: Ok(())

View file

@ -1,31 +0,0 @@
Ident "main"
Decl ":="
Fn "fn"
LParen "("
RParen ")"
Colon ":"
Ident "int"
LBrace "{"
Return "return"
Number "10"
Sub "-"
Number "20"
Div "/"
Number "2"
Add "+"
Number "4"
Mul "*"
LParen "("
Number "2"
Add "+"
Number "2"
RParen ")"
Sub "-"
Number "4"
Mul "*"
Number "4"
Add "+"
Number "1"
Semi ";"
RBrace "}"
Eof ""

View file

@ -1 +0,0 @@
Eof ""

View file

@ -1,13 +0,0 @@
Ident "main"
Decl ":="
Fn "fn"
LParen "("
RParen ")"
Colon ":"
Ident "int"
LBrace "{"
Return "return"
Number "1"
Semi ";"
RBrace "}"
Eof ""

View file

@ -1 +0,0 @@
Eof ""

View file

@ -1,3 +0,0 @@
main := fn(): int {
return 10 - 20 / 2 + 4 * (2 + 2) - 4 * 4 + 1;
}

View file

@ -1,3 +0,0 @@
main := fn(): int {
return 1;
}