replacement for the current hblang compiler written in more sane language
Find a file
Jakub Doka dd46d8a2d5
fizing around 500 fuzz tests
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2025-03-18 18:29:11 +01:00
scripts save 2025-03-14 11:33:12 +01:00
src fizing around 500 fuzz tests 2025-03-18 18:29:11 +01:00
test-project making the displayed paths relative to the root file 2025-03-09 07:22:42 +01:00
tests fizing around 500 fuzz tests 2025-03-18 18:29:11 +01:00
vendored-tests purposful break, found more bugs, some also fixed 2025-03-16 08:45:39 +01:00
.gitignore making this compatible with wasm 2025-03-10 18:28:00 +01:00
.gitmodules adding an name_of enum 2025-03-14 17:30:34 +01:00
.rgignore @int_cast, @bit_cast 2025-02-25 06:54:33 +01:00
build.zig fizing around 500 fuzz tests 2025-03-18 18:29:11 +01:00
build.zig.zon refactoring the type passing 2025-03-14 11:51:59 +01:00
foo.hb making the displayed paths relative to the root file 2025-03-09 07:22:42 +01:00
install-fuzz.sh adding binary size metric 2025-03-17 22:04:42 +01:00
README.md fixed some bugs 2025-03-15 12:28:36 +01:00

hblang

Build The Compiler

zig build install
./zig-out/bin/hbc --help

Tour

Note: the examples are used to generate unit tests, n = 1 from each group is most interesting, others are more for testing purposes. Note: expectations contain the test case ecpectations that are asserted when zig build test is run

main fn 1

expectations := .{
    return_value: 42,
}

main := fn(): uint {
    return 42
}

arithmetic 1

main := fn(): uint {
    return 10 - 20 / 2 + 4 * (2 + 2) - 4 * 4 + 1 - 1
}

arithmetic 2

main := fn(): int {
    a: i8 = 0
    b: u8 = 0
    c: i16 = a - 1
    d: u16 = b + 1
    e: i32 = c - 1
    f: u32 = d + 1
    return f + e
}

arithmetic 3

main := fn(): uint {
    {
        // no opts unsigned
        o := one()
        z := zero()

        if cond(o < z) return 1
        if cond(o < o) return 2
        if cond(o <= z) return 3
        if cond(z > o) return 4
        if cond(o > o) return 5
        if cond(z >= o) return 6
    }

    {
        // no opts signed
        o := mne()
        z := mero()

        if cond(o > z) return 7
        if cond(o > o) return 8
        if cond(o >= z) return 9
        if cond(z < o) return 10
        if cond(o < o) return 11
        if cond(z <= o) return 12
    }

    {
        // branch opts unsigned
        o := one()
        z := zero()

        if o < z return 13
        if o < o return 14
        if o <= z return 15
        if z > o return 16
        if o > o return 17
        if z >= o return 18
    }

    {
        // branch opts signed
        o := mne()
        z := mero()

        if o > z return 19
        if o > o return 20
        if o >= z return 21
        if z < o return 22
        if o < o return 23
        if z <= o return 24
        if o != o return 25
        if o == z return 26
    }

    return 0
}

cond := fn(v: bool): bool return v

one := fn(): u8 return 1
zero := fn(): u8 return 0

mne := fn(): i8 return -1
mero := fn(): i8 return 0

arithmetic 4

expectations := .{
    return_value: 1,
}

main := fn(): bool {
    return 3 == 2 * 2 - 1
}

arithmetic 5 (errors)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    v := /**/
    return 1 + v * 10
}

arithmetic 6 (missing operators)

main := fn(): uint {
    return 1 << 3 % 2 - 8 >> 3 | 4 & 2 ^ 0
}

arithmetic 7 (unary operators)

main := fn(): uint {
    if !true return 1
    if ~1 != -1 - 1 return 2
    if -1 != ~1 + 1 return 3
    return 0
}

arithmetic 8

main := fn(): uint {
    return (0 < 1) - (1 + 0)
}

float arithmetic 1

main := fn(): f64 {
    return 10.0 - 20.0 / 2.0 + 4.0 * (2.0 + 2.0) - 4.0 * 4.0 + 1.0 - 1.0
}

float arithmetic 2

expectations := .{
    return_value: 826,
}

sin_table := f32.[0.0, 0.02454122852291229, 0.04906767432741801, 0.07356456359966743, 0.0980171403295606, 0.1224106751992162, 0.1467304744553617, 0.1709618887603012, 0.1950903220161282, 0.2191012401568698, 0.2429801799032639, 0.2667127574748984, 0.2902846772544623, 0.3136817403988915, 0.3368898533922201, 0.3598950365349881, 0.3826834323650898, 0.4052413140049899, 0.4275550934302821, 0.4496113296546065, 0.4713967368259976, 0.492898192229784, 0.5141027441932217, 0.5349976198870972, 0.5555702330196022, 0.5758081914178453, 0.5956993044924334, 0.6152315905806268, 0.6343932841636455, 0.6531728429537768, 0.6715589548470183, 0.6895405447370668, 0.7071067811865475, 0.7242470829514669, 0.7409511253549591, 0.7572088465064845, 0.773010453362737, 0.7883464276266062, 0.8032075314806448, 0.8175848131515837, 0.8314696123025452, 0.844853565249707, 0.8577286100002721, 0.8700869911087113, 0.8819212643483549, 0.8932243011955153, 0.9039892931234433, 0.9142097557035307, 0.9238795325112867, 0.9329927988347388, 0.9415440651830208, 0.9495281805930367, 0.9569403357322089, 0.9637760657954398, 0.970031253194544, 0.9757021300385286, 0.9807852804032304, 0.9852776423889412, 0.989176509964781, 0.99247953459871, 0.9951847266721968, 0.9972904566786902, 0.9987954562051724, 0.9996988186962042, 1.0, 0.9996988186962042, 0.9987954562051724, 0.9972904566786902, 0.9951847266721969, 0.99247953459871, 0.989176509964781, 0.9852776423889412, 0.9807852804032304, 0.9757021300385286, 0.970031253194544, 0.9637760657954398, 0.9569403357322089, 0.9495281805930367, 0.9415440651830208, 0.9329927988347388, 0.9238795325112867, 0.9142097557035307, 0.9039892931234434, 0.8932243011955152, 0.881921264348355, 0.8700869911087115, 0.8577286100002721, 0.8448535652497072, 0.8314696123025455, 0.8175848131515837, 0.8032075314806449, 0.7883464276266063, 0.7730104533627371, 0.7572088465064847, 0.740951125354959, 0.7242470829514669, 0.7071067811865476, 0.6895405447370671, 0.6715589548470186, 0.6531728429537766, 0.6343932841636455, 0.6152315905806269, 0.5956993044924335, 0.5758081914178454, 0.5555702330196022, 0.5349976198870972, 0.5141027441932218, 0.4928981922297841, 0.4713967368259979, 0.4496113296546069, 0.427555093430282, 0.4052413140049899, 0.3826834323650899, 0.3598950365349883, 0.3368898533922203, 0.3136817403988914, 0.2902846772544624, 0.2667127574748985, 0.2429801799032641, 0.21910124015687, 0.1950903220161286, 0.1709618887603012, 0.1467304744553618, 0.1224106751992163, 0.09801714032956083, 0.07356456359966773, 0.04906767432741797, 0.02454122852291233, 0.0, -0.02454122852291208, -0.04906767432741772, -0.0735645635996675, -0.09801714032956059, -0.1224106751992161, -0.1467304744553616, -0.170961888760301, -0.1950903220161284, -0.2191012401568698, -0.2429801799032638, -0.2667127574748983, -0.2902846772544621, -0.3136817403988912, -0.3368898533922201, -0.3598950365349881, -0.3826834323650897, -0.4052413140049897, -0.4275550934302818, -0.4496113296546067, -0.4713967368259976, -0.4928981922297839, -0.5141027441932216, -0.5349976198870969, -0.555570233019602, -0.5758081914178453, -0.5956993044924332, -0.6152315905806267, -0.6343932841636453, -0.6531728429537765, -0.6715589548470184, -0.6895405447370668, -0.7071067811865475, -0.7242470829514668, -0.7409511253549589, -0.7572088465064842, -0.7730104533627367, -0.7883464276266059, -0.8032075314806451, -0.8175848131515838, -0.8314696123025452, -0.844853565249707, -0.857728610000272, -0.8700869911087113, -0.8819212643483549, -0.8932243011955152, -0.9039892931234431, -0.9142097557035305, -0.9238795325112865, -0.932992798834739, -0.9415440651830208, -0.9495281805930367, -0.9569403357322088, -0.9637760657954398, -0.970031253194544, -0.9757021300385285, -0.9807852804032303, -0.9852776423889411, -0.9891765099647809, -0.9924795345987101, -0.9951847266721969, -0.9972904566786902, -0.9987954562051724, -0.9996988186962042, -1.0, -0.9996988186962042, -0.9987954562051724, -0.9972904566786902, -0.9951847266721969, -0.9924795345987101, -0.9891765099647809, -0.9852776423889412, -0.9807852804032304, -0.9757021300385286, -0.970031253194544, -0.96377606579544, -0.9569403357322089, -0.9495281805930368, -0.9415440651830209, -0.9329927988347391, -0.9238795325112866, -0.9142097557035306, -0.9039892931234433, -0.8932243011955153, -0.881921264348355, -0.8700869911087115, -0.8577286100002722, -0.8448535652497072, -0.8314696123025455, -0.817584813151584, -0.8032075314806453, -0.7883464276266061, -0.7730104533627369, -0.7572088465064846, -0.7409511253549591, -0.724247082951467, -0.7071067811865477, -0.6895405447370672, -0.6715589548470187, -0.6531728429537771, -0.6343932841636459, -0.6152315905806274, -0.5956993044924332, -0.5758081914178452, -0.5555702330196022, -0.5349976198870973, -0.5141027441932219, -0.4928981922297843, -0.4713967368259979, -0.449611329654607, -0.4275550934302825, -0.4052413140049904, -0.3826834323650904, -0.359895036534988, -0.33688985339222, -0.3136817403988915, -0.2902846772544625, -0.2667127574748986, -0.2429801799032642, -0.2191012401568702, -0.1950903220161287, -0.1709618887603018, -0.1467304744553624, -0.122410675199216, -0.09801714032956051, -0.07356456359966741, -0.04906767432741809, -0.02454122852291245]

sin := fn(theta: f32): f32 {
    PI := 3.14159265358979323846
    TABLE_SIZE := @as(i32, 256)
    si := @float_to_int(@float_cast(theta) * 0.5 * @int_to_float(TABLE_SIZE) / @float_cast(PI))
    d := theta - @int_to_float(si) * 2.0 * PI / @int_to_float(TABLE_SIZE)
    ci := si + TABLE_SIZE / 4 & TABLE_SIZE - 1
    si &= TABLE_SIZE - 1
    return sin_table[@bit_cast(si)] + (sin_table[@bit_cast(ci)] - 0.5 * sin_table[@bit_cast(si)] * d) * d
}

main := fn(): int {
    return @float_to_int(sin(1000.0) * 1000.0)
}

float arithmetic 3

main := fn(): uint {
    if 1.0 > 2.0 return 1
    if 1.0 >= 2.0 return 2
    if 2.0 <= 1.0 return 3
    if 2.0 < 1.0 return 4
    if 1.0 != 1.0 return 5
    if 2.0 == 1.0 return 6
    return 0
}

literals 1

main := fn(): uint {
    if 10 != 0xa return 16
    if 10 != 0o12 return 8
    if 10 != 0b1010 return 2
    return 0
}

literals 2

expectations := .{
    return_value: 0,
}

hex := fn(): uint return 0x2d
dec := fn(): uint return 45

main := fn(): uint {
    if hex() != dec() return 1
    return 0
}

literals 3

expectations := .{
    return_value: 69,
}

main := fn(): uint {
    return 'E'
}

functions 1

expectations := .{
    return_value: 33,
}

main := fn(): uint {
    return add_one(10) + add_two(20)
}

add_two := fn(x: uint): uint {
    return x + 2
}

add_one := fn(x: uint): uint {
    return x + 1
}

functions 2

expectations := .{
    return_value: 33,
}

main := @use("main.hb").main

// in: main.hb

one := @use("one.hb")
two := @use("two.hb")

main := fn(): uint {
    return one.add(10) + two.add(20)
}

// in: two.hb

add := fn(x: uint): uint {
    return x + 2
}

// in: one.hb

add := fn(x: uint): uint {
    return x + 1
}

functions 3 (errors)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    imaginary := /**/
    imaginary()
    some_fn()
    _ = some_fn(0, 0, 0)
    _ = some_fn(0, 0)
    vl := some_fn(0, /**/, 0, 0)
    return some_fn(vl, /**/, 0)
}

some_fn := fn(a: uint, b: void, c: u8): uint {
    return
}

functions 4 (returning stack)

expectations := .{
    should_error: true,
}

main := fn(): void {
    rstack := return_direct_stack()
    rstruct := return_indirect_stack()
    v: ?^uint = null
    ret := return_indirect_stack_but_not(&v)
}

return_direct_stack := fn(): ^uint {
    a := 0
    if true return &a

    return &a
}

return_indirect_stack := fn(): struct{.u: uint; .b: uint; .c: ^uint} {
    v := 0
    return .(0, 0, &v)
}

return_indirect_stack_but_not := fn(arg: ^?^uint): void {
    v := 0
    arg.* = &v
    arg.* = null
}

comments 1

// commant is an item
main := fn(): uint {
    // comment is a statement
    foo(/* comment is an exprression */)
    return 0
}

foo := fn(comment: void): void return /* comment evaluates to void */

// comments might be formatted in the future

if statements 1

expectations := .{
    return_value: 2,
}

main := fn(): uint {
    return fib(3)
}

fib := fn(x: uint): uint {
    if x <= 2 {
        return 1
    } else {
        return fib(x - 1) + fib(x - 2)
    }
}

if statements 2

expectations := .{
    return_value: 2,
}

main := fn(): uint {
    return fib(3)
}

fib := fn(x: uint): uint {
    if x <= 2 {
        x = 1
    } else {
        x = fib(x - 1) + fib(x - 2)
    }
    return x
}

if statements 3 (comptime)

main := fn(): uint {
    $if true return 0
}

variables 1

main := fn(): uint {
    ඞ := 1
    b := 2
    ඞ += 1
    return ඞ - b
}

loops 1

expectations := .{
    return_value: 55,
}

main := fn(): uint {
    return fib(10)
}

fib := fn(n: uint): uint {
    b := 1
    a := 0
    loop {
        if n == 0 break
        c := a + b
        a = b
        b = c
        n -= 1
        continue
    }
    return a
}

loops 2

expectations := .{
    return_value: 9,
}

main := fn(): uint {
    return square(3)
}

square := fn(size: uint): uint {
    acc := 0
    y := 0
    loop if y == size break else {
        x := 0
        loop if x == size break else {
            acc += x * y
            x += 1
        }
        y += 1
    }
    return acc
}

loops 3

expectations := .{
    return_value: 4,
}

main := fn(): uint {
    i := 0
    loop if i == 4 break else {
        i += 1
    }
    return i
}

loops 4 (comptime)

expectations := .{
    return_value: 10,
}

main := fn(): uint {
    arr := uint.[1, 2, 3, 4]
    i := 0
    sum := 0
    $loop $if i == arr.len break else {
        sum += arr[i]
        i += 1
    }
    return sum
}

loops 5 (comptime|error)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    arr := uint.[1, 2, 3, 4]
    i := 0
    sum := 0
    $loop if i == arr.len break else {
        sum += arr[i]
        i += 1
    }
    return sum
}

loops 6 (infinite)

expectations := .{
    times_out: true,
}

main := fn(): uint {
    if true loop {
    }

    return 0
}

loops 7 (loop invariant break)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    arr := uint.[1, 2, 3, 4]
    i := 0
    sum := 0
    loop if i == arr.len break else {
        sum += arr[i]
        //i += 1 // ups forgot to increment
    }
    return sum
}

pointers 1

main := fn(): uint {
    a := 1
    b := &a
    modify(b)
    b.* += 2
    return b.* - 4
}

modify := fn(a: ^uint): void {
    a.* = 2
    return
}

pointers 2

expectations := .{
    return_value: 1,
}

main := fn(): uint {
    a := 1
    b := 2

    c := &a
    d := &b

    swap(c, d)

    return a - b
}

swap := fn(a: ^uint, b: ^uint): void {
    tmp := b.*
    b.* = a.*
    a.* = tmp
}

pointers 3

expectations := .{
    return_value: 1,
}

main := fn(): uint {
    a := 1
    _ = do_stuff(&a)
    return a
}

do_stuff := fn(v: ^uint): uint {
    if v.* == 0 {
        return 0
        v.* = 2
    } else {
        return 1
        v.* = 3
    }
}

pointers 4 (errors)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    return (~0).*
}

pointers 5 (math)

main := fn(): uint {
    val := uint.[1, 0]
    return (val.ptr + 1).*
}

structs 1

expectations := .{
    return_value: 3,
}

Ty := struct {
    .a: int;
    .b: int = 1

    sum := fn(t: Ty): int {
        t.a -= 2
        t.b += 1
        return t.a - t.b
    }
}

Ty2 := struct {
    .ty: Ty;
    .c: int;
}

main := fn(): int {
    finst := Ty2.{ty: Ty.{a: 4}, c: 3}
    inst := odher_pass(finst)
    if inst.c != 3 {
        return 0
    }
    if inst.ty.sum() != 0 {
        return 100
    }
    return pass(&inst.ty)
}

pass := fn(t: ^Ty): int {
    t.a -= 1
    t.a += 1
    return t.a - t.b
}

odher_pass := fn(t: Ty2): Ty2 {
    return t
}

structs 2

expectations := .{
    return_value: 3,
}

Ty := struct {
    .a: int;
    .b: int;
    .c: int;
}

main := fn(): int {
    a := Ty.{a: 0, b: 0, c: 0}
    b := Ty.{a: 1, b: 1, c: 1}
    swap(&a, &b)
    return a.a + a.b + a.c
}

swap := fn(a: ^Ty, b: ^Ty): void {
    tmp := a.*
    a.* = b.*
    b.* = tmp
}

structs 3 (call stress test)

Pair := struct{.a: u8; .b: uint}
Triple := struct{.a: uint; .b: uint; .c: u8}

main := fn(): uint {
    pr := return_pair()
    tpl := return_triple()

    if take_pair(pr) != take_triple(tpl) return 1

    return 0
}

return_pair := fn(): Pair {
    return .{a: 1, b: 5}
}

return_triple := fn(): Triple {
    return .{a: 1, b: 2, c: 3}
}

take_pair := fn(pair: Pair): uint {
    return pair.a + pair.b
}

take_triple := fn(triple: Triple): uint {
    return triple.a + triple.b + triple.c
}

structs 4 (errors)

expectations := .{
    should_error: true,
}

Ty := struct{.a: uint; .b: uint}

main := fn(): uint {
    _ = .{}
    _ = .()
    _ = uint.{}
    _ = uint.()
    _ = Ty.{}
    _ = Ty.()
    _ = Ty.{p: 10}
    _ = Ty.{a: 1, b: 2, p: 10}
    _ = Ty.{a: 1, a: 2}
    _ = Ty.(.{}, .(), /**/)
    v := Ty.(0, 0, 0)
    return Ty.(v, 0)
}

structs 5 (comptime)

expectations := .{
    return_value: 6,
}

main := fn(): uint {
    Ty := struct{.a: uint; .b: uint; .c: uint}

    vl := Ty.(1, 2, 3)

    i := 0
    sum := 0
    $loop $if i == @len_of(Ty) break else {
        sum += vl[i]
        i += 1
    }

    return sum
}

structs 6 (packed)

expectations := .{
    return_value: 4,
}

main := fn(): uint {
    Pckd := struct align(1){.a: u8; .b: u16}
    return @size_of(Pckd) + @align_of(Pckd)
}

file structs 1

main := fn(): uint {
    foo := @use("Foo.hb").init(2)
    return foo.a - foo.b
}

// in: Foo.hb
.a: uint;
.b: uint

init := fn(v: uint): @CurrentScope() {
    return .(v, v)
}

generic structs 1

main := fn(): uint {
    $if Vec(uint) != Vec(uint) return 100

    vl: Vec(uint) = nvec(uint, 1)
    return vl.sub()
}

nvec := fn($E: type, v: uint): Vec(E) {
    return .(v, v)
}

Vec := fn(E: type): type return struct {
    .x: E;
    .y: E

    sub := fn(self: @CurrentScope()): E {
        return self.x - self.y
    }
}

generic structs 2

main := fn(): uint {
    vl: Foo(uint).Bar(u8) = .init()
    return vl.sub()
}

Foo := fn(F: type): type return struct {
    Bar := fn(B: type): type return struct {
        .foo: F;
        .bar: B

        init := fn(): @CurrentScope() return .(1, 1)

        sub := fn(self: @CurrentScope()): uint {
            return self.foo - self.bar
        }
    }
}

generic structs 3

expectations := .{
    return_value: 10,
}

main := fn(): uint {
    vl: Foo(Foo(uint)) = .(.(10))
    return vl.sub().sub()
}

Foo := fn(F: type): type return struct {
    .foo: F

    sub := fn(self: @CurrentScope()): F {
        return self.foo
    }
}

generic structs 4

expectations := .{
    return_value: 6,
}

main := fn(): uint {
    val: Array(uint, 3) = .(1, .(2, .(3, .())))
    return val.get(0) + val.get(1) + val.get(2)
}

Array := fn(E: type, len: uint): type if len == 0 {
    return struct {
        get := fn(self: @CurrentScope(), i: uint): E die
    }
} else {
    Next := Array(E, len - 1)
    return struct {
        .elem: E;
        .next: Next

        get := fn(self: @CurrentScope(), i: uint): E {
            if i == 0 return self.elem

            return self.next.get(i - 1)
        }
    }
}

generic structs 5 (iterators)

main := fn(): uint {
    ref := "abcd"

    start := "ab"
    end := "cd"

    Next := fn(T: type): type return struct {
        .next: bool;
        .val: T
        none := fn(): @CurrentScope() {
            return .(false, idk)
        }
        some := fn(val: T): @CurrentScope() {
            return .(true, val)
        }
    }

    Chars := struct {
        .slc: []u8
        next := fn(self: ^@CurrentScope()): Next(u8) {
            if self.slc.len == 0 return .none()
            defer self.slc = self.slc[1..]
            return .some(self.slc[0])
        }
    }

    Chain := fn(A: type, B: type): type return struct {
        .state: enum{.a; .b; .done};
        .a: A;
        .b: B

        Elem := @TypeOf(A.next(idk))

        next := fn(self: ^@CurrentScope()): Elem {
            loop match self.state {
                .a => {
                    nxt := self.a.next()
                    if nxt.next return nxt
                    self.state = .b
                },
                .b => {
                    nxt := self.b.next()
                    if nxt.next return nxt
                    self.state = .done
                },
                .done => return .none(),
            }
        }
    }

    siter := Chars.(start)
    riter := Chars.(ref)
    citer := Chain(Chars, Chars).(.a, .(start), .(end))

    loop {
        sc := siter.next()
        if !sc.next break
        rc := riter.next()
        if !rc.next return 1

        if sc != rc return 2
    }

    riter = .(ref)
    loop {
        rc := riter.next()
        if !rc.next break
        sc := citer.next()
        if !sc.next return 3

        if sc != rc return 4
    }

    return 0
}

generic structs 6 (more iterators)

main := fn(): uint {
    _ = @use("foo.hb").foo(0, u8)
    return 0
}

// in: foo.hb
Map := fn(I: type, F: type): type return struct{}
foo := fn(vl: int, $oo: type): Map(u8, oo) return .()

generic structs 7 (template method call)

A := struct {
    apply := fn(self: ^@CurrentScope(), $func: type): void {
    }
}

main := fn(): uint {
    z := A.()
    y := z.apply(fn(): void {
    })
    return 0
}

comptime 1

main := fn(): uint {
    some_int := uint
    some_fn := fn($ui: type, x: some_int): ui {
        val: some_int = x + 1
        return val * val
    }

    return some_fn(some_int, 9) - (some_fn(some_int, 5) + some_fn(some_int, 7))
}

comptime 2

expectations := .{
    return_value: 33,
}

main := fn(): uint {
    some_int := uint

    some_fn := fn(): some_int {
        return 1
    }

    some_fn2 := fn(): some_int {
        return some_fn() + 1
    }

    some_fn3 := fn($fnc: type): type {
        return fn(): some_int {
            return fnc() + 10
        }
    }

    return some_fn3(some_fn)() + some_fn3(some_fn3(some_fn2))()
}

comptime 3 (errors)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    some_int := 1

    not_a_closure := fn(): uint {
        return some_int
    }

    return not_a_closure()
}

comptime 4 (@TypeOf)

main := fn(): uint {
    some_int := 0

    Ty := struct{.field: @TypeOf(some_int)}

    return Ty.(some_int).field
}

tuples 1

expectations := .{
    return_value: 6,
}

main := fn(): uint {
    tuple := .(1, 2, 3)
    return tuple[0] + tuple[1] + tuple[2]
}

struct operators 1

expectations := .{
    return_value: 10,
}

Point := struct {
    .x: uint;
    .y: uint;
}

Rect := struct {
    .a: Point;
    .b: Point;
}

Color := struct{.b: u8; .g: u8; .r: u8; .a: u8}

main := fn(): uint {
    i := Color.(0, 0, 0, 0)
    i += .(1, 1, 1, 1)
    if i.r + i.g + i.b + i.a != 4 return 1001

    if Point.(1, 1) != Point.(1, 1) return 1002
    if Point.(1, 2) == Point.(1, 1) return 1003

    a := Point.(1, 2)
    b := Point.(3, 4)

    d := Rect.(a + b, b - a)
    zp := Point.(0, 0)
    d2 := Rect.(zp - b, a)
    d2 += d

    c := d2.a + d2.b
    return c.x + c.y
}

unions 1

expectations := .{
    return_value: 257,
}

main := fn(): uint {
    Union := union {
        .single: u16;
        .pair: struct{.l: u8; .r: u8};
    }

    val := Union.{pair: .(1, 1)}

    return val.single
}

unions 2

expectations := .{
    return_value: 42,
}

main := fn(): int {
    Number := union {
        ._int: int;
        ._uint: uint;
    }

    n1 := Number.{_int: 42}

    return n1._int
}

enums 1

expectations := .{
    return_value: 1,
}

main := fn(): uint {
    Enum := enum{.A; .B; .C}

    n1 := Enum.A
    n2: Enum = .B

    if n1 == n2 return 10

    return @as(u8, n1) + n2
}

enums 2 (one variant)

main := fn(): uint {
    return Target.current().Lib().page_size()
}

ableos := @use("ableos.hb")

Target := enum {
    .AbleOS

    current := fn(): Target {
        $if @target("ableos") return .AbleOS
        @error("Unknown Target")
    }

    Lib := fn(target: Target): type {
        match target {
            .AbleOS => return ableos,
        }
    }
}

// in: ableos.hb
page_size := fn(): uint return 0

enums 3 (customization)

main := fn(): uint {
    Enm := enum(u32) {
        .a = 1;
        .b;
        .c = 0
    }

    if @as(Enm, @bit_cast(@as(u32, 1))) != .a return 1
    if @as(Enm, @bit_cast(@as(u32, 2))) != .b return 2
    if @as(Enm, @bit_cast(@as(u32, 0))) != .c return 3

    return 0
}

enums 4

expectations := .{
    return_value: 69,
}

NameMap := fn($Enum: type): type {
    sum := 0
    i: u8 = 0
    $loop $if i == @len_of(Enum) break else {
        sum += @int_cast(@name_of(@as(Enum, @bit_cast(i))).len)
        i += 1
    }

    StrBuf := [sum]u8
    IndexBuf := [@len_of(Enum) + 1]uint
    return struct {
        .buf: StrBuf;
        .index: IndexBuf

        new := fn(): @CurrentScope() {
            buf: StrBuf = idk
            index: IndexBuf = idk
            index[0] = 0

            ii: u8 = 0
            bi := 0
            $loop $if ii == @len_of(Enum) break else {
                name := @name_of(@as(Enum, @bit_cast(ii)))
                ij := 0
                $loop $if ij == name.len break else {
                    buf[bi + ij] = name[ij]
                    ij += 1
                }

                bi += @int_cast(name.len)
                ii += 1
                index[ii] = bi
            }

            return .(buf, index)
        }

        get := fn(self: ^@CurrentScope(), k: Enum): []u8 {
            return self.buf[self.index[k]..self.index[@as(u8, k) + 1]]
        }
    }
}

Nm := enum{.E; .bcd; .cd}

map := NameMap(Nm).new()

main := fn(): uint {
    return map.get(.E)[0]
}

enums 5

expectations := .{
    return_value: 0,
}

Enum := enum{.A; .B; .C}

main := fn(): uint {
    if Enum.C > .A {
        return 0
    }
    return 1
}

match 1

main := fn(): uint {
    Nm := enum{.a; .b; .c}

    match Nm.a {
        .a => {
        },
        Nm.b => return 1,
        .c => return 2,
    }

    match Nm.b {
        .a => return 3,
        _ => return 0,
    }
}

match 2 (comptime)

main := fn(): uint {
    $match enum{.a}.a {
        .a => {
        },
    }

    $match enum{.a; .b}.a {
        .a => return 0,
        .b => return 1,
    }
}

match 3 (errors)

expectations := .{
    should_error: true,
}

main := fn(): void {
    match enum{.a; .b}.a {
    }

    match enum{.a; .b}.a {
        .a => {
        },
    }

    $match enum{.a; .b}.a {
    }

    $match enum{.a; .b}.a {
        .b => {
        },
    }
}

defer 1

main := fn(): uint {
    i := 0
    {
        defer i += 1
        if i == 1 return 1
    }

    if i != 1 return 2

    loop {
        defer i += 1
        if i == 3 continue
        if i == 4 break
    }

    if i != 5 return 3

    ret_defer := fn(str: ^uint): void {
        defer str.* += 1
    }

    v := 0
    ret_defer(&v)
    if v != 1 return 4

    return 0
}

die 1

expectations := .{
    unreaches: true,
}

main := fn(): uint {
    if false return 1
    die
}

die 2

expectations := .{
    unreaches: true,
}

fallible := fn(): ?^u8 {
    return null
}

main := fn(): void {
    a := fallible()
    if a == null die
    die
}

global variables 1

counter := 0

dec := fn(): void {
    counter -= 1
}

main := fn(): uint {
    counter = 1
    dec()
    return counter
}

global variables 2

expectations := .{
    return_value: 55,
}

some_other := 10

some_fib := fib(some_other)

fib := fn(n: uint): uint {
    if n != 10 return 0
    return 55
}

bigon_era := some_other - 10

main := fn(): uint {
    return some_fib - bigon_era
}

global variables 3

expectations := .{
    return_value: 3,
}

a: uint = 0
b: uint = a + 1
c: uint = b + 1
d: uint = c + 1

main := fn(): uint {
    return d
}

strings

expectations := .{
    return_value: 69,
}

main := fn(): uint {
    return "\t\{45}dvard\r\nඞ\0"[1]
}

string errors

expectations := .{
    should_error: true,
}

main := fn(): void {
    _ = "\ඞ"
}

arrays 1

expectations := .{
    return_value: 28,
}

main := fn(): uint {
    arr: [8]uint = idk

    i := 0
    loop if i == arr.len break else {
        arr[i] = i
        i += 1
    }

    i = 0
    sum := 0
    loop if i == arr.len break else {
        sum += arr[i]
        i += 1
    }

    return sum
}

arrays 2

expectations := .{
    return_value: 9,
}

dim: uint = 3

main := fn(): uint {
    narr: [dim][dim]uint = idk

    y := 0
    loop if y == narr.len break else {
        x := 0
        loop if x == narr[y].len break else {
            narr[y][x] = x * y
            x += 1
        }
        y += 1
    }

    linarr: ^[dim * dim]uint = @bit_cast(&narr)

    sum := 0
    i := 0
    loop if i == linarr.len break else {
        sum += linarr[i]
        i += 1
    }

    return sum
}

arrays 3 (errors)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    arr := uint.[0, 1, 2]
    return arr[3]
}

slices 1

expectations := .{
    return_value: 50,
}

main := fn(): uint {
    arr := u8.[1, 2, 3, 4]
    slice := arr[..]

    slices := ([]u8).[arr[..], arr[..2], arr[2..], arr[1..3], slice[..], slice[..2], slice[2..], slice[1..3]]

    sum := 0
    i := 0
    loop if i == slices.len break else {
        j := 0
        loop if j == slices[i].len break else {
            sum += slices[i][j]
            j += 1
        }
        i += 1
    }

    return sum
}

opaque := fn(): uint return 0

slices 2

expectations := .{
    return_value: 25,
}

main := fn(): uint {
    arr := uint.[1, 2, 3, 4]
    slice := arr[..]

    slices := ([]uint).[arr[..], arr.ptr[..arr[1]], arr[arr[1]..], arr.ptr[arr[0]..arr[2]]]

    sum := 0
    i := 0
    loop if i == slices.len break else {
        j := 0
        loop if j == slices[i].len break else {
            sum += slices[i][j]
            j += 1
        }
        i += 1
    }

    return sum
}

slices 3

reverse := fn(slice: []u8): []u8 {
    if slice.len == 0 return slice
    j := slice.len - 1
    i := 0
    temp: u8 = 0
    loop if i < j {
        temp = slice[i]
        slice[i] = slice[j]
        slice[j] = temp
        i += 1
        j -= 1
    } else return slice
}

main := fn(): uint {
    arr := u8.[1, 2, 3]
    _ = reverse(&.[])
    _ = reverse(arr[..])
    return arr[0] - arr[1] - arr[2]
}

slices 4

equals := fn(lhs: []u8, rhs: []u8): bool {
    if lhs.len != rhs.len return false
    if lhs.ptr == rhs.ptr return true
    i := 0
    loop if i == lhs.len break else {
        if lhs[i] != rhs[i] return false
        i += 1
    }
    return true
}

main := fn(): uint {
    abc := "abc"
    a_b_c := u8.['a', 'b', 'c'][..]
    if !equals(abc, abc) return 1
    if !equals(a_b_c, abc) return 1

    return 0
}

nullable types 1

expectations := .{
    return_value: 10,
}

main := fn(): uint {
    ten := mkval(uint, 10)

    if ten == null return 1

    if mknull(uint) != null return 2

    return ten.?
}

mknull := fn($T: type): ?T return null
mkval := fn($T: type, val: T): ?T return val

nullable types 2

Stru := struct {
    .a: uint;
    .b: uint;
}

main := fn(): uint {
    nlbl: ?Stru = .(0, 0)
    other: ?Stru = .{a: 0, b: 0}
    othera: ?[2]uint = .[0, 0]

    if nlbl == null return 1
    if other == null return 2
    if othera == null return 3

    nlbl.?.b = 1
    take(&nlbl.?)

    return nlbl.?.a - nlbl.?.b
}

take := fn(s: ^Stru): void {
    s.a += 1
}

struct patters 1

expectations := .{
    return_value: 3,
}

foo.{bar, bas: .{baz: bax}} := @use("foo.hb")

main := fn(): uint {
    return foo.foo() + bar() + bax()
}

// in: foo.hb
foo := fn(): uint return 0
bar := fn(): uint return 1
bas := @use("bas.hb")
// in: bas.hb
baz := fn(): uint return 2

directives 1 (@size_of, @align_of)

expectations := .{
    return_value: 3,
}

main := fn(): uint {
    return @size_of(struct{.b: u16; .a: u8}) - @align_of(u8)
}

directives 2 (@as)

expectations := .{
    return_value: 3,
}

main := fn(): uint {
    val := @as(struct{.a: uint}, .(3))
    return val.a
}

directives 3 (@ecall)

expectations := .{
    return_value: 3,
    ecalls: .(
        .(0, 1, 2): 3,
    ),
}

main := fn(): uint {
    return @ecall(
        0,
        struct{.a: uint; .b: uint}.(1, 2),
        struct{.a: uint; .b: uint; .c: uint}.(3, 4, 5),
    )
}

directives 4 (@int_cast)

main := fn(): u8 {
    v: uint = 0
    return @int_cast(v)
}

directives 5 (@bit_cast)

main := fn(): u32 {
    return @bit_cast(@as(struct{.l: u16; .r: u16}, @bit_cast(@as(u32, 0))))
}

directives 6 (@len_of)

expectations := .{
    return_value: 4,
}

main := fn(): uint {
    return @len_of(struct{.a: u8; .b: u32}) + @len_of([2]u8)
}

directives 7 (@kind_of)

expectations := .{
    return_value: 7,
}

main := fn(): uint {
    return @kind_of(struct{})
}

directives 8 (@ChildOf)

expectations := .{
    return_value: 1,
}

main := fn(): uint {
    vl := 1
    return deref(^uint, &vl)
}

deref := fn($T: type, arg: T): @ChildOf(T) {
    return arg.*
}

directives 9 (@embed)

expectations := .{
    return_value: 69,
}

main := fn(): uint {
    val: ^[1]u8 = &@embed("mbed.txt")
    return val[0]
}

// in: mbed.txt
E

directives 10 (@error)

expectations := .{
    should_error: true,
}

main := fn(): uint {
    $if false @error("never happens")
    @error("no main in sight, here is a type: ", ^u8)
}

directives 11 (@target, @is_comptime)

expectations := .{
    return_value: 1,
}

global: bool = @target("ableos")

main := fn(): uint {
    $if @is_comptime() @error("unecpected")

    return global
}

directives 12 (@inline)

expectations := .{
    return_value: 10,
}

main := fn(): uint {
    // NOTE: does nothing right now
    return @inline(foo, 10)
}

foo := fn(vl: uint): uint return vl

directives 13 (@Any)

expectations := .{
    return_value: 10,
}

main := fn(): uint {
    _ = func(@as(uint, 1), 2)
    return foo(5) + foo(@as(u8, 5))
}

foo := fn(vl: @Any()): @TypeOf(vl) {
    return vl
}

func := fn(a: @Any(), b: @TypeOf(a)): uint {
    return 0
}

directives 14 (@name_of)

expectations := .{
    return_value: 7,
}

main := fn(): uint {
    return @name_of(uint).len + @name_of(enum{.foo}.foo).len
}

progress

  • ? diagnostics
    • ? don't crash on cycles
  • control flow
    • functions
      • ? inlining
        • noop compatibility
      • scope inference
      • comptime parameters
    • ifs
      • comptime
    • loops
      • comptime
      • break
      • continue
      • infinite
        • clean up the hacky graph hierarchy
      • ? labels
    • blocks
      • ? labels
    • match
      • comptime
    • defer
    • die
  • import pattern matching
  • global variables
    • strings
    • comptime evaluation
    • ? references
    • ? immutable
      • noop compatibility
  • types
    • idk
    • integers/bool
      • bool literals
      • integer literals
        • binary
        • octal
        • decimal
        • hexadecimal
      • binary operators
        • - + * / % == != <= >= < > << >> | ^ &
      • unary operators
        • - ! ~
    • floats
      • binary operators
        • - + * / == != <= >= < >
      • unary operators
        • -
    • structs
      • indexing
      • alignment
      • ? field alignment
      • constructors
        • dictionary
        • tuple
      • default values
      • scope
      • file
      • operators
      • comparison
    • enums
      • ? specific values
      • ? backing integer
      • scope
    • unions
      • ? alignment
      • ? field alignment
      • ? tag + customizable
      • scope
    • pointers
      • slicing
    • slices
      • known size (arrays)
      • field access
      • indexing
      • slicing
      • empty
      • ? array slicing
    • tuples
    • nullable types
  • ? directives
    • @use(<string>): <struct>
    • @TypeOf(<expr>): type
    • @as(<ty>, <expr>): <ty>
    • @int_cast(<int>): <infered-int>
    • @size_of(<ty>): uint
      • comptime interrupt
    • @align_of(<ty>): uint
      • comptime interrupt
    • @bit_cast(<expr>): <infered-ty>
    • @ecall(...<expr>): <infered-ty>
    • @embed(<string>): [len]u8
    • ? @inline(<func>, ...<args>): <func>.ret
      • noop compatibility
    • @len_of(<ty>): uint
      • comptime interrupt
    • @kind_of(<ty>): u8
      • comptime interrupt
    • @Any(<fn(type): void/type>..): type
      • ? type filters
    • @error(...<expr>): never
    • ? @compiles(<expr>): bool
    • @ChildOf(<ty>): type
      • comptime interrupt
    • @target("<pat>"): bool
    • @is_comptime(): bool
    • @int_to_float(<int>): <float>
    • @float_to_int(<float>): int
    • @float_cast(<float>): <float>
    • @name_of(<ty>): []u8
      • comptime interrupt
    • ? @recall(..<args>): never
  • optimizations
    • assumptions
    • memory
      • uninitialized global memory
      • constant global loads
      • stack elimination
      • load alias reordering
        • around stores
        • around calls
      • store->load forwarding
      • splitting
      • mem2reg
        • scalar locals
        • arbitrary locals
    • compute
      • folding
      • algebra
    • control flow
      • inlining (heuristic)
      • loop unrolling
      • folding
    • clonable insructions
    • allocate abi registers
  • static analisys
    • source locations
    • constraint propagation
      • provenance
    • returning stack reference
      • trivial direct pointer
      • trough memory
      • ? trough memcpy
    • uninitialized reads
    • unreachable loop breaks
    • loop invariant conditions
    • dead code reporting
    • local out of bounds read/write
      • scalar read/write
      • memcpy
    • semantic assertions
      • null checks
      • bound checks

vendored tests

git submodule add https://git.ablecorp.us/lily-org/lily.git vendored-tests/lily # add a new
git submodule update --init --recursive                                         # fetch
git submodule update --remote --rebase -- vendored-tests/lily/                  # update a

Contributing

When contributing make sure to:

  1. Mark what you completed in progress, as the part of your changes. Stuff that is not in the checklist needs an issue. TODO: make an issue template
  2. Add apropriate examples to test the functionality, zig build test will automatically generate a test for the example and run it with rverithing else. It's preferable to add #### <feature> <n> and use n = 1 for demonstration and other examples to cover all ways to use the frature you can think of.
  3. Extend the fuzzer to generate newly added syntax and run zig build fuzz.