diff --git a/Cargo.lock b/Cargo.lock
index fc69eb1c..d08398f6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -213,35 +213,25 @@ dependencies = [
 [[package]]
 name = "hbbytecode"
 version = "0.1.0"
-
-[[package]]
-name = "hbbytecode"
-version = "0.1.0"
-source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#248bdf003aa991f4bad18ddcb084532d1bcb78d5"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e769fa8dbad4d69ccab9e13112e20be4b8bd91bf"
 
 [[package]]
 name = "hblang"
 version = "0.1.0"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e769fa8dbad4d69ccab9e13112e20be4b8bd91bf"
 dependencies = [
  "hashbrown",
- "hbbytecode 0.1.0",
- "hbvm 0.1.0",
+ "hbbytecode",
+ "hbvm",
  "log",
 ]
 
 [[package]]
 name = "hbvm"
 version = "0.1.0"
+source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#e769fa8dbad4d69ccab9e13112e20be4b8bd91bf"
 dependencies = [
- "hbbytecode 0.1.0",
-]
-
-[[package]]
-name = "hbvm"
-version = "0.1.0"
-source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#248bdf003aa991f4bad18ddcb084532d1bcb78d5"
-dependencies = [
- "hbbytecode 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
+ "hbbytecode",
 ]
 
 [[package]]
@@ -401,7 +391,7 @@ dependencies = [
  "crossbeam-queue",
  "derive_more",
  "hashbrown",
- "hbvm 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
+ "hbvm",
  "ktest_macro",
  "limine",
  "log",
diff --git a/repbuild/Cargo.toml b/repbuild/Cargo.toml
index 3d006a3d..2b7fc845 100644
--- a/repbuild/Cargo.toml
+++ b/repbuild/Cargo.toml
@@ -14,8 +14,7 @@ fatfs = { version = "0.3", default-features = false, features = [
 	"alloc",
 ] }
 toml = "0.8"
-#hblang.git = "https://git.ablecorp.us/AbleOS/holey-bytes.git"
-hblang.path = "../../holey-bytes/lang/"
+hblang.git = "https://git.ablecorp.us/AbleOS/holey-bytes.git"
 log = "0.4"
 raw-cpuid = "11"
 ureq = { version = "2", default-features = false, features = ["tls"] }
diff --git a/sysdata/libraries/stn/src/formatters.hb b/sysdata/libraries/stn/src/formatters.hb
index e5a15bda..14ae4c02 100644
--- a/sysdata/libraries/stn/src/formatters.hb
+++ b/sysdata/libraries/stn/src/formatters.hb
@@ -1,55 +1,51 @@
-.{Kind, string, unsigned_int, signed_int, float, integer, memory, panic} := @use("stn")
+.{Kind, string, signed_int, float, integer, memory, panic} := @use("stn")
 
-format_int := fn($T: type, v: T, str: ^u8, radix: T): uint {
-	if integer(T) {
-		is_negative := signed_int(T) & v < 0
-		if is_negative v = -v
+fmt_int := fn(v: @Any(), str: ^u8, radix: @TypeOf(v)): uint {
+	is_negative := signed_int(@TypeOf(v)) & v < 0
+	if is_negative v = -v
 
-		prefix_len := 0
-		if radix == 16 {
-			*str = '0';
-			*(str + 1) = 'x'
-			prefix_len = 2
-		} else if radix == 2 {
-			*str = '0';
-			*(str + 1) = 'b'
-			prefix_len = 2
-		} else if radix == 8 {
-			*str = '0';
-			*(str + 1) = 'o'
-			prefix_len = 2
-		}
-
-		if v == 0 {
-			*(str + prefix_len) = '0'
-			return prefix_len + 1
-		}
-
-		i := 0
-		loop if v <= 0 break else {
-			remainder := v % radix
-			v /= radix
-			if remainder > 9 {
-				*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
-			} else {
-				*(str + prefix_len + i) = @intcast(remainder + '0')
-			}
-			i += 1
-		}
-
-		if is_negative {
-			*(str + prefix_len + i) = '-'
-			i += 1
-		}
-
-		@inline(string.reverse, str + prefix_len)
-		return prefix_len + i
-	} else {
-		panic("Type is not an integer\0")
+	prefix_len := 0
+	if radix == 16 {
+		*str = '0';
+		*(str + 1) = 'x'
+		prefix_len = 2
+	} else if radix == 2 {
+		*str = '0';
+		*(str + 1) = 'b'
+		prefix_len = 2
+	} else if radix == 8 {
+		*str = '0';
+		*(str + 1) = 'o'
+		prefix_len = 2
 	}
+
+	if v == 0 {
+		*(str + prefix_len) = '0'
+		return prefix_len + 1
+	}
+
+	i := 0
+	loop if v <= 0 break else {
+		remainder := v % radix
+		v /= radix
+		if remainder > 9 {
+			*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
+		} else {
+			*(str + prefix_len + i) = @intcast(remainder + '0')
+		}
+		i += 1
+	}
+
+	if is_negative {
+		*(str + prefix_len + i) = '-'
+		i += 1
+	}
+
+	@inline(string.reverse, str + prefix_len)
+	return prefix_len + i
 }
 
-format_bool := fn(v: bool, str: ^u8): uint {
+fmt_bool := fn(v: bool, str: ^u8): uint {
 	if v {
 		*@as(^[u8; 4], @bitcast(str)) = *@bitcast("true\0")
 		return 4
@@ -59,133 +55,139 @@ format_bool := fn(v: bool, str: ^u8): uint {
 	}
 }
 
-format_float := fn($T: type, v: T, str: ^u8, precision: uint, radix: int): uint {
-	if float(T) {
-		is_negative := v < 0
-		i := 0
+fmt_float := fn(v: @Any(), str: ^u8, precision: uint, radix: int): uint {
+	is_negative := v < 0
+	i := 0
 
-		prefix_len := 0
-		if radix == 16 {
-			*str = '0';
-			*(str + 1) = 'x'
-			prefix_len = 2
-		} else if radix == 2 {
-			*str = '0';
-			*(str + 1) = 'b'
-			prefix_len = 2
-		} else if radix == 8 {
-			*str = '0';
-			*(str + 1) = 'o'
-			prefix_len = 2
+	prefix_len := 0
+	if radix == 16 {
+		*str = '0';
+		*(str + 1) = 'x'
+		prefix_len = 2
+	} else if radix == 2 {
+		*str = '0';
+		*(str + 1) = 'b'
+		prefix_len = 2
+	} else if radix == 8 {
+		*str = '0';
+		*(str + 1) = 'o'
+		prefix_len = 2
+	}
+
+	if is_negative v = -v
+
+	integer_part := @fti(v)
+	fractional_part := v - @itf(integer_part)
+
+	loop if integer_part == 0 & i > 0 break else {
+		remainder := integer_part % radix
+		integer_part /= radix
+		if remainder > 9 {
+			*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
+		} else {
+			*(str + prefix_len + i) = @intcast(remainder + '0')
 		}
-
-		if is_negative v = -v
-
-		integer_part := @fti(v)
-		fractional_part := v - @itf(integer_part)
-
-		loop if integer_part == 0 & i > 0 break else {
-			remainder := integer_part % radix
-			integer_part /= radix
-			if remainder > 9 {
-				*(str + prefix_len + i) = @intcast(remainder - 10 + 'A')
-			} else {
-				*(str + prefix_len + i) = @intcast(remainder + '0')
-			}
-			i += 1
-		}
-
-		if is_negative {
-			*(str + prefix_len + i) = '-'
-			i += 1
-		}
-
-		@inline(string.reverse, str + prefix_len);
-		*(str + prefix_len + i) = '.'
 		i += 1
+	}
 
-		p := precision
-		tolerance := @as(T, 0.00000001)
-		loop if p <= 0 | fractional_part < tolerance break else {
-			fractional_part *= @itf(radix)
-			digit := @fti(fractional_part)
-			if digit > 9 {
-				*(str + prefix_len + i) = @intcast(digit - 10 + 'A')
-			} else {
-				*(str + prefix_len + i) = @intcast(digit + '0')
-			}
-			i += 1
-			fractional_part -= @itf(digit)
-			p -= 1
+	if is_negative {
+		*(str + prefix_len + i) = '-'
+		i += 1
+	}
+
+	@inline(string.reverse, str + prefix_len);
+	*(str + prefix_len + i) = '.'
+	i += 1
+
+	p := precision
+	tolerance := @as(@TypeOf(v), 0.00000001)
+	loop if p <= 0 | fractional_part < tolerance break else {
+		fractional_part *= @itf(radix)
+		digit := @fti(fractional_part)
+		if digit > 9 {
+			*(str + prefix_len + i) = @intcast(digit - 10 + 'A')
+		} else {
+			*(str + prefix_len + i) = @intcast(digit + '0')
 		}
+		i += 1
+		fractional_part -= @itf(digit)
+		p -= 1
+	}
 
-		return prefix_len + i
+	return prefix_len + i
+}
+
+fmt_container := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
+	T2 := @TypeOf(v)
+	i := 0;
+	*@as(^[u8; @len(@nameof(T2))], @bitcast(str)) = *@bitcast(@nameof(T2))
+	len := @len(@nameof(T2));
+	*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(".(\0")
+	len += 2
+	$loop {
+		v_sub := v[i]
+		len += @inline(fmt_inner, v_sub, str + len, opts)
+		i += 1
+		if i == @len(T2) break else {
+			*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(", \0")
+			len += 2
+		}
+	};
+	*@as(^[u8; 1], @bitcast(str + len)) = *@bitcast(")\0")
+	len += 1
+	return len
+}
+
+fmt_nullable := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
+	if v == null {
+		*@as(^[u8; 4], @bitcast(str)) = *@bitcast("null\0")
+		return 4
 	} else {
-		panic("Type is not a floating point\0")
+		return @inline(fmt_inner, @unwrap(v), str, opts)
 	}
 }
 
-format_inner := fn($T: type, v: T, str: ^u8, opts: FormatOptions): uint {
-	match @as(Kind, @bitcast(@kindof(T))) {
-		.Pointer => return @inline(format_int, uint, @bitcast(v), str, 16),
+fmt_inner := fn(v: @Any(), str: ^u8, opts: FormatOptions): uint {
+	T := @TypeOf(v)
+	match Kind.of(T) {
+		.Pointer => return @inline(fmt_int, @as(uint, @bitcast(v)), str, 16),
 		.Builtin => {
 			if integer(T) {
-				return @inline(format_int, T, v, str, @intcast(opts.radix))
+				return @inline(fmt_int, v, str, @intcast(opts.radix))
 			} else if T == bool {
-				return @inline(format_bool, v, str)
+				return @inline(fmt_bool, v, str)
 			} else if float(T) {
-				return @inline(format_float, T, v, str, opts.decimal_digits, @intcast(opts.radix))
+				return @inline(fmt_float, v, str, opts.decimal_digits, @intcast(opts.radix))
 			}
 		},
-		.Struct => {
-			// name := ;
-			i := 0;
-			*@as(^[u8; @len(@nameof(T))], @bitcast(str)) = *@bitcast(@nameof(T))
-			len := @len(@nameof(T));
-			*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(".(\0")
-			len += 2
-			$loop {
-				v_sub := v[i]
-				TSub := @TypeOf(v_sub)
-				len += @inline(format_inner, TSub, v_sub, str + len, opts)
-				i += 1
-				if i == @len(T) break else {
-					*@as(^[u8; 2], @bitcast(str + len)) = *@bitcast(", \0")
-					len += 2
-				}
-			};
-			*@as(^[u8; 1], @bitcast(str + len)) = *@bitcast(")\0")
-			len += 1
-			return len
-		},
-		_ => panic("unsupported format type\0"),
+		.Opt => return @inline(fmt_nullable, v, str, opts),
+		.Struct => return @inline(fmt_container, v, str, opts),
+		.Slice => return @inline(fmt_container, v, str, opts),
+		_ => @error("Type: \"\0", T, "\" is not supported.\0"),
 	}
 }
 
 /* TODO:
 * Custom formatters using struct methods (T._fmt(self, str): uint),
-* Format struct names "Name.(x, y, z)"
 * Format struct fields "Name.{a: x, b: y, c: z}"
 * Optionally tabulate
 * Add more FormatOption fields
 * Support scientific notation for floating point
-* Support format string (impossible right now)
-* Support nullables (impossible right now)
-* Support pointers
+* Support format string
 */
 
 FormatOptions := struct {
-	decimal_digits: uint = 1 << 32,
+	decimal_digits: uint = 2,
 	radix: uint = 10,
 }
 
 /* SAFETY:
  * Assumes the buffer is wide enough for the formatted text and a null char
 */
-format := fn($T: type, v: T, str: ^u8): ^u8 return @inline(format_args, T, v, str, .{})
+format := fn(v: @Any(), str: ^u8): ^u8 return @inline(format_args, v, str, .{})
 
-format_args := fn($T: type, v: T, str: ^u8, opts: FormatOptions): ^u8 {
+format_args := fn(v: @Any(), str: ^u8, opts: FormatOptions): ^u8 {
 	@inline(string.clear, str)
-	_ = @inline(format_inner, T, v, str, opts)
+	_ = @inline(fmt_inner, v, str, opts)
 	return str
 }
\ No newline at end of file
diff --git a/sysdata/libraries/stn/src/lib.hb b/sysdata/libraries/stn/src/lib.hb
index 14aa9f5c..259d73e8 100644
--- a/sysdata/libraries/stn/src/lib.hb
+++ b/sysdata/libraries/stn/src/lib.hb
@@ -24,7 +24,23 @@ panic := fn(message: ?^u8): never {
 	die
 }
 
-Kind := enum {Builtin, Struct, Enum, Union, Pointer, Slice, Opt, Function, Template, Global, Const, Module}
+Kind := enum {
+	Builtin,
+	Struct,
+	Enum,
+	Union,
+	Pointer,
+	Slice,
+	Opt,
+	Function,
+	Template,
+	Global,
+	Const,
+	Module,
+	$of := fn($T: type): Self {
+		return @bitcast(@kindof(T))
+	}
+}
 
 $unsigned_int := fn($T: type): bool {
 	return T == uint | T == u8 | T == u16 | T == u32
diff --git a/sysdata/programs/test/src/tests/stn/formatters.hb b/sysdata/programs/test/src/tests/stn/formatters.hb
index 538f9c86..a955562c 100644
--- a/sysdata/programs/test/src/tests/stn/formatters.hb
+++ b/sysdata/programs/test/src/tests/stn/formatters.hb
@@ -12,16 +12,25 @@ SubThingy := struct {
 	b: bool,
 }
 
+a := enum {
+	Bababa,
+}
+
+opaque := fn(): ?u32 {
+	return null
+}
+
 test := fn(): uint {
 	buffer := memory.request_page(1)
-	log.info(format(Thingy, .(-100, -100, .(-math.PI, true)), buffer))
-	log.info(format(SubThingy, .(-math.E, false), buffer))
-	log.info(format_args(Color, .{r: 255, g: 254, b: 253, a: 252}, buffer, .{
+	log.info(format(Thingy.(-100, -100, .(-math.PI, true)), buffer))
+	log.info(format(SubThingy.(-math.E, false), buffer))
+	log.info(format_args(Color.{r: 255, g: 254, b: 253, a: 252}, buffer, .{
 		radix: 16,
 	}))
-	log.info(format_args(f64, math.LN_2, buffer, .{radix: 16}))
-	log.info(format([u8; 3], .(1, 2, 3), buffer))
-	log.info(format(^SubThingy, &.(0.0, true), buffer))
+	log.info(format_args(math.LN_2, buffer, .{radix: 16, decimal_digits: 1 << 32}))
+	log.info(format([u8].(1, 2, 3), buffer))
+	log.info(format(&SubThingy.(0.0, true), buffer))
+	log.info(format(opaque(), buffer))
 
 	return 0
 }
\ No newline at end of file