hello world
This commit is contained in:
parent
c040aced10
commit
5232c465d2
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target/
|
20
README.md
20
README.md
|
@ -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
54
build
Executable 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
90
src/lib/alloc.hb
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
7
src/lib/collections/lib.hb
Normal file
7
src/lib/collections/lib.hb
Normal file
|
@ -0,0 +1,7 @@
|
|||
.{Vec, RawVec} := @use("vec.hb")
|
||||
|
||||
Error := enum {
|
||||
KeyNotFound,
|
||||
OutOfRange,
|
||||
Full,
|
||||
}
|
54
src/lib/collections/vec.hb
Normal file
54
src/lib/collections/vec.hb
Normal 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
64
src/lib/lib.hb
Normal 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
16
src/lib/result.hb
Normal 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)
|
||||
}
|
6
src/lib/target/c_native.hb
Normal file
6
src/lib/target/c_native.hb
Normal 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()
|
64
src/lib/target/hbvm_ableos.hb
Normal file
64
src/lib/target/hbvm_ableos.hb
Normal 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
20
src/main.hb
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue