parse const decls

This commit is contained in:
2026-07-25 00:26:47 +02:00
parent c7f527e3c3
commit 90c7195d4b
28 changed files with 1452 additions and 1134 deletions
+51 -51
View File
@@ -1,74 +1,74 @@
import "@std/mem"
ArrayList proc($T type) type {
return struct {
items []mut T
capacity usize
allocator mem.Allocator
}
return struct {
items []mut T
capacity usize
allocator mem.Allocator
}
}
init proc($T type, allocator mem.Allocator) ArrayList(T) {
return ArrayList(T) {
items = mem.empty(T),
capacity = 0,
allocator = allocator,
}
return ArrayList(T) {
items = mem.empty(T),
capacity = 0,
allocator = allocator,
}
}
deinit proc($T type, list @mut ArrayList(T)) void {
allocation []mut T :: list.items.ptr[..list.capacity]
mem.free(list.allocator, allocation)
list.items = mem.empty(T)
list.capacity = 0
allocation []mut T :: list.items.ptr[..list.capacity]
mem.free(list.allocator, allocation)
list.items = mem.empty(T)
list.capacity = 0
}
reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError {
if min_capacity <= list.capacity {
return
}
hide reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError {
if min_capacity <= list.capacity {
return
}
new_capacity usize = 8
if list.capacity >= 8 {
half usize :: divtrunc!(list.capacity, 2)
if list.capacity > maxval!(usize) - half {
new_capacity = min_capacity
} else {
new_capacity = list.capacity + half
}
}
if new_capacity < min_capacity {
new_capacity = min_capacity
}
new_capacity usize = 8
if list.capacity >= 8 {
half usize :: divtrunc!(list.capacity, 2)
if list.capacity > maxval!(usize) - half {
new_capacity = min_capacity
} else {
new_capacity = list.capacity + half
}
}
if new_capacity < min_capacity {
new_capacity = min_capacity
}
length usize :: list.items.len
allocation []mut T :: list.items.ptr[..list.capacity]
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
return .out_of_memory
}
list.items = grown.ptr[..length]
list.capacity = new_capacity
return
length usize :: list.items.len
allocation []mut T :: list.items.ptr[..list.capacity]
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
return .out_of_memory
}
list.items = grown.ptr[..length]
list.capacity = new_capacity
return
}
append proc($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
length usize :: list.items.len
if length == maxval!(usize) {
return .out_of_memory
}
try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1]
list.items[length] = value
return
length usize :: list.items.len
if length == maxval!(usize) {
return .out_of_memory
}
try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1]
list.items[length] = value
return
}
pop proc($T type, list @mut ArrayList(T)) ?T {
if (list.items.len == 0) return null
value :: list.items[list.items.len - 1]
list.items = list.items.ptr[..list.items.len - 1]
return value
if (list.items.len == 0) return null
value :: list.items[list.items.len - 1]
list.items = list.items.ptr[..list.items.len - 1]
return value
}
clear proc($T type, list @mut ArrayList(T)) void {
list.items = list.items.ptr[..0]
list.items = list.items.ptr[..0]
}
+15 -15
View File
@@ -2,31 +2,31 @@ import "@std/mem"
import "@std/testing"
handles_append test {
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
try append(&list, 42)
try append(&list, 42)
try testing.expect_equal(1, list.items.len)
try testing.expect_equal(42, list.items[0])
try testing.expect_equal(1, list.items.len)
try testing.expect_equal(42, list.items[0])
}
handles_clear test {
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
try append(&list, 42)
clear(&list)
try append(&list, 42)
clear(&list)
try testing.expect_equal(0, list.items.len)
try testing.expect_equal(0, list.items.len)
}
handles_reserve test {
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
list ArrayList(i32) = init(mem.c_allocator)
defer deinit(&list)
try reserve(&list, 10)
try reserve(&list, 10)
try testing.expect_equal(10, list.capacity)
try testing.expect_equal(0, list.items.len)
try testing.expect_equal(10, list.capacity)
try testing.expect_equal(0, list.items.len)
}
+7 -7
View File
@@ -7,11 +7,11 @@
# Declarative and literal-only: one executable per build. List fields take an
# address-of an array literal (`&["raylib"]`) and default to empty.
BuildConfig :: struct {
name []u8 # output executable name under root/build
source []u8 # program package directory, relative to build.bro
libraries [][]u8 = &[] # library names to link (-l)
lib_paths [][]u8 = &[] # library search directories (-L)
includes [][]u8 = &[] # C include directories (-I)
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
name []u8 # output executable name under root/build
source []u8 # program package directory, relative to build.bro
libraries [][]u8 = &[] # library names to link (-l)
lib_paths [][]u8 = &[] # library search directories (-L)
includes [][]u8 = &[] # C include directories (-I)
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
}
+24 -20
View File
@@ -1,28 +1,32 @@
import "@ffi/c"
import "@std/io"
assert proc(ok bool) void {
if (!ok) unreachable
}
print proc($format []u8, $Args type, args Args) void {
writer io.Writer :: io.Writer{
context = null,
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
write = write,
}
io.print(writer, format, Args, args) catch |_| {}
writer io.Writer :: io.Writer{
context = null,
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
write = write,
}
io.print(writer, format, Args, args) catch |_| {}
}
hide write proc(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
if c.__error()?^ != c.EINTR {
return .write_failed
}
}
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
if c.__error()?^ != c.EINTR {
return .write_failed
}
}
}
+45
View File
@@ -0,0 +1,45 @@
import "@std/meta"
EnumMap proc($E, $V type) type {
match typeinfo!(E) {
.enum |info|: return struct {
present [info.fields.len]mut bool # fixme: replace with bitset
values [info.fields.len]mut V
}
else: compile_error!("EnumMap key must be an enum")
}
}
init proc(
$E, $V type,
values meta.EnumFieldStruct(E, ?V, some!(null)),
) EnumMap(E, V) {
map EnumMap(E, V) = undefined
match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| {
map.present[i] = false
if field!(values, field.name) |value| {
map.present[i] = true
map.values[i] = value
}
}
else: compile_error!("EnumMap key must be an enum")
}
return map
}
get proc($E, $V type, map @EnumMap(E, V), key E) ?V {
# fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup
match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| {
if key == field!(E, field.name) {
if (map.present[i]) return map.values[i]
return null
}
}
else: compile_error!("EnumMap key must be an enum")
}
}
+25
View File
@@ -0,0 +1,25 @@
import "@std/mem"
import "@std/testing"
TestEnum :: enum(u8) {
ident = 3
int = 8
eof = 21
}
handles_sparse_enum_get test {
names EnumMap(TestEnum, []u8) = init({
ident = "identifier",
int = "integer",
})
ident :: get(&names, TestEnum.ident)
try testing.expect_type(?[]u8, ident)
try testing.expect(mem.eql("identifier", ident?))
eof :: get(&names, TestEnum.eof)
try testing.expect_type(?[]u8, eof)
try testing.expect_equal(null, eof)
}
-45
View File
@@ -1,45 +0,0 @@
import "@std/meta"
EnumMap proc($E, $V type) type {
match typeinfo!(E) {
.enum |info|: return struct {
present [info.fields.len]mut bool # fixme: replace with bitset
values [info.fields.len]mut V
}
else: compile_error!("EnumMap key must be an enum")
}
}
init proc(
$E, $V type,
values meta.EnumFieldStruct(E, ?V, some!(null)),
) EnumMap(E, V) {
map EnumMap(E, V) = undefined
match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| {
map.present[i] = false
if field!(values, field.name) |value| {
map.present[i] = true
map.values[i] = value
}
}
else: compile_error!("EnumMap key must be an enum")
}
return map
}
get proc($E, $V type, map @EnumMap(E, V), key E) ?V {
# fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup
match typeinfo!(E) {
.enum |info|: expand for info.fields |field, i| {
if key == field!(E, field.name) {
if (map.present[i]) return map.values[i]
return null
}
}
else: compile_error!("EnumMap key must be an enum")
}
}
-25
View File
@@ -1,25 +0,0 @@
import "@std/mem"
import "@std/testing"
TestEnum :: enum(u8) {
ident = 3
int = 8
eof = 21
}
handles_sparse_enum_get test {
names EnumMap(TestEnum, []u8) = init({
ident = "identifier",
int = "integer",
})
ident :: get(&names, TestEnum.ident)
try testing.expect_type(?[]u8, ident)
try testing.expect(mem.eql("identifier", ident?))
eof :: get(&names, TestEnum.eof)
try testing.expect_type(?[]u8, eof)
try testing.expect_equal(null, eof)
}
+101 -101
View File
@@ -3,154 +3,154 @@ import "@std/mem"
PutError :: enum { key_exists }
Entry proc($K, $V type) type {
return struct {
# hash = 0 means empty
hash usize = 0
key K
value V
}
return struct {
# hash = 0 means empty
hash usize = 0
key K
value V
}
}
HashMap proc(
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
) type {
return struct {
entries []mut Entry(K, V)
count usize
allocator mem.Allocator
}
return struct {
entries []mut Entry(K, V)
count usize
allocator mem.Allocator
}
}
StringHashMap proc($V type) type {
return HashMap([]u8, V, str_hash, str_eql)
return HashMap([]u8, V, str_hash, str_eql)
}
init proc(
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
allocator mem.Allocator,
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
allocator mem.Allocator,
) HashMap(K, V, hash_key, keys_eql) {
return HashMap(K, V, hash_key, keys_eql){
entries = mem.empty(Entry(K, V)),
count = 0,
allocator = allocator,
}
return HashMap(K, V, hash_key, keys_eql){
entries = mem.empty(Entry(K, V)),
count = 0,
allocator = allocator,
}
}
#! free the entries in the hash map.
#! note: this operation invalidates the map.
deinit proc(
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @HashMap(K, V, hash_key, keys_eql),
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @HashMap(K, V, hash_key, keys_eql),
) void { mem.free(map.allocator, map.entries) }
get proc(
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @HashMap(K, V, hash_key, keys_eql),
key K,
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @HashMap(K, V, hash_key, keys_eql),
key K,
) ?V {
if (map.count == 0) return null
if (map.count == 0) return null
hash :: normalize(hash_key(key))
idx usize = hash & (map.entries.len - 1)
hash :: normalize(hash_key(key))
idx usize = hash & (map.entries.len - 1)
while true {
entry :: map.entries[idx]
if (entry.hash == 0) return null
while true {
entry :: map.entries[idx]
if (entry.hash == 0) return null
if (entry.hash == hash and keys_eql(entry.key, key)) {
return entry.value
}
if (entry.hash == hash and keys_eql(entry.key, key)) {
return entry.value
}
idx = (idx + 1) & (map.entries.len - 1)
}
idx = (idx + 1) & (map.entries.len - 1)
}
}
put proc(
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @mut HashMap(K, V, hash_key, keys_eql),
key K,
value V,
$K, $V type,
$hash_key proc(key K) usize,
$keys_eql proc(a, b K) bool,
map @mut HashMap(K, V, hash_key, keys_eql),
key K,
value V,
) void ! (PutError | mem.AllocError) {
# check grow
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
if (map.entries.len == 0 or map.count + 1 > threshold) {
old_entries :: map.entries
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
# check grow
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
if (map.entries.len == 0 or map.count + 1 > threshold) {
old_entries :: map.entries
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
# zero new entries
for (0..new_entries.len) |i| new_entries[i].hash = 0
# zero new entries
for (0..new_entries.len) |i| new_entries[i].hash = 0
# move old entries
for old_entries |entry| {
if (entry.hash == 0) continue
# move old entries
for old_entries |entry| {
if (entry.hash == 0) continue
# find an empty slot
idx usize = entry.hash & (new_entries.len - 1)
while new_entries[idx].hash != 0 {
idx = (idx + 1) & (new_entries.len - 1)
}
# find an empty slot
idx usize = entry.hash & (new_entries.len - 1)
while new_entries[idx].hash != 0 {
idx = (idx + 1) & (new_entries.len - 1)
}
new_entries[idx] = entry
}
new_entries[idx] = entry
}
map.entries = new_entries
mem.free(map.allocator, old_entries)
}
map.entries = new_entries
mem.free(map.allocator, old_entries)
}
# put new entry
hash :: normalize(hash_key(key))
idx usize = hash & (map.entries.len - 1)
# put new entry
hash :: normalize(hash_key(key))
idx usize = hash & (map.entries.len - 1)
while true {
entry :: map.entries[idx]
if entry.hash == 0 {
map.entries[idx] = Entry(K, V){
hash = hash,
key = key,
value = value,
}
map.count += 1
return
}
while true {
entry :: map.entries[idx]
if entry.hash == 0 {
map.entries[idx] = Entry(K, V){
hash = hash,
key = key,
value = value,
}
map.count += 1
return
}
if entry.hash == hash and keys_eql(entry.key, key) {
return .key_exists
}
if entry.hash == hash and keys_eql(entry.key, key) {
return .key_exists
}
idx = (idx + 1) & (map.entries.len - 1)
}
idx = (idx + 1) & (map.entries.len - 1)
}
}
hide normalize proc(hash usize) usize {
# mapping both 0 and 1 to 1 is safe because equality resolves
# collisions (since hash and key must both be equal).
if (hash == 0) return 1
return hash
# mapping both 0 and 1 to 1 is safe because equality resolves
# collisions (since hash and key must both be equal).
if (hash == 0) return 1
return hash
}
#! FNV-1a hash implementation.
#! note: vulnerable to collision attacks.
hide str_hash proc(key []u8) usize {
hash u32 = 2166136261 # offset basis
prime u32 = 16777619
hash u32 = 2166136261 # offset basis
prime u32 = 16777619
for key |byte| {
product u64 :: u64(hash xor u32(byte)) * prime
hash = u32(product & u64(maxval!(u32)))
}
for key |byte| {
product u64 :: u64(hash xor u32(byte)) * prime
hash = u32(product & u64(maxval!(u32)))
}
return usize(hash)
return usize(hash)
}
hide str_eql proc(a, b []u8) bool { return mem.eql(a, b) }
+6 -6
View File
@@ -2,12 +2,12 @@ import "@std/mem"
import "@std/testing"
handles_put_and_get test {
map StringHashMap(u32) = init(mem.c_allocator)
defer deinit(&map)
map StringHashMap(u32) = init(mem.c_allocator)
defer deinit(&map)
try put(&map, "key", 42)
value :: get(&map, "key")
try put(&map, "key", 42)
value :: get(&map, "key")
try testing.expect_type(?u32, value)
try testing.expect_equal(42, value?)
try testing.expect_type(?u32, value)
try testing.expect_equal(42, value?)
}
+68 -68
View File
@@ -1,124 +1,124 @@
import "@ffi/c"
File :: struct {
io Io
handle Handle
io Io
handle Handle
}
FileMode :: enum {
read_only
write_only
read_write
read_only
write_only
read_write
}
OpenError :: enum {
open_failed
open_failed
}
CloseError :: enum {
close_failed
close_failed
}
open proc(io Io, path [;0]u8, mode FileMode) File ! OpenError {
handle Handle :: try io.vtable.open(io.context, path, mode)
return File{ io = io, handle = handle }
handle Handle :: try io.vtable.open(io.context, path, mode)
return File{ io = io, handle = handle }
}
close proc(file File) void ! CloseError {
try file.io.vtable.close(file.io.context, file.handle)
try file.io.vtable.close(file.io.context, file.handle)
}
reader proc(file File) Reader {
return Reader {
context = file.io.context,
handle = file.handle,
read = file.io.vtable.read,
}
return Reader {
context = file.io.context,
handle = file.handle,
read = file.io.vtable.read,
}
}
writer proc(file File) Writer {
return Writer {
context = file.io.context,
handle = file.handle,
write = file.io.vtable.write,
}
return Writer {
context = file.io.context,
handle = file.handle,
write = file.io.vtable.write,
}
}
hide system_read proc(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
if (request > maximum) request = maximum
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
if (request > maximum) request = maximum
while true {
count c_long :: c.read(handle.file_desc, buffer.ptr, c_ulong(request))
if (count >= 0) return usize(count)
while true {
count c_long :: c.read(handle.file_desc, buffer.ptr, c_ulong(request))
if (count >= 0) return usize(count)
errno c_int :: c.__error()?^
if (errno == c.EINTR) continue
if (errno == c.EBADF) return .not_open_for_reading
return .read_failed
}
errno c_int :: c.__error()?^
if (errno == c.EINTR) continue
if (errno == c.EBADF) return .not_open_for_reading
return .read_failed
}
}
hide system_write proc(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if (request > maximum) request = maximum
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if (request > maximum) request = maximum
while true {
count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if (count >= 0) return usize(count)
while true {
count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
if (count >= 0) return usize(count)
errno c_int :: c.__error()?^
if (errno == c.EINTR) continue
if (errno == c.EBADF) return .not_open_for_writing
return .write_failed
}
errno c_int :: c.__error()?^
if (errno == c.EINTR) continue
if (errno == c.EBADF) return .not_open_for_writing
return .write_failed
}
}
hide system_open proc(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
flags c_int = c.O_RDONLY
match mode {
.read_only: flags = c.O_RDONLY
.write_only: flags = c.O_WRONLY
.read_write: flags = c.O_RDWR
}
while true {
fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags)
if (fd >= 0) return Handle{ file_desc = fd }
if (c.__error()?^ != c.EINTR) return .open_failed
}
flags c_int = c.O_RDONLY
match mode {
.read_only: flags = c.O_RDONLY
.write_only: flags = c.O_WRONLY
.read_write: flags = c.O_RDWR
}
while true {
fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags)
if (fd >= 0) return Handle{ file_desc = fd }
if (c.__error()?^ != c.EINTR) return .open_failed
}
}
hide system_close proc(_ ?@mut anyopaque, handle Handle) void ! CloseError {
if (c.close(handle.file_desc) != 0) return .close_failed
if (c.close(handle.file_desc) != 0) return .close_failed
}
hide system_stdin proc(_ ?@mut anyopaque) Handle {
return Handle{ file_desc = c_int(Stream.stdin) }
return Handle{ file_desc = c_int(Stream.stdin) }
}
hide system_stdout proc(_ ?@mut anyopaque) Handle {
return Handle{ file_desc = c_int(Stream.stdout) }
return Handle{ file_desc = c_int(Stream.stdout) }
}
hide system_stderr proc(_ ?@mut anyopaque) Handle {
return Handle{ file_desc = c_int(Stream.stderr) }
return Handle{ file_desc = c_int(Stream.stderr) }
}
hide system_vtable IoVTable :: IoVTable {
read = system_read,
write = system_write,
open = system_open,
close = system_close,
stdin = system_stdin,
stdout = system_stdout,
stderr = system_stderr,
read = system_read,
write = system_write,
open = system_open,
close = system_close,
stdin = system_stdin,
stdout = system_stdout,
stderr = system_stderr,
}
hide system proc() Io {
return Io {
context = null,
vtable = &system_vtable,
}
return Io {
context = null,
vtable = &system_vtable,
}
}
+345 -305
View File
@@ -2,407 +2,447 @@ import "@ffi/c"
import "@std/meta"
ReadError :: enum {
not_open_for_reading
read_failed
not_open_for_reading
read_failed
}
WriteError :: enum {
not_open_for_writing
write_failed
no_progress
not_open_for_writing
write_failed
no_progress
}
Handle :: union {
file_desc c_int
ptr @mut anyopaque
file_desc c_int
ptr @mut anyopaque
}
Stream :: enum(c_int) {
stdin = c.STDIN_FILENO
stdout = c.STDOUT_FILENO
stderr = c.STDERR_FILENO
stdin = c.STDIN_FILENO
stdout = c.STDOUT_FILENO
stderr = c.STDERR_FILENO
}
Io :: struct {
context ?@mut anyopaque
vtable @IoVTable
context ?@mut anyopaque
vtable @IoVTable
}
IoVTable :: struct {
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
open @proc(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError
close @proc(context ?@mut anyopaque, handle Handle) void ! CloseError
stdin @proc(context ?@mut anyopaque) Handle
stdout @proc(context ?@mut anyopaque) Handle
stderr @proc(context ?@mut anyopaque) Handle
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
open @proc(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError
close @proc(context ?@mut anyopaque, handle Handle) void ! CloseError
stdin @proc(context ?@mut anyopaque) Handle
stdout @proc(context ?@mut anyopaque) Handle
stderr @proc(context ?@mut anyopaque) Handle
}
Reader :: struct {
context ?@mut anyopaque
handle Handle
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
context ?@mut anyopaque
handle Handle
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
}
Writer :: struct {
context ?@mut anyopaque
handle Handle
write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
context ?@mut anyopaque
handle Handle
write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError
}
read proc(input Reader, buffer []mut u8) usize ! ReadError {
if (buffer.len == 0) return 0
if (buffer.len == 0) return 0
count usize :: try input.read(input.context, input.handle, buffer)
if (count > buffer.len) return .read_failed
count usize :: try input.read(input.context, input.handle, buffer)
if (count > buffer.len) return .read_failed
return count
return count
}
write proc(output Writer, bytes []u8) usize ! WriteError {
if (bytes.len == 0) return 0
if (bytes.len == 0) return 0
count usize :: try output.write(output.context, output.handle, bytes)
if (count > bytes.len) return .write_failed
count usize :: try output.write(output.context, output.handle, bytes)
if (count > bytes.len) return .write_failed
return count
return count
}
write_all proc(output Writer, bytes []u8) void ! WriteError {
offset usize = 0
while offset < bytes.len {
count usize :: try write(output, bytes[offset..])
if (count == 0) return .no_progress
offset += count
}
offset usize = 0
while offset < bytes.len {
count usize :: try write(output, bytes[offset..])
if (count == 0) return .no_progress
offset += count
}
}
stdin proc(io Io) Reader {
return Reader {
context = io.context,
handle = io.vtable.stdin(io.context),
read = io.vtable.read,
}
return Reader {
context = io.context,
handle = io.vtable.stdin(io.context),
read = io.vtable.read,
}
}
stdout proc(io Io) Writer {
return Writer {
context = io.context,
handle = io.vtable.stdout(io.context),
write = io.vtable.write,
}
return Writer {
context = io.context,
handle = io.vtable.stdout(io.context),
write = io.vtable.write,
}
}
stderr proc(io Io) Writer {
return Writer {
context = io.context,
handle = io.vtable.stderr(io.context),
write = io.vtable.write,
}
return Writer {
context = io.context,
handle = io.vtable.stderr(io.context),
write = io.vtable.write,
}
}
print proc(output Writer, $format []u8, $Args type, args Args) void ! WriteError {
expand for parse_format(format.len, format, Args) |token| {
match token.kind {
.unused: break
.literal: try write_all(output, format[token.start..token.end])
.string: try write_all(output, field!(args, token.field))
.default: try write_default(output, field!(args, token.field))
.decimal: try write_decimal(output, field!(args, token.field))
.binary: try write_integer(output, field!(args, token.field), 2, false)
.octal: try write_integer(output, field!(args, token.field), 8, false)
.hex_lower: try write_integer(output, field!(args, token.field), 16, false)
.hex_upper: try write_integer(output, field!(args, token.field), 16, true)
.character: try write_character(output, field!(args, token.field))
else: try write_float(output, field!(args, token.field), true)
}
}
expand for parse_format(format.len, format, Args) |token| {
match token.kind {
.unused: break
.literal: try write_all(output, format[token.start..token.end])
.string: try write_all(output, field!(args, token.field))
.default: try write_default(output, field!(args, token.field))
.decimal: try write_decimal(output, field!(args, token.field))
.binary: try write_integer(output, field!(args, token.field), 2, false)
.octal: try write_integer(output, field!(args, token.field), 8, false)
.hex_lower: try write_integer(output, field!(args, token.field), 16, false)
.hex_upper: try write_integer(output, field!(args, token.field), 16, true)
.character: try write_character(output, field!(args, token.field))
else: try write_float(output, field!(args, token.field), true)
}
}
}
hide write_integer_signed proc(output Writer, value i64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined
end usize = buffer.len
current i64 = value
buffer [65]mut u8 = undefined
end usize = buffer.len
current i64 = value
while true {
digit_value i64 :: rem!(current, i64(base))
digit u8 = if (digit_value < 0)
u8(-digit_value)
else
u8(digit_value)
while true {
digit_value i64 :: rem!(current, i64(base))
digit u8 = if (digit_value < 0)
u8(-digit_value)
else
u8(digit_value)
end -= 1
buffer[end] = if (digit < 10)
'0' + digit
else if (uppercase)
'A' + digit - 10
else
'a' + digit - 10
end -= 1
buffer[end] = if (digit < 10)
'0' + digit
else if (uppercase)
'A' + digit - 10
else
'a' + digit - 10
current = divtrunc!(current, i64(base))
if (current == 0) break
}
if value < 0 {
end -= 1
buffer[end] = '-'
current = divtrunc!(current, i64(base))
if (current == 0) break
}
try write_all(output, buffer[end..])
if value < 0 {
end -= 1
buffer[end] = '-'
}
try write_all(output, buffer[end..])
}
hide write_integer_unsigned proc(output Writer, value u64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined
end usize = buffer.len
current u64 = value
buffer [65]mut u8 = undefined
end usize = buffer.len
current u64 = value
while true {
digit u8 :: u8(rem!(current, base))
while true {
digit u8 :: u8(rem!(current, base))
end -= 1
buffer[end] = if (digit < 10)
'0' + digit
else if (uppercase)
'A' + digit - 10
else
'a' + digit - 10
end -= 1
buffer[end] = if (digit < 10)
'0' + digit
else if (uppercase)
'A' + digit - 10
else
'a' + digit - 10
current = divtrunc!(current, base)
if (current == 0) break
}
current = divtrunc!(current, base)
if (current == 0) break
}
try write_all(output, buffer[end..])
try write_all(output, buffer[end..])
}
hide FormatTokenKind :: enum {
unused
literal
default
string
decimal
binary
octal
hex_lower
hex_upper
character
scientific
unused
literal
default
string
decimal
binary
octal
hex_lower
hex_upper
character
scientific
}
hide FormatToken :: struct {
kind FormatTokenKind
start usize
end usize
field []u8
kind FormatTokenKind
start usize
end usize
field []u8
}
hide parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
tokens [N]mut FormatToken = undefined
for (usize(0))..format.len |index| {
tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" }
}
tokens [N]mut FormatToken = undefined
for (usize(0))..format.len |index| {
tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" }
}
field_count usize = 0
match typeinfo!(Args) {
.record |r|: {
if (!r.is_tuple) compile_error!("io.print arguments must be a tuple")
field_count = r.fields.len
}
else: compile_error!("io.print arguments must be a tuple")
}
field_count usize = 0
match typeinfo!(Args) {
.record |r|: {
if (!r.is_tuple) compile_error!("io.print arguments must be a tuple")
field_count = r.fields.len
}
else: compile_error!("io.print arguments must be a tuple")
}
token_count usize = 0
argument_count usize = 0
literal_start usize = 0
cursor usize = 0
while cursor < format.len {
byte :: format[cursor]
if byte == '{' {
if cursor + 1 >= format.len {
compile_error!("io.print format has an unmatched '{'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = cursor,
field = "",
}
token_count += 1
}
next :: format[cursor + 1]
if next == '{' {
tokens[token_count] = FormatToken{
kind = .literal,
start = cursor,
end = cursor + 1,
field = "",
}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
token_count usize = 0
argument_count usize = 0
literal_start usize = 0
cursor usize = 0
while cursor < format.len {
byte :: format[cursor]
if byte == '{' {
if cursor + 1 >= format.len {
compile_error!("io.print format has an unmatched '{'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = cursor,
field = "",
}
token_count += 1
}
next :: format[cursor + 1]
if next == '{' {
tokens[token_count] = FormatToken{
kind = .literal,
start = cursor,
end = cursor + 1,
field = "",
}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
kind FormatTokenKind = .default
width usize = 2
if next != '}' {
if cursor + 2 >= format.len or format[cursor + 2] != '}' {
compile_error!("io.print format expects a one-character specifier")
}
kind FormatTokenKind = .default
width usize = 2
if next != '}' {
if cursor + 2 >= format.len or format[cursor + 2] != '}' {
compile_error!("io.print format expects a one-character specifier")
}
width = 3
if (next == 's') kind = .string
else if (next == 'd') kind = .decimal
else if (next == 'b') kind = .binary
else if (next == 'o') kind = .octal
else if (next == 'x') kind = .hex_lower
else if (next == 'X') kind = .hex_upper
else if (next == 'c') kind = .character
else if (next == 'e') kind = .scientific
else compile_error!("io.print format has an unknown specifier")
}
width = 3
if (next == 's') kind = .string
else if (next == 'd') kind = .decimal
else if (next == 'b') kind = .binary
else if (next == 'o') kind = .octal
else if (next == 'x') kind = .hex_lower
else if (next == 'X') kind = .hex_upper
else if (next == 'c') kind = .character
else if (next == 'e') kind = .scientific
else compile_error!("io.print format has an unknown specifier")
}
if argument_count >= field_count {
compile_error!("io.print format argument count does not match the tuple")
}
if argument_count >= field_count {
compile_error!("io.print format argument count does not match the tuple")
}
tokens[token_count] = FormatToken{
kind = kind,
start = 0,
end = 0,
field = format_field_name(Args, argument_count),
}
tokens[token_count] = FormatToken{
kind = kind,
start = 0,
end = 0,
field = format_field_name(Args, argument_count),
}
token_count += 1
argument_count += 1
cursor += width
literal_start = cursor
continue
}
token_count += 1
argument_count += 1
cursor += width
literal_start = cursor
continue
}
if byte == '}' {
if cursor + 1 >= format.len or format[cursor + 1] != '}' {
compile_error!("io.print format has an unmatched '}'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = cursor,
field = "",
}
token_count += 1
}
tokens[token_count] = FormatToken{
kind = .literal,
start = cursor,
end = cursor + 1,
field = "",
}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
cursor += 1
}
if byte == '}' {
if cursor + 1 >= format.len or format[cursor + 1] != '}' {
compile_error!("io.print format has an unmatched '}'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = cursor,
field = "",
}
token_count += 1
}
tokens[token_count] = FormatToken{
kind = .literal,
start = cursor,
end = cursor + 1,
field = "",
}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
cursor += 1
}
if literal_start < format.len {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = format.len,
field = "",
}
}
if literal_start < format.len {
tokens[token_count] = FormatToken{
kind = .literal,
start = literal_start,
end = format.len,
field = "",
}
}
if argument_count != field_count {
compile_error!("io.print format argument count does not match the tuple")
}
return tokens
if argument_count != field_count {
compile_error!("io.print format argument count does not match the tuple")
}
return tokens
}
hide format_field_name proc($T type, index usize) []u8 {
match typeinfo!(T) {
.record |r|: return r.fields[index].name
else: compile_error!("io.print arguments must be a tuple")
}
match typeinfo!(T) {
.record |r|: return r.fields[index].name
else: compile_error!("io.print arguments must be a tuple")
}
}
hide distinct_value proc($Backing, $Distinct type, value Distinct) Backing {
return ptrcast!(Backing, &value)^
}
hide scalar_or_distinct_type proc($T type) bool {
match typeinfo!(T) {
.bool: return true
.integer: return true
.float: return true
.distinct: return true
else: return false
}
}
hide write_integer proc(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
match typeinfo!(T) {
.integer: if minval!(T) < 0 {
try write_integer_signed(output, i64(value), base, uppercase)
} else {
try write_integer_unsigned(output, u64(value), base, uppercase)
}
else: compile_error!("io.print integer format requires an integer argument")
}
match typeinfo!(T) {
.integer: if minval!(T) < 0 {
try write_integer_signed(output, i64(value), base, uppercase)
} else {
try write_integer_unsigned(output, u64(value), base, uppercase)
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_integer(output, distinct_value(backing, T, value), base, uppercase)
} else {
compile_error!("io.print integer format requires an integer argument")
}
else: compile_error!("io.print integer format requires an integer argument")
}
}
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
hide write_float proc(output Writer, $T type, value T, scientific bool) void ! WriteError {
match typeinfo!(T) {
.float: {
buffer [64]mut u8 = undefined
count c_int = 0
if sizeof!(T) == 4 {
if (scientific) count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
else count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value)
} else if scientific {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value)
} else {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value)
}
if (count < 0 or usize(count) >= buffer.len) return .write_failed
try write_all(output, buffer[0..usize(count)])
}
else: compile_error!("io.print float format requires a float argument")
}
match typeinfo!(T) {
.float: {
buffer [64]mut u8 = undefined
count c_int = 0
if sizeof!(T) == 4 {
if (scientific) count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
else count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value)
} else if scientific {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value)
} else {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value)
}
if (count < 0 or usize(count) >= buffer.len) return .write_failed
try write_all(output, buffer[0..usize(count)])
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_float(output, distinct_value(backing, T, value), scientific)
} else {
compile_error!("io.print float format requires a float argument")
}
else: compile_error!("io.print float format requires a float argument")
}
}
hide write_decimal proc(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, false)
else: compile_error!("io.print '{d}' requires an integer or float argument")
}
match typeinfo!(T) {
.integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, false)
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_decimal(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{d}' requires an integer or float argument")
}
else: compile_error!("io.print '{d}' requires an integer or float argument")
}
}
hide write_character proc(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.integer: {
if minval!(T) < 0 or maxval!(T) > 255 {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
buffer [1]u8 = [u8(value)]
try write_all(output, buffer[..])
}
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
match typeinfo!(T) {
.integer: {
if minval!(T) < 0 or maxval!(T) > 255 {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
buffer [1]u8 = [u8(value)]
try write_all(output, buffer[..])
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_character(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
}
hide write_default proc(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.bool: if value {
try write_all(output, "true")
} else {
try write_all(output, "false")
}
.integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, false)
.array: try write_all(output, value)
.pointer: try write_all(output, value)
.slice: try write_all(output, value)
.enum |enum_info|: {
expand for enum_info.fields |field| {
if value == field!(T, field.name) {
try write_all(output, ".")
try write_all(output, field.name)
return
}
}
return .write_failed
}
else: compile_error!("io.print '{}' does not support this argument type")
}
match typeinfo!(T) {
.bool: if value {
try write_all(output, "true")
} else {
try write_all(output, "false")
}
.integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, false)
.array: try write_all(output, value)
.pointer: try write_all(output, value)
.slice: try write_all(output, value)
.enum |enum_info|: {
expand for enum_info.fields |field| {
if value == field!(T, field.name) {
try write_all(output, ".")
try write_all(output, field.name)
return
}
}
return .write_failed
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_default(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{}' does not support this argument type")
}
else: compile_error!("io.print '{}' does not support this argument type")
}
}
+107 -107
View File
@@ -1,119 +1,119 @@
import "@ffi/c"
AllocError :: enum {
out_of_memory
out_of_memory
}
Allocator :: struct {
context ?@mut anyopaque
vtable @AllocatorVTable
context ?@mut anyopaque
vtable @AllocatorVTable
}
AllocatorVTable :: struct {
alloc @proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8
realloc @proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8
free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
alloc @proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8
realloc @proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8
free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
}
raw_alloc proc(allocator Allocator, size usize, alignment usize) ?*mut u8 {
return allocator.vtable.alloc(allocator.context, size, alignment)
return allocator.vtable.alloc(allocator.context, size, alignment)
}
raw_realloc proc(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment)
return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment)
}
raw_free proc(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.vtable.free(allocator.context, memory, size, alignment)
allocator.vtable.free(allocator.context, memory, size, alignment)
}
eql proc($T type, left, right []T) bool {
if (left.len != right.len) return false
for (0..left.len) |i| if (left[i] != right[i]) {
if (left.len != right.len) return false
for (0..left.len) |i| if (left[i] != right[i]) {
return false
}
return true
}
return true
}
#! allocate memory for a slice of type `T` with `count` elements.
alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError {
if (count == 0) return empty_slice(T, 0)
if (count == 0) return empty_slice(T, 0)
element_size usize :: sizeof!(T)
if (element_size == 0) return empty_slice(T, count)
element_size usize :: sizeof!(T)
if (element_size == 0) return empty_slice(T, count)
if count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory
}
if count > divtrunc!(maxval!(usize), element_size) {
return .out_of_memory
}
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
if memory |bytes| {
pointer *mut T :: ptrcast!(T, bytes)
return pointer[..count]
}
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
if memory |bytes| {
pointer *mut T :: ptrcast!(T, bytes)
return pointer[..count]
}
return .out_of_memory
return .out_of_memory
}
#! reallocate memory for a slice of type `T` with `new_count` elements.
#! reallocating with `new_count == 0` will free the memory and return an empty slice.
#! note: memory must be reallocated with the same allocator that was used to allocate it.
realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
if new_count == memory.len {
return memory
}
if new_count == memory.len {
return memory
}
if new_count == 0 {
free(allocator, memory)
return empty_slice(T, 0)
}
if new_count == 0 {
free(allocator, memory)
return empty_slice(T, 0)
}
element_size usize :: sizeof!(T)
if (element_size == 0) return empty_slice(T, new_count)
if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory
element_size usize :: sizeof!(T)
if (element_size == 0) return empty_slice(T, new_count)
if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory
old_memory ?*mut u8 = null
old_size usize = 0
if memory.len != 0 {
old_memory = ptrcast!(u8, memory.ptr)
old_size = memory.len * element_size
}
resized ?*mut u8 = raw_realloc(
allocator,
old_memory,
old_size,
new_count * element_size,
alignof!(T),
)
if resized |bytes| {
pointer *mut T :: ptrcast!(T, bytes)
return pointer[..new_count]
}
old_memory ?*mut u8 = null
old_size usize = 0
if memory.len != 0 {
old_memory = ptrcast!(u8, memory.ptr)
old_size = memory.len * element_size
}
resized ?*mut u8 = raw_realloc(
allocator,
old_memory,
old_size,
new_count * element_size,
alignof!(T),
)
if resized |bytes| {
pointer *mut T :: ptrcast!(T, bytes)
return pointer[..new_count]
}
return .out_of_memory
return .out_of_memory
}
#! free memory allocated for a slice of type `T`.
#! note: memory must be freed with the same allocator that was used to allocate it.
free proc($T type, allocator Allocator, memory []T) void {
if (memory.len == 0 or sizeof!(T) == 0) return
raw_free(allocator, ptrcast!(
u8,
constcast!(memory).ptr),
memory.len * sizeof!(T),
alignof!(T),
)
if (memory.len == 0 or sizeof!(T) == 0) return
raw_free(allocator, ptrcast!(
u8,
constcast!(memory).ptr),
memory.len * sizeof!(T),
alignof!(T),
)
}
#! get an empty slice of type `T` with `count` elements.
empty_slice proc($T type, count usize) []mut T {
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
return pointer[..count]
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
return pointer[..count]
}
#! get an empty slice of type `T` with 0 elements.
empty proc($T type) []mut T {
return empty_slice(T, 0)
return empty_slice(T, 0)
}
hide empty_storage [1]mut u64 = [0]
@@ -121,70 +121,70 @@ hide empty_storage [1]mut u64 = [0]
hide malloc_alignment usize :: 16 # note: aarch64-macos libc malloc alignment assumption.
hide power_of_two proc(value usize) bool {
if (value == 0) return false
current usize = value
while current > 1 {
half usize = divtrunc!(current, 2)
if (half * 2 != current) return false
current = half
}
return true
if (value == 0) return false
current usize = value
while current > 1 {
half usize = divtrunc!(current, 2)
if (half * 2 != current) return false
current = half
}
return true
}
hide c_alloc proc(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
if (power_of_two(alignment) == false) return null
if (alignment <= malloc_alignment) return ptrcast!(u8, c.malloc(c_ulong(size)))
if (power_of_two(alignment) == false) return null
if (alignment <= malloc_alignment) return ptrcast!(u8, c.malloc(c_ulong(size)))
memory [1]mut ?*mut anyopaque = [null]
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
if (status != 0) return null
memory [1]mut ?*mut anyopaque = [null]
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
if (status != 0) return null
return ptrcast!(u8, memory[0])
return ptrcast!(u8, memory[0])
}
hide c_realloc proc(
_ ?@mut anyopaque,
memory ?*mut u8,
old_size usize,
new_size usize,
alignment usize,
_ ?@mut anyopaque,
memory ?*mut u8,
old_size usize,
new_size usize,
alignment usize,
) ?*mut u8 {
if (power_of_two(alignment) == false) return null
if (power_of_two(alignment) == false) return null
if new_size == 0 {
c.free(memory)
return null
}
if new_size == 0 {
c.free(memory)
return null
}
if memory |old_memory| {
if alignment <= malloc_alignment {
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
}
if memory |old_memory| {
if alignment <= malloc_alignment {
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
}
new_memory ?*mut u8 = c_alloc(null, new_size, alignment)
if new_memory |new_bytes| {
copy_size usize = old_size
if (new_size < copy_size) copy_size = new_size
memcopy!(new_bytes[..copy_size], old_memory[..copy_size])
c.free(old_memory)
}
return new_memory
}
new_memory ?*mut u8 = c_alloc(null, new_size, alignment)
if new_memory |new_bytes| {
copy_size usize = old_size
if (new_size < copy_size) copy_size = new_size
memcopy!(new_bytes[..copy_size], old_memory[..copy_size])
c.free(old_memory)
}
return new_memory
}
return c_alloc(null, new_size, alignment)
return c_alloc(null, new_size, alignment)
}
hide c_free proc(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c.free(memory)
c.free(memory)
}
hide c_vtable AllocatorVTable :: AllocatorVTable {
alloc = c_alloc,
realloc = c_realloc,
free = c_free,
alloc = c_alloc,
realloc = c_realloc,
free = c_free,
}
c_allocator Allocator :: Allocator {
context = null,
vtable = &c_vtable,
context = null,
vtable = &c_vtable,
}
+42 -42
View File
@@ -1,61 +1,61 @@
Layout :: enum { auto, c }
ArrayInfo :: struct {
child type
len usize
child type
len usize
}
FieldInfo :: struct {
name []u8
type type
index usize
name []u8
type type
index usize
}
RecordInfo :: struct {
name ?[]u8
fields []FieldInfo
is_tuple bool
layout Layout
name ?[]u8
fields []FieldInfo
is_tuple bool
layout Layout
}
EnumInfo :: struct {
fields []FieldInfo
fields []FieldInfo
}
TypeInfo :: union(enum) {
invalid void
void void
noreturn void
anyopaque void
bool void
integer void
float void
array ArrayInfo
pointer void
slice void
range void
optional void
function void
enum EnumInfo
record RecordInfo
union void
fallible void
distinct void
invalid void
void void
noreturn void
anyopaque void
bool void
integer void
float void
array ArrayInfo
pointer void
slice void
range void
optional void
function void
enum EnumInfo
record RecordInfo
union void
fallible void
distinct type
}
EnumFieldStruct proc($E, $Field type, $default ?Field) type {
match typeinfo!(E) {
.enum |info|: {
names [info.fields.len]mut []u8 = undefined
field_types [info.fields.len]mut type = undefined
defaults [info.fields.len]mut ?Field = undefined
expand for info.fields |field, index| {
names[index] = field.name
field_types[index] = Field
defaults[index] = default
}
return struct_type!(.auto, names, field_types, defaults)
}
else: compile_error!("EnumFieldStruct key must be an enum")
}
match typeinfo!(E) {
.enum |info|: {
names [info.fields.len]mut []u8 = undefined
field_types [info.fields.len]mut type = undefined
defaults [info.fields.len]mut ?Field = undefined
expand for info.fields |field, index| {
names[index] = field.name
field_types[index] = Field
defaults[index] = default
}
return struct_type!(.auto, names, field_types, defaults)
}
else: compile_error!("EnumFieldStruct key must be an enum")
}
}
+50 -33
View File
@@ -1,50 +1,67 @@
testing :: import "@std/testing"
TestTokenKind :: enum(u8) {
ident = 3
int = 8
eof = 21
ident = 3
int = 8
eof = 21
}
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
TestArrayAlias :: alias [3]u16
TestInner :: distinct u16
TestOuter :: distinct TestInner
TestOuterAlias :: alias TestOuter
hide array_info_matches proc($Array, $Child type, $len usize) bool {
match typeinfo!(Array) {
.array |info|: return info.child == Child and info.len == len
else: return false
}
match typeinfo!(Array) {
.array |info|: return info.child == Child and info.len == len
else: return false
}
}
hide distinct_info_matches proc($Distinct, $Backing type) bool {
match typeinfo!(Distinct) {
.distinct |backing|: return backing == Backing
else: return false
}
}
array_reflection_exposes_child_and_logical_length test {
try testing.expect($(array_info_matches([4]i32, i32, 4)))
try testing.expect($(array_info_matches([0]bool, bool, 0)))
try testing.expect($(array_info_matches(TestArrayAlias, u16, 3)))
try testing.expect($(array_info_matches([2]mut i64, i64, 2)))
try testing.expect($(array_info_matches([2;0]u8, u8, 2)))
try testing.expect($(array_info_matches([4]i32, i32, 4)))
try testing.expect($(array_info_matches([0]bool, bool, 0)))
try testing.expect($(array_info_matches(TestArrayAlias, u16, 3)))
try testing.expect($(array_info_matches([2]mut i64, i64, 2)))
try testing.expect($(array_info_matches([2;0]u8, u8, 2)))
}
distinct_reflection_exposes_immediate_backing test {
try testing.expect($(distinct_info_matches(TestInner, u16)))
try testing.expect($(distinct_info_matches(TestOuter, TestInner)))
try testing.expect($(distinct_info_matches(TestOuterAlias, TestInner)))
}
enum_field_struct_defaults test {
names TestNames = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
try testing.expect(value.len == 10)
} else {
try testing.expect(false)
}
if field!(names, "int") |value| {
try testing.expect(value.len == 7)
} else {
try testing.expect(false)
}
if field!(names, "eof") |_| {
try testing.expect(false)
}
names TestNames = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
try testing.expect(value.len == 10)
} else {
try testing.expect(false)
}
if field!(names, "int") |value| {
try testing.expect(value.len == 7)
} else {
try testing.expect(false)
}
if field!(names, "eof") |_| {
try testing.expect(false)
}
empty TestNames = {}
if field!(empty, "ident") |_| {
try testing.expect(false)
}
empty TestNames = {}
if field!(empty, "ident") |_| {
try testing.expect(false)
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import "@std/io"
Init :: struct {
io io.Io
io io.Io
}
+2 -2
View File
@@ -1,11 +1,11 @@
import "io"
import "enums"
import "enums/enummap"
import "hashmap"
import "arraylist"
import "strmap"
Io :: alias io.Io
EnumMap :: alias enums.EnumMap
EnumMap :: alias enummap.EnumMap
ArrayList :: alias arraylist.ArrayList
StringHashMap :: alias hashmap.StringHashMap
StringMap :: alias strmap.StringMap
+71 -71
View File
@@ -1,95 +1,95 @@
import "@std/mem"
StringMap proc($V type) type {
return struct {
keys [][]u8
values []V
len_indexes []u32
min_len u32
max_len u32
}
return struct {
keys [][]u8
values []V
len_indexes []u32
min_len u32
max_len u32
}
}
hide Pair proc($V type) type {
return struct { []u8, V }
return struct { []u8, V }
}
#! initializes a static string map from a list of key-value pairs (constructed at compile-time).
init proc($V type, $N usize, $entries [N]Pair(V)) StringMap(V) {
if N > usize(maxval!(u32)) {
compile_error!("static string map has too many entries")
}
if N > usize(maxval!(u32)) {
compile_error!("static string map has too many entries")
}
keys [N]mut []u8 = undefined
values [N]mut V = undefined
keys [N]mut []u8 = undefined
values [N]mut V = undefined
# assert no duplicate keys
for entries |entry, i| {
if entry.0.len > usize(maxval!(u32)) {
compile_error!("static string map key is too long")
}
# assert no duplicate keys
for entries |entry, i| {
if entry.0.len > usize(maxval!(u32)) {
compile_error!("static string map key is too long")
}
for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) {
compile_error!("duplicate static string map key")
}
for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) {
compile_error!("duplicate static string map key")
}
keys[i] = entry.0
values[i] = entry.1
}
keys[i] = entry.0
values[i] = entry.1
}
if N == 0 {
len_indexes [0]u32 = undefined
return StringMap(V){
keys = keys[..],
values = values[..],
len_indexes = len_indexes[..],
min_len = 0,
max_len = 0,
}
}
if N == 0 {
len_indexes [0]u32 = undefined
return StringMap(V){
keys = keys[..],
values = values[..],
len_indexes = len_indexes[..],
min_len = 0,
max_len = 0,
}
}
# fixme: insertion sort is compile-time O(N²); replace if large maps affect builds
for 1..N |i| {
key :: keys[i]
value :: values[i]
j usize = i
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
keys[j] = keys[j - 1]
values[j] = values[j - 1]
}
keys[j] = key
values[j] = value
}
# fixme: insertion sort is compile-time O(N²); replace if large maps affect builds
for 1..N |i| {
key :: keys[i]
value :: values[i]
j usize = i
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
keys[j] = keys[j - 1]
values[j] = values[j - 1]
}
keys[j] = key
values[j] = value
}
min_len u32 :: u32(keys[0].len)
max_len u32 :: u32(keys[N - 1].len)
len_indexes [usize(max_len) + 1]mut u32 = undefined
entry_index usize = 0
for 0..=usize(max_len) |length| {
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
len_indexes[length] = u32(entry_index)
}
min_len u32 :: u32(keys[0].len)
max_len u32 :: u32(keys[N - 1].len)
len_indexes [usize(max_len) + 1]mut u32 = undefined
entry_index usize = 0
for 0..=usize(max_len) |length| {
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
len_indexes[length] = u32(entry_index)
}
return StringMap(V) {
keys = keys[..],
values = values[..],
len_indexes = len_indexes[..],
min_len = min_len,
max_len = max_len,
}
return StringMap(V) {
keys = keys[..],
values = values[..],
len_indexes = len_indexes[..],
min_len = min_len,
max_len = max_len,
}
}
get proc($V type, map @StringMap(V), key []u8) ?V {
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
length u32 = u32(key.len)
if (length < map.min_len or length > map.max_len) return null
length u32 = u32(key.len)
if (length < map.min_len or length > map.max_len) return null
idx usize = usize(map.len_indexes[usize(length)])
while idx < map.keys.len : idx += 1 {
candidate :: map.keys[idx]
if (candidate.len != key.len) return null # key not found
if mem.eql(u8, candidate, key) return map.values[idx]
}
return null
idx usize = usize(map.len_indexes[usize(length)])
while idx < map.keys.len : idx += 1 {
candidate :: map.keys[idx]
if (candidate.len != key.len) return null # key not found
if mem.eql(u8, candidate, key) return map.values[idx]
}
return null
}
+1
View File
@@ -0,0 +1 @@
# todo
+71 -71
View File
@@ -1,85 +1,85 @@
import "@std/debug"
import "@std/mem"
import "@std/debug"
import "@std/mem"
Error :: enum {
expectation_failed
Error :: enum {
expectation_failed
}
SourceLocation :: struct {
file []u8
line usize
column usize
SourceLocation :: struct {
file []u8
line usize
column usize
}
expect proc(condition bool, location SourceLocation) void ! Error {
if !condition {
debug.print("{s}:{d}:{d}: expectation failed\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
expect proc(condition bool, location SourceLocation) void ! Error {
if !condition {
debug.print("{s}:{d}:{d}: expectation failed\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
}
expect_equal proc($T type, expected, actual T, location SourceLocation) void ! Error {
match typeinfo!(T) {
.optional: {
if expected |expected_value| {
if actual |actual_value| {
try expect_equal(expected_value, actual_value, location)
return
}
debug.print("{s}:{d}:{d}: expected an optional value, found null\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
if actual |_| {
debug.print("{s}:{d}:{d}: expected null, found an optional value\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
}
.slice: if !mem.eql(expected, actual) {
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
else: if expected != actual {
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {
location.file,
location.line,
location.column,
expected,
actual,
})
return .expectation_failed
}
}
expect_equal proc($T type, expected, actual T, location SourceLocation) void ! Error {
match typeinfo!(T) {
.optional: {
if expected |expected_value| {
if actual |actual_value| {
try expect_equal(expected_value, actual_value, location)
return
}
debug.print("{s}:{d}:{d}: expected an optional value, found null\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
if actual |_| {
debug.print("{s}:{d}:{d}: expected null, found an optional value\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
}
.slice: if !mem.eql(expected, actual) {
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {
location.file,
location.line,
location.column,
})
return .expectation_failed
}
else: if expected != actual {
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {
location.file,
location.line,
location.column,
expected,
actual,
})
return .expectation_failed
}
}
}
expect_type proc($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
try expect($(Expected == Actual), location)
expect_type proc($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
try expect($(Expected == Actual), location)
}
run proc(name []u8, callback *proc() void ! Error) bool {
callback() catch |_| {
debug.print("{s}...[failed]\n", {name,})
return false
}
debug.print("{s}...[ok]\n", {name,})
return true
run proc(name []u8, callback *proc() void ! Error) bool {
callback() catch |_| {
debug.print("{s}...[failed]\n", {name,})
return false
}
debug.print("{s}...[ok]\n", {name,})
return true
}
summary proc(passed, failed i32) void {
debug.print("{d} passed, {d} failed\n", {passed, failed})
summary proc(passed, failed i32) void {
debug.print("{d} passed, {d} failed\n", {passed, failed})
}