Compare commits

...

5 Commits

Author SHA1 Message Date
fee5dc3099 more error handling 2026-03-16 12:26:58 +01:00
720218cf31 error handling 2026-03-16 10:59:55 +01:00
964bbe4ec0 make mem.free O(1) with some linked list voodoo thank you bill gates 2026-03-16 09:46:41 +01:00
78282679e7 a few more fixes 2026-03-16 09:32:12 +01:00
7b09b505b5 a few fixes 2026-03-16 09:20:40 +01:00
17 changed files with 338 additions and 162 deletions

View File

@@ -26,15 +26,9 @@ func main[argc: i64, argv: ptr] : i64
if i < url_len if i < url_len
path = str.substr(url, i, url_len - i) path = str.substr(url, i, url_len - i)
let addr: i64 = net.resolve(host) let addr: i64 = must(net.resolve?(host))
if addr <= 0
io.println("ERROR: domain resolution failed")
return 1
let s: i64 = net.connect(addr, 80) let s: i64 = must(net.connect?(addr, 80))
if s < 0
io.println("ERROR: connection failed")
return 1
// TODO: add string builder to std // TODO: add string builder to std
let req: str = "GET " let req: str = "GET "

View File

@@ -15,7 +15,7 @@ func part2[l1: Array, l2: Array] : void
io.println_i64(out) io.println_i64(out)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
let l1: Array = [] let l1: Array = []
let l2: Array = [] let l2: Array = []

View File

@@ -40,7 +40,7 @@ func part2[data: Array] : void
io.println_i64(out) io.println_i64(out)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
let data: Array = [] let data: Array = []
for i in 0..lines->size for i in 0..lines->size

View File

@@ -56,7 +56,7 @@ func part2[lines: Array] : void
io.println_i64(out) io.println_i64(out)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
part1(lines) part1(lines)
part2(lines) part2(lines)

View File

@@ -47,7 +47,7 @@ func part2[updates: Array, rules_left: Array, rules_right: Array] : void
io.println_i64(out) io.println_i64(out)
func main[] : i64 func main[] : i64
let data: Array = io.read_file("input.txt") |> str.split("\n\n") let data: Array = must(io.read_text_file?("input.txt")) |> str.split("\n\n")
let rules_left: Array = [] let rules_left: Array = []
let rules_right: Array = [] let rules_right: Array = []

View File

@@ -61,7 +61,7 @@ func part2[equations: Array] : void
io.println_i64(out) io.println_i64(out)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
let equations: Array = [] let equations: Array = []
for i in 0..lines->size for i in 0..lines->size

View File

@@ -47,7 +47,7 @@ func part2[lines: Array] : void
io.println_i64(password) io.println_i64(password)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
part1(lines) part1(lines)
part2(lines) part2(lines)

View File

@@ -54,7 +54,7 @@ func part2[ranges: Array] : void
io.println_i64(sum) io.println_i64(sum)
func main[] : i64 func main[] : i64
let ranges: Array = io.read_file("input.txt") |> str.split(",") let ranges: Array = must(io.read_text_file?("input.txt")) |> str.split(",")
part1(ranges) part1(ranges)
part2(ranges) part2(ranges)

View File

@@ -45,7 +45,7 @@ func part2[lines: Array] : void
io.println_i64(sum) io.println_i64(sum)
func main[] : i64 func main[] : i64
let lines: Array = io.read_file("input.txt") |> str.split("\n") let lines: Array = must(io.read_text_file?("input.txt")) |> str.split("\n")
part1(lines) part1(lines)
part2(lines) part2(lines)

View File

@@ -1,7 +1,5 @@
func main[] : i64 func main[] : i64
let s: i64 = net.listen(net.pack_addr(127, 0, 0, 1), 8000) let s: i64 = must(net.listen?(net.pack_addr(127, 0, 0, 1), 8000))
if s < 0
panic("failed to bind a socket")
io.println("Listening on port 8000...") io.println("Listening on port 8000...")
let resp: str = mem.alloc(60000) let resp: str = mem.alloc(60000)

View File

@@ -1,7 +1,5 @@
func main[] : i64 func main[] : i64
let s: i64 = net.udp_listen(net.pack_addr(127, 0, 0, 1), 8000) let s: i64 = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000))
if !s
panic("failed to bind a socket")
io.println("Listening on port 8000...") io.println("Listening on port 8000...")
while true while true
@@ -10,5 +8,5 @@ func main[] : i64
net.UDPPacket.free(pkt) net.UDPPacket.free(pkt)
continue continue
net.udp_send_to(s, pkt->source_addr, pkt->buffer, pkt->size) net.udp_send_to(s, pkt->source_addr, pkt->data, pkt->size)
net.UDPPacket.free(pkt) net.UDPPacket.free(pkt)

View File

@@ -17,6 +17,8 @@ impl Analyzer {
functions: HashMap::from([ functions: HashMap::from([
("_builtin_heap_head".into(), 0), ("_builtin_heap_head".into(), 0),
("_builtin_heap_tail".into(), 0), ("_builtin_heap_tail".into(), 0),
("_builtin_err_code".into(), 0),
("_builtin_err_msg".into(), 0),
("_builtin_read64".into(), 1), ("_builtin_read64".into(), 1),
("_builtin_set64".into(), 2), ("_builtin_set64".into(), 2),
("_builtin_syscall".into(), -1), ("_builtin_syscall".into(), -1),

View File

@@ -110,6 +110,8 @@ section .bss
_heap_head: resq 1 _heap_head: resq 1
_heap_tail: resq 1 _heap_tail: resq 1
_environ: resq 1 _environ: resq 1
_err_code: resq 1
_err_msg: resq 1
section .text._builtin_heap_head section .text._builtin_heap_head
_builtin_heap_head: _builtin_heap_head:
@@ -121,6 +123,16 @@ _builtin_heap_tail:
lea rax, [rel _heap_tail] lea rax, [rel _heap_tail]
ret ret
section .text._builtin_err_code
_builtin_err_code:
lea rax, [rel _err_code]
ret
section .text._builtin_err_msg
_builtin_err_msg:
lea rax, [rel _err_msg]
ret
section .text._builtin_read64 section .text._builtin_read64
_builtin_read64: _builtin_read64:
mov rax, qword [rdi] mov rax, qword [rdi]
@@ -734,6 +746,7 @@ _builtin_environ:
Expr::New(struct_name) => { Expr::New(struct_name) => {
let struct_fields = &self.analyzer.structs[&struct_name.lexeme]; let struct_fields = &self.analyzer.structs[&struct_name.lexeme];
// TODO: panic on mem.alloc error
let memory_size = struct_fields.len() * 8; let memory_size = struct_fields.len() * 8;
emit!(&mut self.output, " mov rdi, {}", memory_size); emit!(&mut self.output, " mov rdi, {}", memory_size);
emit!(&mut self.output, " call mem.alloc"); emit!(&mut self.output, " call mem.alloc");

View File

@@ -4,14 +4,17 @@ const SOCK_DGRAM = 2
const SOL_SOCKET = 1 const SOL_SOCKET = 1
const SO_REUSEADDR = 2 const SO_REUSEADDR = 2
func net.listen[packed_host: i64, port: i64] : i64 func net.listen?[packed_host: i64, port: i64] : i64
err.clear()
let s: i64 = _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0) let s: i64 = _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0)
if s < 0 if s < 0
err.set(ERR_SYSCALL_FAILED, "net.listen?: failed to create a socket")
return -1 return -1
let optval = 1 let optval = 1
if _builtin_syscall(SYS_setsockopt, s, SOL_SOCKET, SO_REUSEADDR, ^optval, 8) < 0 if _builtin_syscall(SYS_setsockopt, s, SOL_SOCKET, SO_REUSEADDR, ^optval, 8) < 0
_builtin_syscall(SYS_close, s) _builtin_syscall(SYS_close, s)
err.set(ERR_SYSCALL_FAILED, "net.listen?: setsockopt() failed")
return -1 return -1
let sa: ptr = mem.alloc(16) let sa: ptr = mem.alloc(16)
@@ -28,19 +31,23 @@ func net.listen[packed_host: i64, port: i64] : i64
if _builtin_syscall(SYS_bind, s, sa, 16) < 0 if _builtin_syscall(SYS_bind, s, sa, 16) < 0
_builtin_syscall(SYS_close, s) _builtin_syscall(SYS_close, s)
mem.free(sa) mem.free(sa)
err.set(ERR_SYSCALL_FAILED, "net.listen?: failed to bind")
return -1 return -1
mem.free(sa) mem.free(sa)
if _builtin_syscall(SYS_listen, s, 128) < 0 if _builtin_syscall(SYS_listen, s, 128) < 0
_builtin_syscall(SYS_close, s) _builtin_syscall(SYS_close, s)
err.set(ERR_SYSCALL_FAILED, "net.listen?: listen() failed")
return -1 return -1
return s return s
// TODO: resolve DNS // TODO: resolve DNS
func net.connect[packed_host: i64, port: i64] : i64 func net.connect?[packed_host: i64, port: i64] : i64
err.clear()
let s: i64 = _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0) let s: i64 = _builtin_syscall(SYS_socket, AF_INET, SOCK_STREAM, 0)
if s < 0 if s < 0
err.set(ERR_SYSCALL_FAILED, "net.connect?: failed to create a socket")
return -1 return -1
let sa: ptr = mem.alloc(16) let sa: ptr = mem.alloc(16)
@@ -57,6 +64,7 @@ func net.connect[packed_host: i64, port: i64] : i64
if _builtin_syscall(SYS_connect, s, sa, 16) < 0 if _builtin_syscall(SYS_connect, s, sa, 16) < 0
mem.free(sa) mem.free(sa)
_builtin_syscall(SYS_close, s) _builtin_syscall(SYS_close, s)
err.set(ERR_CONNECTION_FAILED, "net.connect?: connection failed")
return -1 return -1
mem.free(sa) mem.free(sa)
@@ -86,12 +94,14 @@ struct net.UDPPacket
source_addr: ptr source_addr: ptr
size: i64 size: i64
func net.create_udp_client[packed_host: i64, port: i64] : net.UDPSocket func net.create_udp_client?[packed_host: i64, port: i64] : net.UDPSocket
err.clear()
let s: net.UDPSocket = new net.UDPSocket let s: net.UDPSocket = new net.UDPSocket
s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0) s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0)
if s->fd < 0 if s->fd < 0
mem.free(s) mem.free(s)
err.set(ERR_SYSCALL_FAILED, "net.create_udp_client?: failed to create a socket")
return 0 return 0
s->addr = mem.alloc(16) s->addr = mem.alloc(16)
@@ -106,13 +116,15 @@ func net.create_udp_client[packed_host: i64, port: i64] : net.UDPSocket
s->addr[7] = packed_host & 255 s->addr[7] = packed_host & 255
return s return s
func net.udp_listen[packed_host: i64, port: i64] : net.UDPSocket func net.udp_listen?[packed_host: i64, port: i64] : net.UDPSocket
let s: net.UDPSocket = net.create_udp_client(packed_host, port) err.clear()
if !s let s: net.UDPSocket = net.create_udp_client?(packed_host, port)
if err.check()
return 0 return 0
if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0 if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0
net.UDPSocket.close_and_free(s) net.UDPSocket.close_and_free(s)
err.set(ERR_SYSCALL_FAILED, "net.udp_listen?: failed to bind")
return 0 return 0
return s return s
@@ -146,7 +158,7 @@ const DNS_RECURSION_DESIRED = 256
func net.encode_dns_name[domain: str] : io.Buffer func net.encode_dns_name[domain: str] : io.Buffer
let domain_len: i64 = str.len(domain) let domain_len: i64 = str.len(domain)
let buf: io.Buffer = io.Buffer.alloc(domain_len + 2) let buf: io.Buffer = must(io.Buffer.alloc?(domain_len + 2))
let out_pos: i64 = 0 let out_pos: i64 = 0
let part_start: i64 = 0 let part_start: i64 = 0
let i: i64 = 0 let i: i64 = 0
@@ -165,7 +177,7 @@ func net.encode_dns_name[domain: str] : io.Buffer
func net.build_dns_query[domain_name: str, record_type: i64] : io.Buffer func net.build_dns_query[domain_name: str, record_type: i64] : io.Buffer
let name: io.Buffer = net.encode_dns_name(domain_name) let name: io.Buffer = net.encode_dns_name(domain_name)
let out: io.Buffer = io.Buffer.alloc(12 + name->size + 4) let out: io.Buffer = must(io.Buffer.alloc?(12 + name->size + 4))
// header // header
let id: i64 = math.abs(os.urandom_i64() % 65536) let id: i64 = math.abs(os.urandom_i64() % 65536)
@@ -184,13 +196,15 @@ func net.build_dns_query[domain_name: str, record_type: i64] : io.Buffer
io.Buffer.free(name) io.Buffer.free(name)
return out return out
func net.resolve[domain: str] : i64 // TODO: dont resolve IPs
func net.resolve?[domain: str] : i64
err.clear()
let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A) let query: io.Buffer = net.build_dns_query(domain, DNS_TYPE_A)
// TODO: this probably shouldnt be hardcoded // TODO: this probably shouldnt be hardcoded
let s: net.UDPSocket = net.create_udp_client(net.pack_addr(8, 8, 8, 8), 53) let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8, 8, 8, 8), 53)
if s == 0 if err.check()
return 0 return -1
net.udp_send_to(s, s->addr, query->data, query->size) net.udp_send_to(s, s->addr, query->data, query->size)
io.Buffer.free(query) io.Buffer.free(query)

View File

@@ -1,15 +1,46 @@
const ERR_NONE = 0
const ERR_SYSCALL_FAILED = 1
const ERR_OPEN_FAILED = 2
const ERR_READ_FAILED = 3
const ERR_WRITE_FAILED = 4
const ERR_ALLOC_FAILED = 5
const ERR_CONNECTION_FAILED = 6
func err.code[] : i64
return mem.read64(_builtin_err_code())
func err.msg[] : str
return mem.read64(_builtin_err_msg())
func err.check[] : bool
return err.code() != 0
func err.clear[] : void
err.set(0, 0)
func err.set[code: i64, msg: str] : void
mem.write64(_builtin_err_code(), code)
mem.write64(_builtin_err_msg(), msg)
func must[x: any] : any
if err.check()
panic(err.msg())
return x
func panic[msg: str] : void func panic[msg: str] : void
io.print("PANIC: ") io.print("PANIC: ")
io.println(msg) io.println(msg)
(0 / 0) // crashes program which is kinda better since you get a gdb backtrace // for gdb backtrace
_builtin_syscall(SYS_kill, os.getpid(), 6)
os.exit(1) os.exit(1)
const MEM_BLOCK_SIZE = 24 const MEM_BLOCK_SIZE = 32
struct mem.Block struct mem.Block
size: i64 size: i64
free: bool free: bool
next: mem.Block next: mem.Block
prev: mem.Block
func mem._align[x: i64] : i64 func mem._align[x: i64] : i64
return (x + 7) & -8 return (x + 7) & -8
@@ -20,14 +51,19 @@ func mem._split_block[blk: mem.Block, needed: i64] : void
new_blk->size = blk->size - needed - MEM_BLOCK_SIZE new_blk->size = blk->size - needed - MEM_BLOCK_SIZE
new_blk->free = true new_blk->free = true
new_blk->next = blk->next new_blk->next = blk->next
new_blk->prev = blk
if blk->next
let next: mem.Block = blk->next
next->prev = new_blk
else
mem.write64(_builtin_heap_tail(), new_blk)
blk->size = needed blk->size = needed
blk->next = new_blk blk->next = new_blk
if mem.read64(_builtin_heap_tail()) == blk func mem._request_space?[size: i64] : mem.Block
mem.write64(_builtin_heap_tail(), new_blk) err.clear()
func mem._request_space[size: i64] : mem.Block
let needed: i64 = size + MEM_BLOCK_SIZE let needed: i64 = size + MEM_BLOCK_SIZE
let alloc_size: i64 = (needed + 4095) & -4096 let alloc_size: i64 = (needed + 4095) & -4096
@@ -36,55 +72,26 @@ func mem._request_space[size: i64] : mem.Block
// fd = -1, offset = 0 // fd = -1, offset = 0
let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0)
if blk == -1 if blk == -1
err.set(ERR_ALLOC_FAILED, "mem._request_space: mmap failed")
return 0 return 0
blk->size = alloc_size - MEM_BLOCK_SIZE blk->size = alloc_size - MEM_BLOCK_SIZE
blk->free = false blk->free = false
blk->next = 0 blk->next = 0
blk->prev = 0
let tail: mem.Block = mem.read64(_builtin_heap_tail()) let tail: mem.Block = mem.read64(_builtin_heap_tail())
if tail if tail
tail->next = blk tail->next = blk
blk->prev = tail
mem.write64(_builtin_heap_tail(), blk) mem.write64(_builtin_heap_tail(), blk)
return blk return blk
func mem._coalesce[] : void func mem.alloc?[size: i64] : ptr
let cur: mem.Block = mem.read64(_builtin_heap_head()) err.clear()
let prev: mem.Block = 0 if size <= 0
err.set(ERR_ALLOC_FAILED, "mem.alloc? called with non-positive size")
while cur
let next: mem.Block = cur->next
let expected_next: mem.Block = cur + MEM_BLOCK_SIZE + cur->size
if cur->free && next && next->free && expected_next == next
cur->size = cur->size + MEM_BLOCK_SIZE + next->size
cur->next = next->next
if !cur->next
mem.write64(_builtin_heap_tail(), cur)
continue
let block_total: i64 = cur->size + MEM_BLOCK_SIZE
if cur->free && (cur & 4095) == 0 && (block_total & 4095) == 0
if prev
prev->next = cur->next
if !cur->next
mem.write64(_builtin_heap_tail(), prev)
else
mem.write64(_builtin_heap_head(), cur->next)
if !cur->next
mem.write64(_builtin_heap_tail(), 0)
let saved_next: mem.Block = cur->next
_builtin_syscall(11, cur, block_total)
cur = saved_next
continue
prev = cur
cur = cur->next
func mem.alloc[size: i64] : ptr
if size == 0
return 0 return 0
size = mem._align(size) size = mem._align(size)
@@ -97,8 +104,8 @@ func mem.alloc[size: i64] : ptr
return cur + MEM_BLOCK_SIZE return cur + MEM_BLOCK_SIZE
cur = cur->next cur = cur->next
let blk: mem.Block = mem._request_space(size) let blk: mem.Block = mem._request_space?(size)
if !blk if err.check()
return 0 return 0
if !mem.read64(_builtin_heap_head()) if !mem.read64(_builtin_heap_head())
@@ -108,17 +115,63 @@ func mem.alloc[size: i64] : ptr
return blk + MEM_BLOCK_SIZE return blk + MEM_BLOCK_SIZE
func mem.alloc[size: i64] : ptr
return must(mem.alloc?(size))
func mem.free[x: ptr] : void func mem.free[x: ptr] : void
if !x if !x
return 0 return 0
let blk: mem.Block = x - MEM_BLOCK_SIZE let blk: mem.Block = x - MEM_BLOCK_SIZE
blk->free = true blk->free = true
mem._coalesce()
func mem.realloc[x: ptr, new_size: i64] : ptr let next: mem.Block = blk->next
if next && next->free
let expected_next: mem.Block = blk + MEM_BLOCK_SIZE + blk->size
if expected_next == next
blk->size = blk->size + MEM_BLOCK_SIZE + next->size
blk->next = next->next
if next->next
let b: mem.Block = next->next
b->prev = blk
if mem.read64(_builtin_heap_tail()) == next
mem.write64(_builtin_heap_tail(), blk)
let prev: mem.Block = blk->prev
if prev && prev->free
let expected_blk: mem.Block = prev + MEM_BLOCK_SIZE + prev->size
if expected_blk == blk
prev->size = prev->size + MEM_BLOCK_SIZE + blk->size
prev->next = blk->next
if blk->next
let b: mem.Block = blk->next
b->prev = prev
if mem.read64(_builtin_heap_tail()) == blk
mem.write64(_builtin_heap_tail(), prev)
blk = prev
let block_total: i64 = blk->size + MEM_BLOCK_SIZE
if (blk & 4095) == 0 && (block_total & 4095) == 0
if blk->prev
let b: mem.Block = blk->prev
b->next = blk->next
else
mem.write64(_builtin_heap_head(), blk->next)
if blk->next
let b: mem.Block = blk->next
b->prev = blk->prev
else
mem.write64(_builtin_heap_tail(), blk->prev)
_builtin_syscall(SYS_munmap, blk, block_total)
func mem.realloc?[x: ptr, new_size: i64] : ptr
err.clear()
if !x if !x
return mem.alloc(new_size) return mem.alloc?(new_size)
if new_size < 0
err.set(ERR_ALLOC_FAILED, "mem.realloc? called with negative size")
return 0
if new_size == 0 if new_size == 0
mem.free(x) mem.free(x)
@@ -139,17 +192,19 @@ func mem.realloc[x: ptr, new_size: i64] : ptr
if combined >= new_size if combined >= new_size
blk->size = combined blk->size = combined
blk->next = next->next blk->next = next->next
if !blk->next if next->next
let b: mem.Block = next->next
b->prev = blk
if mem.read64(_builtin_heap_tail()) == next
mem.write64(_builtin_heap_tail(), blk) mem.write64(_builtin_heap_tail(), blk)
mem._split_block(blk, new_size) mem._split_block(blk, new_size)
return x return x
let new_ptr: ptr = mem.alloc(new_size) let new_ptr: ptr = mem.alloc?(new_size)
if !new_ptr if err.check()
return 0 return 0
mem.copy(x, new_ptr, math.min(blk->size, new_size)) mem.copy(x, new_ptr, math.min(blk->size, new_size))
mem.free(x) mem.free(x)
return new_ptr return new_ptr
@@ -162,8 +217,14 @@ func mem.zero[x: ptr, size: i64] : void
x[i] = 0 x[i] = 0
func mem.copy[src: ptr, dst: ptr, n: i64] : void func mem.copy[src: ptr, dst: ptr, n: i64] : void
for i in 0..n if dst > src
dst[i] = src[i] let i: i64 = n - 1
while i >= 0
dst[i] = src[i]
i = i - 1
else
for i in 0..n
dst[i] = src[i]
func mem.read8[x: ptr] : u8 func mem.read8[x: ptr] : u8
return x[0] return x[0]
@@ -270,71 +331,100 @@ func io.read_line[]: str
let buffer: str = mem.alloc(MAX_SIZE + 1) let buffer: str = mem.alloc(MAX_SIZE + 1)
let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE) let n: i64 = _builtin_syscall(SYS_read, 0, buffer, MAX_SIZE)
if n < 0 if n < 0
mem.free(buffer) n = 0
return ""
buffer[n] = 0
return buffer
func io.file_exists[path: str] : bool
return _builtin_syscall(SYS_faccessat, -100, path, 0, 0) == 0
func io.read_file[path: str] : str
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd <= 0
panic("failed to open file") // TODO
let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0)
let buffer: str = mem.alloc(size + 1)
let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size)
_builtin_syscall(SYS_close, fd)
buffer[n] = 0 buffer[n] = 0
buffer = must(mem.realloc?(buffer, n + 1))
return buffer return buffer
struct io.Buffer struct io.Buffer
data: ptr data: ptr
size: i64 size: i64
func io.Buffer.alloc[size: i64] : io.Buffer func io.Buffer.alloc?[size: i64] : io.Buffer
err.clear()
let buffer: io.Buffer = new io.Buffer let buffer: io.Buffer = new io.Buffer
buffer->size = size buffer->size = size
buffer->data = mem.alloc(size) buffer->data = mem.alloc?(size)
if err.check()
mem.free(buffer)
return 0
return buffer return buffer
func io.Buffer.free[buf: io.Buffer] : void func io.Buffer.free[buf: io.Buffer] : void
mem.free(buf->data) mem.free(buf->data)
mem.free(buf) mem.free(buf)
func io.read_binary_file[path: str] : io.Buffer func io.file_exists[path: str] : bool
return _builtin_syscall(SYS_faccessat, -100, path, 0, 0) == 0
func io.read_text_file?[path: str] : str
err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0 if fd < 0
panic("failed to open file") // TODO err.set(ERR_OPEN_FAILED, "io.read_text_file?: failed to open file")
return 0
let buffer: io.Buffer = new io.Buffer let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2)
buffer->size = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0) _builtin_syscall(SYS_lseek, fd, 0, 0)
buffer->data = mem.alloc(buffer->size) let buffer: str = mem.alloc?(size + 1)
_builtin_syscall(SYS_read, fd, buffer->data, buffer->size) if err.check()
_builtin_syscall(SYS_close, fd)
return 0
let n: i64 = _builtin_syscall(SYS_read, fd, buffer, size)
_builtin_syscall(SYS_close, fd) _builtin_syscall(SYS_close, fd)
if n != size
mem.free(buffer)
err.set(ERR_READ_FAILED, "io.read_text_file?: failed to read file")
return 0
buffer[n] = 0
return buffer return buffer
func io.write_file[path: str, content: str] : void func io.read_binary_file?[path: str] : io.Buffer
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644) err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0 if fd < 0
panic("failed to open file") // TODO err.set(ERR_OPEN_FAILED, "io.read_binary_file?: failed to open file")
return 0
_builtin_syscall(SYS_write, fd, content, str.len(content)) let buf: io.Buffer = new io.Buffer
buf->size = _builtin_syscall(SYS_lseek, fd, 0, 2)
_builtin_syscall(SYS_lseek, fd, 0, 0)
buf->data = mem.alloc?(buf->size)
if err.check()
io.Buffer.free(buf)
_builtin_syscall(SYS_close, fd)
return 0
let n: i64 = _builtin_syscall(SYS_read, fd, buf->data, buf->size)
_builtin_syscall(SYS_close, fd) _builtin_syscall(SYS_close, fd)
func io.write_binary_file[path: str, content: ptr, size: i64] : void if n != buf->size
io.Buffer.free(buf)
err.set(ERR_READ_FAILED, "io.read_binary_file?: failed to read file")
return 0
return buf
func io.write_file?[path: str, content: str] : void
io.write_binary_file?(path, content, str.len(content))
func io.write_binary_file?[path: str, content: ptr, size: i64] : void
err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644) let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644)
if fd < 0 if fd < 0
panic("failed to open file") // TODO err.set(ERR_OPEN_FAILED, "io.write_binary_file?: failed to open file")
return 0
_builtin_syscall(SYS_write, fd, content, size) let n: i64 = _builtin_syscall(SYS_write, fd, content, size)
_builtin_syscall(SYS_close, fd) _builtin_syscall(SYS_close, fd)
if n != size
err.set(ERR_WRITE_FAILED, "io.write_binary_file?: failed to write file")
return 0
func str.len[s: str] : i64 func str.len[s: str] : i64
let i = 0 let i = 0
@@ -396,6 +486,9 @@ func str.find[haystack: str, needle: str] : i64
if needle_len == 0 if needle_len == 0
return 0 return 0
if needle_len > haystack_len
return -1
for i in 0..(haystack_len - needle_len + 1) for i in 0..(haystack_len - needle_len + 1)
let match: bool = true let match: bool = true
for j in 0..needle_len for j in 0..needle_len
@@ -418,7 +511,9 @@ func str.substr[s: str, start: i64, length: i64] : str
func str.trim[s: str] : str func str.trim[s: str] : str
let len: i64 = str.len(s) let len: i64 = str.len(s)
if len == 0 if len == 0
return "" let out: str = mem.alloc(1)
out[0] = 0
return out
let start = 0 let start = 0
let end: i64 = len - 1 let end: i64 = len - 1
@@ -472,7 +567,6 @@ func str.reverse[s: str] : str
out[len] = 0 out[len] = 0
return out return out
// not sure this covers all the wacky edge cases
func str.from_i64[n: i64] : str func str.from_i64[n: i64] : str
if n == 0 if n == 0
let out: str = mem.alloc(2) let out: str = mem.alloc(2)
@@ -482,19 +576,22 @@ func str.from_i64[n: i64] : str
let neg: bool = n < 0 let neg: bool = n < 0
if neg if neg
// negating MIN_I64 causes overflow
if n == -9223372036854775808
return str.make_copy("-9223372036854775808")
n = -n n = -n
let buf: str = mem.alloc(21) // enough to fit -MAX_I64 let buf: str = mem.alloc(21) // enough to fit -MAX_I64
let i = 0 let end = 20
buf[end] = 0
end = end - 1
while n > 0 while n > 0
let d: u8 = n % 10 buf[end] = '0' + (n % 10)
buf[i] = '0' + d
n = n / 10 n = n / 10
i = i + 1 end = end - 1
if neg if neg
buf[i] = '-' buf[end] = '-'
i = i + 1 end = end - 1
buf[i] = 0 let s: str = str.make_copy(buf + end + 1)
let s: str = str.reverse(buf)
mem.free(buf) mem.free(buf)
return s return s
@@ -507,12 +604,13 @@ func str.hex_from_i64[n: i64] : str
out[1] = 0 out[1] = 0
return out return out
let mask: i64 = (1 << 60) - 1
let buf: str = mem.alloc(17) let buf: str = mem.alloc(17)
let len = 0 let len = 0
while n > 0 while n != 0
buf[len] = hex_chars[n & 15] buf[len] = hex_chars[n & 15]
n = n >> 4 n = (n >> 4) & mask
len = len + 1 len = len + 1
let out: str = mem.alloc(len + 1) let out: str = mem.alloc(len + 1)
@@ -522,6 +620,7 @@ func str.hex_from_i64[n: i64] : str
j = j + 1 j = j + 1
out[len] = 0 out[len] = 0
mem.free(buf)
return out return out
func str.from_char[c: u8] : str func str.from_char[c: u8] : str
@@ -572,6 +671,9 @@ func str._hex_digit_to_int[d: u8] : i64
func str.hex_decode[s: str] : str func str.hex_decode[s: str] : str
let s_len: i64 = str.len(s) let s_len: i64 = str.len(s)
if s_len % 2 != 0
panic("invalid hex string passed to str.hex_decode")
let i = 0 let i = 0
let j = 0 let j = 0
let out: str = mem.alloc(s_len / 2 + 1) let out: str = mem.alloc(s_len / 2 + 1)
@@ -585,6 +687,8 @@ func str.hex_decode[s: str] : str
return out return out
func math.gcd[a: i64, b: i64] : i64 func math.gcd[a: i64, b: i64] : i64
a = math.abs(a)
b = math.abs(b)
while b != 0 while b != 0
let tmp: i64 = b let tmp: i64 = b
b = a % b b = a % b
@@ -602,6 +706,8 @@ func math.max[a: i64, b: i64] : i64
return b return b
func math.abs[n: i64] : i64 func math.abs[n: i64] : i64
if n == -9223372036854775808
panic("MIN_I64 passed to math.abs")
if n < 0 if n < 0
return -n return -n
return n return n
@@ -615,13 +721,15 @@ func math.sign[n: i64] : i64
return 0 return 0
func math.pow[b: i64, e: i64] : i64 func math.pow[b: i64, e: i64] : i64
if e < 0
panic("negative exponent passed to math.pow")
let out = 1 let out = 1
for i in 0..e for i in 0..e
out = out * b out = out * b
return out return out
func math.lcm[a: i64, b: i64] : i64 func math.lcm[a: i64, b: i64] : i64
return (a * b) / math.gcd(a, b) return (a / math.gcd(a, b)) * b
func math.isqrt[n: i64] : i64 func math.isqrt[n: i64] : i64
if n < 0 if n < 0
@@ -661,6 +769,15 @@ struct Array
func array.new[] : Array func array.new[] : Array
return new Array return new Array
func array.new_with_size_zeroed[size: i64] : Array
let xs: Array = new Array
if size > 0
xs->data = must(mem.realloc?(xs->data, size * 8))
mem.zero(xs->data, size * 8)
xs->size = size
xs->capacity = size
return xs
func array.nth[xs: Array, n: i64] : any func array.nth[xs: Array, n: i64] : any
if n < 0 || n >= xs->size if n < 0 || n >= xs->size
panic("array.nth out of bounds") panic("array.nth out of bounds")
@@ -676,7 +793,7 @@ func array.push[xs: Array, x: any] : void
let new_capacity = 4 let new_capacity = 4
if xs->capacity != 0 if xs->capacity != 0
new_capacity = xs->capacity * 2 new_capacity = xs->capacity * 2
xs->data = mem.realloc(xs->data, new_capacity * 8) xs->data = must(mem.realloc?(xs->data, new_capacity * 8))
xs->capacity = new_capacity xs->capacity = new_capacity
mem.write64(xs->data + xs->size * 8, x) mem.write64(xs->data + xs->size * 8, x)
@@ -698,17 +815,17 @@ func array.slice[xs: Array, start: i64, length: i64] : Array
if start < 0 || length < 0 || start + length > xs->size if start < 0 || length < 0 || start + length > xs->size
panic("array.slice out of bounds") panic("array.slice out of bounds")
let new_array: Array = [] let new_array: Array = array.new_with_size_zeroed(length)
for i in 0..length for i in 0..length
array.push(new_array, array.nth(xs, start + i)) array.set(new_array, i, array.nth(xs, start + i))
return new_array return new_array
func array.concat[a: Array, b: Array] : Array func array.concat[a: Array, b: Array] : Array
let new_array: Array = [] let new_array: Array = array.new_with_size_zeroed(a->size + b->size)
for i in 0..a->size for i in 0..a->size
array.push(new_array, array.nth(a, i)) array.set(new_array, i, array.nth(a, i))
for i in 0..b->size for i in 0..b->size
array.push(new_array, array.nth(b, i)) array.set(new_array, a->size + i, array.nth(b, i))
return new_array return new_array
func alg.quicksort[arr: Array] : void func alg.quicksort[arr: Array] : void
@@ -742,9 +859,9 @@ func alg.count[arr: Array, item: any] : i64
return count return count
func alg.map[arr: Array, fn: ptr] : Array func alg.map[arr: Array, fn: ptr] : Array
let out: Array = [] let out: Array = array.new_with_size_zeroed(arr->size)
for i in 0..arr->size for i in 0..arr->size
array.push(out, fn(array.nth(arr, i))) array.set(out, i, fn(array.nth(arr, i)))
return out return out
func alg.filter[arr: Array, fn: ptr] : Array func alg.filter[arr: Array, fn: ptr] : Array
@@ -765,26 +882,44 @@ func os.exit[code: i64] : void
func os.getpid[] : i64 func os.getpid[] : i64
return _builtin_syscall(SYS_getpid) return _builtin_syscall(SYS_getpid)
struct os.Timespec
tv_sec: i64
tv_nsec: i64
func os.sleep[ms: i64] : void func os.sleep[ms: i64] : void
if ms < 0 if ms < 0
panic("negative time passed to os.sleep") panic("negative time passed to os.sleep")
// TODO: we really shouldnt need a heap allocation to sleep // TODO: we really shouldnt need a heap allocation to sleep
let req: os.Timeval = new os.Timeval let req: os.Timespec = new os.Timespec
req->tv_sec = ms / 1000 req->tv_sec = ms / 1000
// yes, we should have a os.Timespec with tv_nsec but this works for now req->tv_nsec = (ms % 1000) * 1000000
req->tv_usec = (ms % 1000) * 1000000
_builtin_syscall(SYS_nanosleep, req, 0) _builtin_syscall(SYS_nanosleep, req, 0)
mem.free(req) mem.free(req)
func os.urandom[n: i64]: ptr func os.urandom?[n: i64]: ptr
err.clear()
let buffer: ptr = mem.alloc(n) let buffer: ptr = mem.alloc(n)
if err.check()
return 0
let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0)
_builtin_syscall(SYS_read, fd, buffer, n) if fd < 0
mem.free(buffer)
err.set(ERR_READ_FAILED, "os.urandom?: failed to open /dev/urandom")
return 0
let bytes_read: i64 = _builtin_syscall(SYS_read, fd, buffer, n)
_builtin_syscall(SYS_close, fd) _builtin_syscall(SYS_close, fd)
if n != bytes_read
mem.free(buffer)
err.set(ERR_READ_FAILED, "os.urandom?: failed to read /dev/urandom")
return 0
return buffer return buffer
func os.urandom_i64[]: i64 func os.urandom_i64[]: i64
let buffer: ptr = os.urandom(8) let buffer: ptr = must(os.urandom?(8))
let n: i64 = mem.read64(buffer) let n: i64 = mem.read64(buffer)
mem.free(buffer) mem.free(buffer)
return n return n
@@ -802,8 +937,13 @@ func os.time[] : i64
return out return out
// voodoo magic // voodoo magic
func os.run_shell_command[command: str] : i64 func os.run_shell_command?[command: str] : i64
err.clear()
let pid: i64 = _builtin_syscall(SYS_fork) let pid: i64 = _builtin_syscall(SYS_fork)
if pid < 0
err.set(ERR_SYSCALL_FAILED, "os.run_shell_command?: failed to fork")
return -1
if pid == 0 if pid == 0
let argv: Array = ["sh", "-c", command, 0] // gets freed after child process exits let argv: Array = ["sh", "-c", command, 0] // gets freed after child process exits
_builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ()) _builtin_syscall(SYS_execve, "/bin/sh", argv->data, _builtin_environ())
@@ -811,7 +951,8 @@ func os.run_shell_command[command: str] : i64
else else
let status = 0 let status = 0
let wp: i64 = _builtin_syscall(SYS_wait4, pid, ^status, 0, 0) let wp: i64 = _builtin_syscall(SYS_wait4, pid, ^status, 0, 0)
if wp == -1 if wp < 0
err.set(ERR_SYSCALL_FAILED, "os.run_shell_command?: wait() failed")
return -1 return -1
let st: i64 = status & 0xffffffff let st: i64 = status & 0xffffffff
@@ -820,16 +961,28 @@ func os.run_shell_command[command: str] : i64
else else
return -(st & 0x7f) return -(st & 0x7f)
func os.list_directory[path: str] : Array func os.list_directory?[path: str] : Array
err.clear()
let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0)
if fd < 0 if fd < 0
return [] err.set(ERR_OPEN_FAILED, "os.list_directory?: failed to open dir")
return 0
let files: Array = [] let files: Array = []
let buf: ptr = mem.alloc(1024) let buf: ptr = mem.alloc(1024)
while true while true
let n: i64 = _builtin_syscall(SYS_getdents64, fd, buf, 1024) let n: i64 = _builtin_syscall(SYS_getdents64, fd, buf, 1024)
if n <= 0 if n < 0
mem.free(buf)
for i in 0..files->size
mem.free(array.nth(files, i))
array.free(files)
_builtin_syscall(SYS_close, fd)
err.set(ERR_SYSCALL_FAILED, "os.list_directory?: getdents64() failed")
return 0
else if n == 0
break break
let pos = 0 let pos = 0

View File

@@ -346,7 +346,11 @@ impl Tokenizer {
} }
fn scan_identifier(&mut self) -> Result<(), ZernError> { fn scan_identifier(&mut self) -> Result<(), ZernError> {
while self.peek().is_alphanumeric() || self.peek() == '_' || self.peek() == '.' { while self.peek().is_alphanumeric()
|| self.peek() == '_'
|| self.peek() == '.'
|| self.peek() == '?'
{
self.advance(); self.advance();
} }

10
test.zr
View File

@@ -1,6 +1,6 @@
func run_test[x: str] : void func run_test[x: str] : void
let build_blacklist: Array = ["examples/puzzles", "examples/raylib.zr", "examples/chip8.zr", "examples/sqlite_todo.zr"] let build_blacklist: Array = ["examples/puzzles", "examples/raylib.zr", "examples/chip8.zr", "examples/sqlite_todo.zr"]
let run_blacklist: Array = ["/aoc", "guess_number.zr", "tcp_server.zr"] let run_blacklist: Array = ["/aoc", "guess_number.zr", "tcp_server.zr", "udp_server.zr"]
for i in 0..build_blacklist->size for i in 0..build_blacklist->size
if str.equal(x, array.nth(build_blacklist, i)) if str.equal(x, array.nth(build_blacklist, i))
@@ -14,7 +14,7 @@ func run_test[x: str] : void
io.print("... ") io.print("... ")
let build_start_time: i64 = os.time() let build_start_time: i64 = os.time()
if os.run_shell_command(str.concat("./target/release/zern ", x)) != 0 if must(os.run_shell_command?(str.concat("./target/release/zern ", x))) != 0
os.exit(1) os.exit(1)
let build_end_time: i64 = os.time() let build_end_time: i64 = os.time()
@@ -39,7 +39,7 @@ func run_test[x: str] : void
run_cmd = str.concat(run_cmd, " test.zr") run_cmd = str.concat(run_cmd, " test.zr")
let run_start_time: i64 = os.time() let run_start_time: i64 = os.time()
if os.run_shell_command(run_cmd) != 0 if must(os.run_shell_command?(run_cmd)) != 0
os.exit(1) os.exit(1)
let run_end_time: i64 = os.time() let run_end_time: i64 = os.time()
@@ -50,14 +50,14 @@ func run_test[x: str] : void
io.println("ms") io.println("ms")
func run_directory[dir: str] : void func run_directory[dir: str] : void
let files: Array = os.list_directory(dir) let files: Array = must(os.list_directory?(dir))
for i in 0..files->size for i in 0..files->size
run_test(str.concat(dir, array.nth(files, i))) run_test(str.concat(dir, array.nth(files, i)))
array.free(files) array.free(files)
func main[] : i64 func main[] : i64
os.run_shell_command("cargo build --release") must(os.run_shell_command?("cargo build --release"))
run_directory("examples/") run_directory("examples/")
run_directory("examples/puzzles/") run_directory("examples/puzzles/")