hello world

This commit is contained in:
koniifer 2024-12-31 18:23:56 +00:00
parent c040aced10
commit 5232c465d2
11 changed files with 394 additions and 2 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target/

View file

@ -1,3 +1,19 @@
# lily
# Lily
an attempt at a cross-platform standard library for hblang.<br>
use `./build help` to see available build commands.
a cross-platform standard library for hblang
> [!IMPORTANT]
> all features, targets, etc, are provisional and subject to change
### To change build target
> [!CAUTION]
> this is currently unsupported the `./build` script.
set `target` in `src/lib/lib.hb`, choose one of:
- `target_c_native` `(TARGET=x86_64-unknown-linux-gnu) or similar`
- `target_hbvm_ableos` `(TARGET=unknown-virt-unknown)`
## Currently "working" features
- `std.vec.Vec`
- `std.alloc.{SimpleAllocator, RawAllocator}`
- `std.{Type, Kind, exit}`

54
build Executable file
View file

@ -0,0 +1,54 @@
#!/bin/sh
set -eu
build_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
arg=${1:-""}
default_linker=${2:-""}
if [ "$arg" = "help" ]; then
echo "no arguments: build [OPTIONAL: linker]"
echo "run [OPTIONAL: linker]: build and run"
echo "help: show this"
echo "example: ./build run"
echo "example: ./build run 'zig cc'"
exit 0
fi
if ! command -v hbc > /dev/null 2>&1; then
echo "hblang compiler (hbc) not found. install it with:" >&2
echo "cargo +nightly install --git https://git.ablecorp.us/ableos/holey-bytes hbc" >&2
exit 1
fi
linker=$default_linker
if [ -z "$linker" ]; then
set -- "zig cc" "clang" "gcc"
for linker; do
cmd=$(echo "$linker" | awk '{print $1}')
if command -v "$cmd" > /dev/null 2>&1; then break; fi
done
if [ -z "$linker" ]; then
echo "could not find any of the following:" "$@" ". please install any, or add to PATH." >&2
exit 1
fi
fi
if ! [ -e "$build_dir/target/build" ]; then
mkdir -p "$build_dir/target/build"
elif ! [ -w "$build_dir/target/build" ]; then
echo "$build_dir/target/build is not writable." >&2
exit 1
fi
if hbc "$build_dir/src/main.hb" > "$build_dir/target/build/lily.o"; then
$linker "$build_dir/target/build/lily.o" -o "$build_dir/target/lily"
strip -s "$build_dir/target/lily"
else
echo "compilation failed :(" >&2
exit 1
fi
if [ "$arg" = "run" ]; then
"$build_dir"/target/lily
fi

90
src/lib/alloc.hb Normal file
View file

@ -0,0 +1,90 @@
.{collections: .{Vec}, target, target_c_native, target_hbvm_ableos} := @use("lib.hb")
Allocation := struct {
ptr: ^void,
len: uint,
}
// ! THIS ALLOCATOR IS TEMPORARY
SimpleAllocator := struct {
allocations: Vec(Allocation, RawAllocator),
raw: RawAllocator,
$new := fn(): Self {
raw := RawAllocator.new()
return .(Vec(Allocation, RawAllocator).new(&raw), raw)
}
deinit := fn(self: ^Self): void {
i := 0
loop if i == self.allocations.len() break else {
defer i += 1
alloced := self.allocations.get_unchecked(i)
if target == target_c_native {
target.free(alloced.ptr)
} else if target == target_hbvm_ableos {
target.free(alloced.ptr, alloced.len)
}
};
*self = Self.new()
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := target.malloc(count * @sizeof(T))
// ! (compiler?) bug: null check broken, so unwrapping (unsafe!)
if target == target_hbvm_ableos {
self.allocations.push(.(@unwrap(ptr), count))
}
return @bitcast(ptr)
}
free := fn(self: ^Self, $T: type, ptr: ^T): void {
if target == target_c_native {
target.free(@bitcast(ptr))
} else if target == target_hbvm_ableos {
alloced := self._find(@bitcast(ptr))
if alloced == null return;
target.free(@bitcast(ptr), alloced.len)
}
}
_find := fn(self: ^Self, ptr: ^void): ?Allocation {
i := 0
loop if i == self.allocations.len() break else {
defer i += 1
result := self.allocations.get(i)
if !result.is_ok return null
alloced := result.unwrap_unchecked()
if alloced.ptr == ptr return alloced
}
return null
}
}
// ! THIS ALLOCATOR IS *ALSO* TEMPORARY
RawAllocator := struct {
ptr: ^void,
size: uint,
$new := fn(): Self return .(@bitcast(0), 0)
deinit := fn(self: ^Self): void {
if self.size != 0 {
if target == target_c_native {
target.free(@bitcast(self.ptr))
} else if target == target_hbvm_ableos {
target.free(@bitcast(self.ptr), self.size)
}
};
*self = Self.new()
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := target.malloc(count * @sizeof(T))
if ptr != null {
self.ptr = ptr
self.size = count * @sizeof(T)
}
return @bitcast(ptr)
}
free := fn(self: ^Self, $T: type, ptr: ^T): void {
if target == target_c_native {
target.free(@bitcast(self.ptr))
} else if target == target_hbvm_ableos {
target.free(@bitcast(self.ptr), self.size)
}
}
}

View file

@ -0,0 +1,7 @@
.{Vec, RawVec} := @use("vec.hb")
Error := enum {
KeyNotFound,
OutOfRange,
Full,
}

View file

@ -0,0 +1,54 @@
.{target, target_c_native, target_hbvm_ableos, result: .{Result}} := @use("../lib.hb");
.{Error} := @use("lib.hb")
Vec := fn($T: type, $A: type): type return struct {
slice: []T,
allocator: ^A,
cap: uint,
$new := fn(allocator: ^A): Self return .{slice: T.[][..], allocator, cap: 0}
deinit := fn(self: ^Self): void {
if self.slice.len != 0 {
self.allocator.free(T, self.slice.ptr)
};
*self = Self.new(self.allocator)
}
push := fn(self: ^Self, value: T): void {
if self.slice.len == self.cap {
if self.cap == 0 {
self.cap = 1
} else {
self.cap *= 2
}
// ! (compiler?) bug: null check broken, so unwrapping (unsafe!)
new_alloc := @unwrap(self.allocator.alloc(T, self.cap))
if self.slice.len != 0 {
target.memmove(@bitcast(new_alloc), @bitcast(self.slice.ptr), self.slice.len * @sizeof(T))
self.allocator.free(T, self.slice.ptr)
}
self.slice.ptr = @as(^T, new_alloc)
};
self.slice[self.slice.len] = value
self.slice.len += 1
}
get := fn(self: ^Self, n: uint): Result(T, Error) {
if n >= self.slice.len return Result(T, Error).err(.OutOfRange)
return Result(T, Error).ok(self.slice[n])
}
get_ref := fn(self: ^Self, n: uint): Result(^T, Error) {
if n >= self.slice.len return Result(T, Error).err(.OutOfRange)
return Result(T, Error).ok(self.slice.ptr + n)
}
$get_unchecked := fn(self: ^Self, n: uint): T return self.slice[n]
$get_ref_unchecked := fn(self: ^Self, n: uint): T return self.slice.ptr + n
pop := fn(self: ^Self): Result(T, Error) {
if self.slice.len == 0 return Result(T, Error).err(.OutOfRange)
temp := self.slice[self.slice.len - 1]
self.slice = self.slice[..self.slice.len - 1]
return Result(T, Error).ok(temp)
}
$len := fn(self: ^Self): uint return self.slice.len
$capacity := fn(self: ^Self): uint return self.cap
}

64
src/lib/lib.hb Normal file
View file

@ -0,0 +1,64 @@
Version := struct {
major: uint,
minor: uint,
patch: uint,
}
$STDLIB_VERSION := Version(0, 0, 1)
collections := @use("collections/lib.hb")
result := @use("result.hb")
alloc := @use("alloc.hb")
target_c_native := @use("target/c_native.hb")
target_hbvm_ableos := @use("target/hbvm_ableos.hb")
// target := target_hbvm_ableos
target := target_c_native
$exit := fn(code: int): void {
_ = target.exit(code)
die
}
Kind := enum {
Builtin,
Struct,
Tuple,
Enum,
Union,
Pointer,
Slice,
Opt,
Function,
Template,
Global,
Const,
Module,
}
Type := fn($T: type): type return struct {
$is_unsigned_int := fn(): bool {
return T == uint | T == u8 | T == u16 | T == u32
}
$is_signed_int := fn(): bool {
return T == int | T == i8 | T == i16 | T == i32
}
$is_int := fn(): bool {
return Self.unsigned_int(T) | Self.signed_int(T)
}
$is_float := fn(): bool {
return T == f32 | T == f64
}
$usize := fn(): type {
if @sizeof(T) == 1 return u8 else if @sizeof(T) == 2 return u16 else if @sizeof(T) == 4 return u32 else return uint
}
$bits := fn(): Self.usize() {
return @sizeof(T) << 3
}
$bitmask := fn(): Self.usize() {
return -1
}
$kind := fn(): Kind {
return @bitcast(@kindof(T))
}
}

16
src/lib/result.hb Normal file
View file

@ -0,0 +1,16 @@
.{exit} := @use("lib.hb")
ResultInner := fn($T: type, $E: type): type return union {ok: T, err: E}
Result := fn($T: type, $E: type): type return struct {
inner: ResultInner(T, E),
is_ok: bool,
$ok := fn(k: T): Self return .(.{ok: k}, true)
$err := fn(k: E): Self return .(.{err: k}, false)
$unwrap := fn(self: Self): T return self.expect("Panic: Unwrap on Error Variant.\n\0")
$unwrap_unchecked := fn(self: Self): T return self.inner.ok
unwrap_or := fn(self: Self, v: T): T if self.is_ok return self.inner.ok else return v
unwrap_or_else := fn(self: Self, $F: type): T if self.is_ok return self.inner.ok else return F(self.inner.err)
expect := fn(self: Self, msg: []u8): T if self.is_ok return self.inner.ok else exit(1)
}

View file

@ -0,0 +1,6 @@
malloc := fn(size: uint): ?^void @import()
free := fn(ptr: ^void): void @import()
memmove := fn(dest: ^void, src: ^void, size: uint): void @import()
memcopy := fn(dest: ^void, src: ^void, size: uint): void @import()
memset := fn(dest: ^void, src: ^void, size: uint): void @import()
exit := fn(code: int): void @import()

View file

@ -0,0 +1,64 @@
$PAGE_SIZE := 4096
$MAX_ALLOC := 0xFF
$MAX_FREE := 0xFF
$calculate_pages := fn(size: uint): uint {
return (size + PAGE_SIZE - 1) / PAGE_SIZE
}
RqPageMsg := packed struct {a: u8, count: u8}
$request_pages := fn(count: u8): ?^void {
return @eca(3, 2, &RqPageMsg.(0, count), @sizeof(RqPageMsg))
}
FreePageMsg := packed struct {a: u8, count: u8, ptr: ^void}
$free_pages := fn(ptr: ^void, count: u8): void {
return @eca(3, 2, &FreePageMsg.(1, count, ptr), @sizeof(FreePageMsg))
}
malloc := fn(size: uint): ?^void {
pages := calculate_pages(size)
if pages <= MAX_ALLOC {
return @bitcast(request_pages(@intcast(pages)))
}
ptr := request_pages(MAX_ALLOC)
if ptr == null return null
pages -= MAX_ALLOC
loop if pages <= MAX_ALLOC break else {
if request_pages(MAX_ALLOC) == null return null
pages -= MAX_ALLOC
}
if request_pages(@intcast(pages)) == null return null
return @bitcast(ptr)
}
free := fn(ptr: ^void, size: uint): void {
pages := calculate_pages(size)
if pages <= MAX_FREE {
return free_pages(ptr, @intcast(pages))
}
loop if pages <= MAX_FREE break else {
free_pages(ptr, MAX_FREE)
ptr += PAGE_SIZE * MAX_FREE
pages -= MAX_FREE
}
free_pages(ptr, @intcast(pages))
}
CopyMsg := packed struct {a: u8, count: uint, src: ^void, dest: ^void}
$memcopy := fn(dest: ^void, src: ^void, size: uint): void {
return @eca(3, 2, &CopyMsg.(4, size, src, dest), @sizeof(CopyMsg))
}
SetMsg := packed struct {a: u8, count: uint, size: uint, src: ^void, dest: ^void}
$memset := fn(dest: ^void, src: ^void, size: uint): void {
return @eca(3, 2, &SetMsg.(5, size, 1, src, dest), @sizeof(SetMsg))
}
memmove := fn(dest: ^void, src: ^void, size: uint): void {
memcopy(dest, src, size)
memset(src, @bitcast(&0), size)
}
$exit := fn(code: int): void {
}

20
src/main.hb Normal file
View file

@ -0,0 +1,20 @@
std := @use("lib/lib.hb");
Allocator := std.alloc.SimpleAllocator
Vec := std.collections.Vec
main := fn(argc: uint, argv: []^u8): uint {
allocator := Allocator.new()
defer allocator.deinit()
vec := Vec(int, Allocator).new(&allocator)
defer vec.deinit()
i: int = 0
loop if i == 97 break else {
defer i += 1
vec.push(i)
}
// ! (compiler?) (target_c_native?) bug: not popping here causes len & cap to be zero
_ = vec.pop()
return vec.len()
}