lots of work

improve build script
reorganise stuff
fix some bugs
improve Type struct
start working on string, fmt, rand
null terminator no longer required on strings
better document bugs
minor opts
This commit is contained in:
koniifer 2025-01-05 16:50:02 +00:00
parent 2f74ac8cc0
commit fb67c9b382
15 changed files with 650 additions and 256 deletions

View file

@ -5,19 +5,33 @@ use `./build -h` to see available arguments. supports:
- custom linker (for `c_native`)
- running the executable (for `c_native`)
- setting output path
- only recompiling if either source or environment change
> [!IMPORTANT]
> all features, targets, etc, are provisional and subject to change
### To change build target
create the file `src/lib/target/target.hb` with the contents `target := @use("c_native.hb")` (example)<br>
manually:
> create the file `src/lib/target/target.hb` with the contents `target := @use("c_native.hb")` (example)
automatically:
> use the `-t` flag supplied in `./build`
currently available targets:
- `c_native`
- `hbvm_ableos`
alternatively, use `./build -t c_native` (example)
## Currently "working" features
- `std.vec.Vec`
- `std.alloc.{SimpleAllocator, RawAllocator}`
- `std.{Type, Kind, exit}`
### Currently "working" features
- `lily.{Type, TypeOf, Kind, exit, panic, memcpy, memmove, memset}`
- `lily.log.{log, info, error, warn, debug, trace}`
- `lily.result.Result`
### Currently "in progress" features
- `lily.rand.SimpleRandom`
- `lily.log.{print, printf}`
### Currently "broken due to compiler" features
- `lily.collections.SparseVec`
- `lily.alloc.{SimpleAllocator, RawAllocator}`

308
build
View file

@ -1,197 +1,167 @@
#!/bin/sh
set -eu
build_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
check_for_update=0
SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
readonly SCRIPT_DIR
readonly HBC_COMMIT="18e8a831"
readonly SRC_DIR="$SCRIPT_DIR/src"
readonly DEFAULT_OUT="$SCRIPT_DIR/target/lily"
readonly BUILD_PATH="$SCRIPT_DIR/target/build"
readonly CHECKSUM_FILE="$BUILD_PATH/checksum"
readonly TARGET_FILE="$SRC_DIR/lib/target/target.hb"
linker=""
target="c_native"
run=0
out="$build_dir/target/lily"
out="$DEFAULT_OUT"
make_build_dir() {
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
die() {
printf "ERROR: %s\n" "$1" >&2
exit 1
}
log() {
printf "INFO: %s\n" "$1" >&2
}
check_hbc() {
has_hbc=1
if ! command -v hbc >/dev/null 2>&1; then
printf "Warning: hblang compiler not found in \$PATH\n"
has_hbc=0
fi
if command -v cargo >/dev/null 2>&1; then
cargo_output=$(cargo install --list | grep 'holey-bytes#' | awk -F'#' '{print substr($2, 1, 8)}')
command -v hbc >/dev/null 2>&1 || {
log "hblang compiler not found in \$PATH"
command -v cargo >/dev/null 2>&1 || die "hblang compiler is missing, and could not use cargo to install"
}
if [ -z "$cargo_output" ] && [ $has_hbc = 0 ]; then
printf "Note: Installing hblang compiler using cargo\n"
if ! cargo +nightly install --git https://git.ablecorp.us/ableos/holey-bytes hbc >/dev/null 2>&1; then
printf "Failed to install hblang compiler for whatever reason\n"
exit 1
fi
printf "Done installing\n"
return
fi
if [ $check_for_update = 1 ]; then
short_commit_hash=""
response=$(curl -s "https://git.ablecorp.us/api/v1/repos/ableos/holey-bytes/commits?limit=1")
if [ -n "$response" ]; then
commit_hash=$(printf '%s' "$response" | jq -r '.[0].sha' 2>/dev/null)
if [ -n "$commit_hash" ] && [ ! "$commit_hash" = "null" ]; then
short_commit_hash="$(printf '%s' "$commit_hash" | cut -c 1-8)"
fi
fi
if [ -n "$short_commit_hash" ]; then
if [ ! "$short_commit_hash" = "$cargo_output" ] && [ $has_hbc = 1 ]; then
printf "hblang compiler is out of date"
fi
fi
fi
elif [ $has_hbc = 0 ]; then
printf "Error: hblang compiler is missing, and could not use cargo to install hblang compiler."
exit 1
fi
command -v cargo >/dev/null 2>&1 && {
cargo_ver=$(cargo install --list | awk '/holey-bytes/ {match($0, /[0-9a-f]{8}/); if (RSTART) print substr($0, RSTART, 8)}')
[ "${cargo_ver:-}" = "$HBC_COMMIT" ] && return 0
log "Local hbc version: ${cargo_ver:-None}, Required: $HBC_COMMIT. Installing correct version..."
cargo +nightly install --git https://git.ablecorp.us/ableos/holey-bytes hbc --debug --rev "$HBC_COMMIT" >/dev/null 2>&1 ||
die "Failed to install hblang compiler"
log "hblang compiler installed successfully"
}
}
check_out_dir() {
if ! [ -w "$(dirname "${out}")" ]; then
printf "ERROR: Out directory does not exist or is not writeable"
exit 1
fi
check_changes() {
[ -r "$CHECKSUM_FILE" ] && previous_checksum=$(cat "$CHECKSUM_FILE")
current_checksum=$(printf "%s%s%s%s" \
"$(find "$SRC_DIR" -type f -exec md5sum {} + | sort | md5sum)" \
"$linker" "$out" "$target" | md5sum)
echo "$current_checksum" >"$CHECKSUM_FILE"
[ "${previous_checksum:-}" != "$current_checksum" ]
}
set_target() {
echo "target := @use(\"./$target.hb\")" >"$build_dir/src/lib/target/target.hb"
}
while :; do
case ${1:-""} in
-h | --help)
printf "build script
-r/--run: run (Available only on target=c_native)
-l/--linker: set linker (Available only on target=c_native)
-t/--target: set target (One of: c_native, hbvm_ableos)
-o/--out: set output path
-h/--help: show help
examples
./build -t hbvm_ableos
./build -r -l 'zig cc' -t c_native"
exit
;;
-r | --run)
run=1
;;
-t | --target)
if [ -n "${2:-}" ]; then
target="${2:-}"
parse_args() {
while [ $# -gt 0 ]; do
case ${1:-""} in
-h | --help)
cat <<EOF
Build Script
Usage: $0 [options]
-r, --run Run the compiled output (Available only on target=c_native)
-l, --linker <linker> Specify linker (Available only on target=c_native)
-t, --target <target> Specify target (One of: c_native, hbvm_ableos)
-o, --out <path> Set output path
-h, --help Show this help message
Examples:
$0 -t hbvm_ableos
$0 -r -l 'zig cc' -t c_native
$0 -r
EOF
exit
;;
-r | --run) run=1 ;;
-t | --target)
[ -z "${2:-}" ] && die "'--target' requires a non-empty option argument"
target=$2
shift
else
printf "ERROR: '--target' requires a non-empty option argument." >&2
exit 1
fi
;;
--target=?*)
target=${1#*=}
;;
--target=)
printf 'ERROR: "--target" requires a non-empty option argument.\n' >&2
exit 1
;;
-l | --linker)
if [ -n "${2:-}" ]; then
linker="${2:-}"
;;
--target=?*) target=${1#*=} ;;
--target=) die "'--target' requires a non-empty option argument" ;;
-l | --linker)
[ -z "${2:-}" ] && die "'--linker' requires a non-empty option argument"
linker=$2
shift
else
printf "ERROR: '--linker' requires a non-empty option argument." >&2
exit 1
fi
;;
--linker=?*)
linker=${1#*=}
;;
--linker=)
printf 'ERROR: "--linker" requires a non-empty option argument.\n' >&2
exit 1
;;
-o | --out)
if [ -n "${2:-}" ]; then
out="${2:-}"
;;
--linker=?*) linker=${1#*=} ;;
--linker=) die "'--linker' requires a non-empty option argument" ;;
-o | --out)
[ -z "${2:-}" ] && die "'--out' requires a non-empty option argument"
out=$2
shift
else
printf "ERROR: '--out' requires a non-empty option argument." >&2
exit 1
fi
;;
--out=?*)
out=${1#*=}
;;
--out=)
printf 'ERROR: "--out" requires a non-empty option argument.\n' >&2
exit 1
;;
--)
;;
--out=?*) out=${1#*=} ;;
--out=) die "'--out' requires a non-empty option argument" ;;
--)
shift
break
;;
-?*) die "Unknown option $1" ;;
*) break ;;
esac
shift
break
;;
-?*)
printf "ERROR: Unknown option %s\n" "$1" >&2
;;
*)
break
;;
esac
shift
done
done
if [ -n "$linker" ] && [ "$target" = "hbvm_ableos" ]; then
printf "ERROR: Linker cannot be set with target 'hbvm_ableos'"
exit 1
fi
if [ $run = 1 ] && [ "$target" = "hbvm_ableos" ]; then
printf "ERROR: ./build does not support running the executable with target 'hbvm_ableos'"
exit 1
fi
if [ -n "$linker" ] && [ "$target" = "hbvm_ableos" ]; then
die "Linker cannot be set with target 'hbvm_ableos'"
fi
if [ "$run" = 1 ] && [ "$target" = "hbvm_ableos" ]; then
die "./build does not support running the executable with target 'hbvm_ableos'"
fi
}
check_hbc
if [ "$target" = "c_native" ]; then
build_c_native() {
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
for cmd in "zig cc" clang gcc; do
if command -v "${cmd%% *}" >/dev/null 2>&1; then
linker=$cmd
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
[ -z "$linker" ] && die "Could not find zig, clang, or gcc. Please install any, or add to PATH."
fi
make_build_dir
check_out_dir
set_target
if check_changes || [ ! -e "$out" ]; then
hbc "$SCRIPT_DIR/src/main.hb" --backend-flags=opt_level=speed >"$BUILD_PATH/out.o" ||
{
rm -f "$BUILD_PATH/out.o"
rm -f "$CHECKSUM_FILE"
die "Compilation failed"
}
if hbc "$build_dir/src/main.hb" --backend-flags=opt_level=speed >"$build_dir/target/build/lily.o"; then
$linker "$build_dir/target/build/lily.o" -o "$out"
strip -s "$out"
else
echo "compilation failed :(" >&2
exit 1
$linker "$BUILD_PATH/out.o" -o "$out"
strip -s "$out" || die "Stripping failed"
fi
if [ $run = 1 ] && [ ! "$target" = "hbvm_ableos" ]; then
"$build_dir"/target/lily
if [ "$run" = 1 ]; then exec "$out"; fi
}
build_hbvm() {
[ "$out" = "$DEFAULT_OUT" ] && out="$SCRIPT_DIR/target/lily.hbf"
if check_changes || [ ! -e "$out" ]; then
hbc "$SCRIPT_DIR/src/main.hb" --target=unknown-virt-unknown --backend-flags=opt_level=speed >"$out" ||
{
rm -f "$CHECKSUM_FILE"
die "Compilation failed"
}
fi
elif [ "$target" = "hbvm_ableos" ]; then
make_build_dir
check_out_dir
set_target
if [ "$out" = "$build_dir/target/lily" ]; then out="$build_dir/target/lily.hbf"; fi
if ! hbc "$build_dir/src/main.hb" --target=unknown-virt-unknown >"$out"; then
echo "compilation failed :(" >&2
exit 1
fi
fi
}
main() {
parse_args "$@"
mkdir -p "$BUILD_PATH" || die "Failed to create build directory"
[ -w "$BUILD_PATH" ] || die "$BUILD_PATH is not writable"
[ -w "$(dirname "$out")" ] || die "Out directory does not exist or is not writeable"
echo "target := @use(\"./$target.hb\")" >"$TARGET_FILE"
check_hbc
case $target in
c_native) build_c_native ;;
hbvm_ableos) build_hbvm ;;
*) die "Invalid target: $target" ;;
esac
}
main "$@"

BIN
src/assets/zeroed Normal file

Binary file not shown.

View file

@ -1,4 +1,4 @@
.{collections: .{Vec}, target, target_c_native, target_hbvm_ableos, null_pointer} := @use("lib.hb")
.{collections: .{SparseVec}, target, target_c_native, target_hbvm_ableos, Type} := @use("lib.hb")
Allocation := struct {
ptr: ^void,
@ -7,12 +7,12 @@ Allocation := struct {
// ! THIS ALLOCATOR IS TEMPORARY
SimpleAllocator := struct {
allocations: Vec(Allocation, RawAllocator),
allocations: SparseVec(Allocation, RawAllocator),
raw: RawAllocator,
$new := fn(): Self {
raw := RawAllocator.new()
return .(Vec(Allocation, RawAllocator).new(&raw), raw)
return .(SparseVec(Allocation, RawAllocator).new(&raw), raw)
}
deinit := fn(self: ^Self): void {
loop if self.allocations.len == 0 break else {
@ -66,16 +66,18 @@ RawAllocator := struct {
old_ptr: ^void,
size: uint,
old_size: uint,
$new := fn(): Self return .(null_pointer(void), null_pointer(void), 0, 0)
$new := fn(): Self return .(Type(^void).uninit(), Type(^void).uninit(), 0, 0)
deinit := fn(self: ^Self): void {
if target == target_c_native {
target.free(self.ptr)
target.free(self.old_ptr)
// ! (compiler) bug: comparing `self.ptr != Type(^void).uninit()` rather than `self.size == 0`
// causes condition to never be true (even though it is)
if self.size != 0 target.free(self.ptr)
if self.old_size != 0 target.free(self.old_ptr)
} else if target == target_hbvm_ableos {
target.free(self.ptr, self.size)
target.free(self.old_ptr, self.old_size)
if self.size != 0 target.free(self.ptr, self.size)
if self.old_size != 0 target.free(self.old_ptr, self.old_size)
};
*self = Self.new()
// *self = Self.new()
}
alloc := fn(self: ^Self, $T: type, count: uint): ?^T {
ptr := target.malloc(count * @sizeof(T))

View file

@ -1,8 +1 @@
.{Vec} := @use("vec.hb")
// mostly placeholder error error type
Error := enum {
KeyNotFound,
OutOfRange,
Full,
}
.{SparseVec} := @use("vec.hb")

View file

@ -1,18 +1,19 @@
.{target, log, target_c_native, target_hbvm_ableos, result: .{Result}, null_pointer} := @use("../lib.hb");
.{Error} := @use("lib.hb")
.{target, target_c_native, target_hbvm_ableos, memmove, Type} := @use("../lib.hb");
Vec := fn($T: type, $A: type): type return struct {
// self.slice.len tracks non-null elements
SparseVec := fn($T: type, $A: type): type return struct {
// slice.len tracks null and non-null elements
slice: []?T,
allocator: ^A,
// len tracks non-null elements
len: uint,
// capacity tracks maximum slice.len before reallocating
capacity: uint,
$new := fn(allocator: ^A): Self return .{slice: null_pointer(?T)[0..0], allocator, capacity: 0, len: 0}
$new := fn(allocator: ^A): Self return .{slice: Type([]?T).uninit(), allocator, capacity: 0, len: 0}
deinit := fn(self: ^Self): void {
// currently does not handle deinit of T if T allocates memory
if self.capacity != 0 self.allocator.free(?T, self.slice.ptr)
self.slice = null_pointer(?T)[0..0]
self.slice = Type([]?T).uninit()
self.capacity = 0
}
push := fn(self: ^Self, value: T): void {
@ -26,8 +27,11 @@ Vec := fn($T: type, $A: type): type return struct {
// ! (c_native) (compiler) bug: null check broken, so unwrapping (unsafe!)
new_alloc := @unwrap(self.allocator.alloc(?T, self.capacity))
if self.len > 0 {
memmove(new_alloc, self.slice.ptr, self.slice.len * @sizeof(?T))
}
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 = new_alloc
@ -55,6 +59,7 @@ Vec := fn($T: type, $A: type): type return struct {
a := self.slice[n]
if a != null {
self.slice[n] = null
self.slice.len -= 1
self.len -= 1
return a
}
@ -69,12 +74,12 @@ Vec := fn($T: type, $A: type): type return struct {
a := self.slice[m]
if a != null {
// ! (compiler) bug: This print causes compiler panic
printf("%d\n\0".ptr, a)
if !@target("*-virt-unknown") printf("%d\n\0".ptr, a)
if j == n {
self.slice[m] = null
self.len -= 1
// ! (compiler) bug: This print happens but we never see the "zub zub {a}" in main.hb?????
printf("here: %d\n\0".ptr, a)
// ! (compiler) bug: This print happens but we never see the "zub zub" in main.hb
if !@target("*-virt-unknown") printf("here: %d\n\0".ptr, a)
return a
}
j += 1

71
src/lib/fmt.hb Normal file
View file

@ -0,0 +1,71 @@
.{target, Type, TypeOf, string, memcpy} := @use("lib.hb")
// ! (c_native) (compiler) bug: caused by: `lily.log.print(100)`
fmt_int := fn(v: @Any(), str: []u8, radix: @TypeOf(v)): uint {
is_negative := TypeOf(v).is_signed_int() & v < 0
prefix_len := 0
if is_negative {
v = -v
str[0] = '-'
prefix_len += 1
}
if radix == 16 {
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0x".ptr)
prefix_len += 2
} else if radix == 2 {
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0b".ptr)
prefix_len += 2
} else if radix == 8 {
*@as(^[2]u8, @bitcast(str.ptr + prefix_len)) = *@bitcast("0o".ptr)
prefix_len += 2
}
if v == 0 {
str[prefix_len] = '0'
return prefix_len + 1
}
i := prefix_len
loop if v <= 0 break else {
remainder := v % radix
v /= radix
if remainder > 9 {
str[i] = @intcast(remainder - 10 + 'A')
} else {
str[i] = @intcast(remainder + '0')
}
i += 1
}
string.reverse(str[prefix_len..i])
return i
}
fmt_bool := fn(v: bool, str: []u8): uint {
if v {
memcpy(str.ptr, "true".ptr, 4)
return 4
} else {
memcpy(str.ptr, "false".ptr, 5)
return 5
}
}
format := fn(buf: []u8, v: @Any()): uint {
T := TypeOf(v)
match T.kind() {
.Pointer => return fmt_int(@as(uint, @bitcast(v)), buf, 16),
.Builtin => {
if T.is_int() return fmt_int(v, buf, 10)
if T.is_bool() return fmt_bool(v, buf)
},
_ => @error("format(", T, ") is not supported."),
}
return 0
}
format_with_str := fn(str: []u8, buf: []u8, v: @Any()): uint {
memcpy(buf.ptr, str.ptr, str.len)
return str.len
}

View file

@ -4,16 +4,18 @@ Version := struct {
patch: uint,
}
$VERSION := Version(0, 0, 2)
$VERSION := Version(0, 0, 3)
collections := @use("collections/lib.hb")
result := @use("result.hb")
string := @use("string.hb")
alloc := @use("alloc.hb")
rand := @use("rand.hb")
log := @use("log.hb")
fmt := @use("fmt.hb")
target_c_native := @use("target/c_native.hb")
target_hbvm_ableos := @use("target/hbvm_ableos.hb")
// target := target_hbvm_ableos
target_c_native := @use("target/c_native.hb")
target := @use("target/target.hb").target
$exit := fn(code: int): void {
@ -21,6 +23,39 @@ $exit := fn(code: int): void {
die
}
$panic := fn(message: ?[]u8): void {
if message != null log.error(message) else log.error("The program called panic.")
exit(1)
}
$memcpy := fn(dest: @Any(), src: @Any(), size: uint): void {
return target.memcpy(@bitcast(dest), @bitcast(src), size)
}
$memmove := fn(dest: @Any(), src: @Any(), size: uint): void {
return target.memmove(@bitcast(dest), @bitcast(src), size)
}
$memset := fn(dest: @Any(), src: u8, size: uint): void {
return target.memset(@bitcast(dest), src, size)
}
RawKind := enum {
Builtin,
Struct,
Tuple,
Enum,
Union,
Pointer,
Slice,
Optional,
Function,
Template,
Global,
Constant,
Module,
}
Kind := enum {
Builtin,
Struct,
@ -29,17 +64,31 @@ Kind := enum {
Union,
Pointer,
Slice,
Opt,
Array,
Optional,
Function,
Template,
Global,
Const,
Constant,
Module,
}
$null_pointer := fn($T: type): ^T return @bitcast(0)
TypeOf := fn(T: @Any()): type return Type(@TypeOf(T))
Type := fn($T: type): type return struct {
// ! no way of representing arbitrary size integers yet
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
}
Child := fn(): type {
return Type(@ChildOf(T))
}
This := fn(): type {
return T
}
$is_bool := fn(): bool {
return T == bool
}
$is_unsigned_int := fn(): bool {
return T == uint | T == u8 | T == u16 | T == u32
}
@ -47,21 +96,49 @@ Type := fn($T: type): type return struct {
return T == int | T == i8 | T == i16 | T == i32
}
$is_int := fn(): bool {
return Self.unsigned_int(T) | Self.signed_int(T)
return Self.is_unsigned_int() | Self.is_signed_int()
}
$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
$len := fn(): uint {
return @lenof(T)
}
$bits := fn(): Self.usize() {
$align := fn(): uint {
return @alignof(T)
}
$size := fn(): uint {
return @sizeof(T)
}
$bits := fn(): uint {
return @sizeof(T) << 3
}
$bitmask := fn(): Self.usize() {
$bitmask := fn(): Self.Usize() {
return -1
}
$kind := fn(): Kind {
$raw_kind := fn(): RawKind {
return @bitcast(@kindof(T))
}
$kind := fn(): Kind {
if Self.raw_kind() == .Slice {
if []@ChildOf(T) == T return .Slice else return .Array
} else if @kindof(T) > @bitcast(Kind.Slice) {
return @bitcast(@kindof(T) + 1)
} else return @bitcast(Self.raw_kind())
}
/// ! There are no guarantees that this value is zeroed for builtins, enums, unions, structs, arrays, or tuples.
$uninit := fn(): T {
match Self.kind() {
.Pointer => return @bitcast(0),
.Slice => return @bitcast(@as(^void, @bitcast(0))[0..0]),
.Array => return idk,
.Builtin => return idk,
.Struct => return idk,
.Tuple => return idk,
.Union => return idk,
.Enum => return idk,
.Optional => return null,
_ => @error("Type(", T, ").uninit() does not make sense."),
}
}
}

View file

@ -1,4 +1,4 @@
.{target, target_c_native, target_hbvm_ableos} := @use("lib.hb")
.{target, target_c_native, target_hbvm_ableos, fmt} := @use("lib.hb")
LogLevel := enum {
Error,
@ -13,23 +13,40 @@ log := fn(level: LogLevel, str: []u8): void {
return @eca(3, 1, target.LogMsg.(level, str.ptr, str.len), @sizeof(target.LogMsg))
} else if target == target_c_native {
match level {
.Error => target.printf_str("\{1b}[31mERROR\{1b}[0m: %s\n\0".ptr, str.ptr),
.Warn => target.printf_str("\{1b}[33mWARN\{1b}[0m: %s\n\0".ptr, str.ptr),
.Info => target.printf_str("\{1b}[32mINFO\{1b}[0m: %s\n\0".ptr, str.ptr),
.Debug => target.printf_str("\{1b}[34mDEBUG\{1b}[0m: %s\n\0".ptr, str.ptr),
.Trace => target.printf_str("\{1b}[35mTRACE\{1b}[0m: %s\n\0".ptr, str.ptr),
.Error => target.printf_str("\{1b}[31mERROR\{1b}[0m: %.*s\n\0".ptr, str.len, str.ptr),
.Warn => target.printf_str("\{1b}[33mWARN\{1b}[0m: %.*s\n\0".ptr, str.len, str.ptr),
.Info => target.printf_str("\{1b}[32mINFO\{1b}[0m: %.*s\n\0".ptr, str.len, str.ptr),
.Debug => target.printf_str("\{1b}[34mDEBUG\{1b}[0m: %.*s\n\0".ptr, str.len, str.ptr),
.Trace => target.printf_str("\{1b}[35mTRACE\{1b}[0m: %.*s\n\0".ptr, str.len, str.ptr),
}
}
}
$print := fn(str: []u8): void {
if target == target_c_native {
target.puts(str.ptr)
} else if target == target_hbvm_ableos {
info(str)
// it's good enough i guess. dont write more than 4096 chars or you will explode.
print_buffer := @embed("../assets/zeroed")
print := fn(any: @Any()): void {
if @TypeOf(any) == []u8 {
if target == target_c_native {
target.printf_str("%.*s\n\0".ptr, any.len, any.ptr)
} else if target == target_hbvm_ableos {
info(any)
}
} else {
// limits len to size of buffer - 1
len := fmt.format(print_buffer[0..@sizeof(@TypeOf(print_buffer)) - 1], any)
print_buffer[len] = 0
// ! (compiler) bug: not inlining here causes compiler panic
@inline(print, print_buffer[0..len])
}
}
printf := fn(str: []u8, any: @Any()): void {
len := fmt.format_with_str(str, print_buffer[0..@sizeof(@TypeOf(print_buffer)) - 1], any)
print_buffer[len] = 0
print(print_buffer[0..len])
}
$error := fn(message: []u8): void return log(LogLevel.Error, message)
$warn := fn(message: []u8): void return log(LogLevel.Warn, message)
$info := fn(message: []u8): void return log(LogLevel.Info, message)

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

@ -0,0 +1,64 @@
.{target, TypeOf, Type} := @use("lib.hb")
// ! NON CRYPTOGRAPHIC, TEMPORARY
SimpleRandom := struct {
seed: uint,
$new := fn(): Self return Self.(0)
$deinit := fn(self: ^Self): void {
self.seed = 0
}
$any := fn(self: ^Self, $A: type): A {
T := Type(A)
match T.kind() {
.Slice => @error("Use SimpleRandom.fill or SimpleRandom.fill_values instead."),
.Array => @error("Use SimpleRandom.fill or SimpleRandom.fill_values instead."),
_ => {
},
}
if T.is_bool() {
a: A = idk
target.getrandom(@bitcast(&a), 1)
return @bitcast(a & @as(u8, 1))
}
a: A = idk
target.getrandom(@bitcast(&a), T.size())
return a
}
/// Accepts any type for min and max (as long as it is the same for both).
$range := fn(self: ^Self, min: @Any(), max: @TypeOf(min)): @TypeOf(min) {
return self.any(@TypeOf(min)) % (max - min) + min
}
/// Fills an array or slice with random bytes
$fill := fn(self: ^Self, buf: @Any()): void {
T := TypeOf(buf)
match T.kind() {
.Slice => target.getrandom(@bitcast(buf.ptr), buf.len * T.Child().size()),
.Array => target.getrandom(@bitcast(&buf), T.size()),
_ => @error("Can only fill bytes of Slice or Array."),
}
}
/// Fills an array or slice with random values
// ! (compiler) bug: `buf[i]` causing compiler panic here
fill_values := fn(self: ^Self, buf: @Any()): void {
T := TypeOf(buf)
match T.kind() {
.Slice => {
len := buf.len * T.Child().size()
i := 0
loop if i == len break else {
buf[i] = self.any(T.Child().This())
i += 1
}
},
.Array => {
len := T.size()
i := 0
$loop if i == len break else {
buf[i] = self.any(T.Child().This())
i += 1
}
},
_ => @error("Can only fill values of Slice or Array."),
}
}
}

View file

@ -1,4 +1,4 @@
.{exit} := @use("lib.hb")
.{panic} := @use("lib.hb")
ResultInner := fn($T: type, $E: type): type return union {ok: T, err: E}
@ -8,9 +8,9 @@ Result := fn($T: type, $E: type): type return struct {
$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 := fn(self: Self): T return self.expect("Unwrap on error variant.")
$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)
expect := fn(self: Self, msg: []u8): T if self.is_ok return self.inner.ok else panic(msg)
}

170
src/lib/string.hb Normal file
View file

@ -0,0 +1,170 @@
reverse := fn(str: []u8): void {
if str.len == 0 return;
j := str.len - 1
i := 0
temp := @as(u8, 0)
loop if i < j {
temp = str[i]
str[i] = str[j]
str[j] = temp
i += 1
j -= 1
} else return
}
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
}
clear := fn(str: []u8): void {
i := 0
loop if i == str.len break else {
str[i] = 0
i += 1
}
}
split_once := fn(haystack: []u8, needle: @Any()): ?struct {left: []u8, right: []u8} {
T := @TypeOf(needle)
i := 0
if T == []u8 {
if needle.len == 0 return null
loop {
if i + needle.len > haystack.len return null
if haystack[i] == needle[0] {
matches := true
n := 1
loop {
if n == needle.len break
if haystack[i + n] != needle[n] {
matches = false
break
}
n += 1
}
if matches return .(haystack[0..i], haystack[i + needle.len..])
}
i += 1
}
} else if T == u8 {
loop {
if haystack[i] == needle {
return .(haystack[0..i], haystack[i + 1..])
} else if i == haystack.len return null
i += 1
}
} else {
@error("Type of needle must be []u8 or u8.")
}
}
split := fn(iter: []u8, needle: @Any()): struct {
str: []u8,
needle: @TypeOf(needle),
done: bool,
next := fn(self: ^Self): ?[]u8 {
if self.done return null;
splits := split_once(self.str, self.needle)
if splits != null {
self.str = splits.right
return splits.left
} else {
self.done = true
return self.str
}
}
} {
T := @TypeOf(needle)
if T != []u8 & T != u8 {
@error("Type of needle must be []u8 or u8.")
}
return .(iter, needle, false)
}
chars := fn(iter: []u8): struct {
str: []u8,
next := fn(self: ^Self): ?u8 {
if self.str.len == 0 return null
self.str = self.str[1..]
return self.str[0]
}
} {
return .(iter)
}
count := fn(haystack: []u8, needle: @Any()): uint {
T := @TypeOf(needle)
i := 0
c := 0
if T == []u8 {
if needle.len == 0 return null
loop {
if i + needle.len > haystack.len return c
if haystack[i] == needle[0] {
matches := true
n := 1
loop {
if n == needle.len break
if haystack[i + n] != needle[n] {
matches = false
break
}
n += 1
}
if matches c += 1
}
i += 1
}
} else if T == u8 {
loop {
if haystack[i] == needle c += 1 else if i == haystack.len return c
i += 1
}
} else {
@error("Type of needle must be []u8 or u8.")
}
}
left_trim := fn(str: []u8, sub: []u8): []u8 {
i := 0
if str[0] == sub[0] {
loop if i == sub.len {
return str[i..str.len]
} else if str[i] != sub[i] | i == str.len {
break
} else {
i += 1
}
}
return str
}
right_trim := fn(str: []u8, sub: []u8): []u8 {
i := 0
if str[str.len - 1] == sub[sub.len - 1] {
loop if i == sub.len {
return str[0..str.len - i]
} else if str[str.len - i - 1] != sub[sub.len - i - 1] | i == str.len {
break
} else {
i += 1
}
}
return str
}
trim := fn(str: []u8, sub: []u8): []u8 {
return right_trim(left_trim(str, sub), sub)
}

View file

@ -4,6 +4,5 @@ memmove := fn(dest: ^void, src: ^void, size: uint): void @import()
memcpy := fn(dest: ^void, src: ^void, size: uint): void @import()
memset := fn(dest: ^void, src: u8, size: uint): void @import()
exit := fn(code: int): void @import()
puts := fn(str: ^u8): void @import()
printf := fn(str: ^u8): void @import()
printf_str := fn(str0: ^u8, str1: ^u8): void @import("printf")
printf_str := fn(str0: ^u8, strlen: uint, str1: ^u8): void @import("printf")
getrandom := fn(dest: ^void, size: uint): void @import()

View file

@ -1,8 +1,6 @@
.{log: .{LogLevel}, null_pointer} := @use("../lib.hb")
.{log: .{LogLevel}} := @use("../lib.hb")
$PAGE_SIZE := 4096
$MAX_ALLOC := 0xFF
$MAX_FREE := 0xFF
LogMsg := packed struct {level: LogLevel, string: ^u8, strlen: uint}
@ -20,16 +18,12 @@ $free_pages := fn(ptr: ^void, count: uint): void {
return @eca(3, 2, &FreePageMsg.(1, count, ptr), @sizeof(FreePageMsg))
}
malloc := fn(size: uint): ?^void {
if size == 0 return null
pages := calculate_pages(size)
return request_pages(pages)
$malloc := fn(size: uint): ?^void {
return request_pages(calculate_pages(size))
}
free := fn(ptr: ^void, size: uint): void {
if size == 0 | ptr == null_pointer(void) return;
pages := calculate_pages(size)
free_pages(ptr, pages)
$free := fn(ptr: ^void, size: uint): void {
free_pages(ptr, calculate_pages(size))
}
CopyMsg := packed struct {a: u8, count: uint, src: ^void, dest: ^void}
@ -47,5 +41,7 @@ memmove := fn(dest: ^void, src: ^void, size: uint): void {
memset(src, 0, size)
}
$getrandom := fn(dest: ^void, size: uint): void return @eca(3, 4, dest, size)
$exit := fn(code: int): void {
}

View file

@ -1,33 +1,49 @@
lily := @use("lib/lib.hb");
Allocator := lily.alloc.SimpleAllocator
Vec := lily.collections.Vec
Vec := lily.collections.SparseVec
Random := lily.rand.SimpleRandom
Result := lily.result.Result
printf := fn(str: ^u8, u: uint): void @import()
// ! (runtime) (target_hbvm_ableos?) bug: leaking memory (1 page in this function)
// ! (runtime) bug: leaking memory (waiting on compiler bugfixes)
// ! can't run on target_hbvm_ableos due to unresolved compiler bugs
main := fn(argc: uint, argv: []^void): uint {
allocator := Allocator.new()
defer allocator.deinit()
vec := Vec(uint, Allocator).new(&allocator)
defer vec.deinit()
rand := Random.new()
defer rand.deinit()
i := 0
// ! (compiler) bug: using `if i < 5 {}` rather than `if i >= 5 break else {}` causes
// the program to halt after the loop
// ! (compiler) bug: checking against `vec.len` rather than `i` causes the loop to go forever
// despite the fact that vec.len is incremented in vec.push
loop if i == 5 break else {
defer i += 1
vec.push(i)
lily.log.info("pushed to vec\0")
vec.push(rand.any(uint))
lily.log.info("pushed to vec")
}
z := vec.remove(1)
z = vec.remove(3)
if z != null {
// should print `zub zub 4`
// fails to print anything
// search "(compiler) bug:" in the whole codebase to find bugs
// additional bugs tbd
printf("zub zub %d\n\0".ptr, z)
}
// ! (c_native) this causes a compiler bug in lily.fmt.fmt_int
// lily.log.print(100)
lily.log.print(true)
// ! (c_native) (compiler) bug: compiler panic from vec.remove()
// z := vec.remove(1)
// lily.log.info("removed from vec")
// z = vec.remove(3)
// lily.log.info("removed from vec")
// if z != null {
// ! (compiler) bug: this never happens (even though it should)
// lily.log.info("zub zub")
// }
lily.log.info("the following should panic:")
a := Result(bool, bool).err(false)
_ = a.unwrap()
return 0
}