From 720218cf31caec709832566ebff096980227c63a Mon Sep 17 00:00:00 2001 From: Toni Date: Mon, 16 Mar 2026 10:59:55 +0100 Subject: [PATCH] error handling --- examples/curl.zr | 10 +-- examples/puzzles/aoc2024_01.zr | 2 +- examples/puzzles/aoc2024_02.zr | 2 +- examples/puzzles/aoc2024_04.zr | 2 +- examples/puzzles/aoc2024_05.zr | 2 +- examples/puzzles/aoc2024_07.zr | 2 +- examples/puzzles/aoc2025_01.zr | 2 +- examples/puzzles/aoc2025_02.zr | 2 +- examples/puzzles/aoc2025_03.zr | 2 +- examples/tcp_server.zr | 4 +- examples/udp_server.zr | 4 +- src/analyzer.rs | 2 + src/codegen_x86_64.rs | 12 ++++ src/std/net.zr | 34 ++++++++--- src/std/std.zr | 108 +++++++++++++++++++++++++-------- src/tokenizer.rs | 6 +- 16 files changed, 138 insertions(+), 58 deletions(-) diff --git a/examples/curl.zr b/examples/curl.zr index 05c54a3..6034ddf 100644 --- a/examples/curl.zr +++ b/examples/curl.zr @@ -26,15 +26,9 @@ func main[argc: i64, argv: ptr] : i64 if i < url_len path = str.substr(url, i, url_len - i) - let addr: i64 = net.resolve(host) - if addr <= 0 - io.println("ERROR: domain resolution failed") - return 1 + let addr: i64 = must(net.resolve?(host)) - let s: i64 = net.connect(addr, 80) - if s < 0 - io.println("ERROR: connection failed") - return 1 + let s: i64 = must(net.connect?(addr, 80)) // TODO: add string builder to std let req: str = "GET " diff --git a/examples/puzzles/aoc2024_01.zr b/examples/puzzles/aoc2024_01.zr index 476e43f..6196da2 100644 --- a/examples/puzzles/aoc2024_01.zr +++ b/examples/puzzles/aoc2024_01.zr @@ -15,7 +15,7 @@ func part2[l1: Array, l2: Array] : void io.println_i64(out) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") let l1: Array = [] let l2: Array = [] diff --git a/examples/puzzles/aoc2024_02.zr b/examples/puzzles/aoc2024_02.zr index 4452167..b438371 100644 --- a/examples/puzzles/aoc2024_02.zr +++ b/examples/puzzles/aoc2024_02.zr @@ -40,7 +40,7 @@ func part2[data: Array] : void io.println_i64(out) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") let data: Array = [] for i in 0..lines->size diff --git a/examples/puzzles/aoc2024_04.zr b/examples/puzzles/aoc2024_04.zr index d4b73c6..d3edcfa 100644 --- a/examples/puzzles/aoc2024_04.zr +++ b/examples/puzzles/aoc2024_04.zr @@ -56,7 +56,7 @@ func part2[lines: Array] : void io.println_i64(out) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") part1(lines) part2(lines) diff --git a/examples/puzzles/aoc2024_05.zr b/examples/puzzles/aoc2024_05.zr index dd649cd..22855ab 100644 --- a/examples/puzzles/aoc2024_05.zr +++ b/examples/puzzles/aoc2024_05.zr @@ -47,7 +47,7 @@ func part2[updates: Array, rules_left: Array, rules_right: Array] : void io.println_i64(out) func main[] : i64 - let data: Array = io.read_file("input.txt") |> str.split("\n\n") + let data: Array = must(io.read_file?("input.txt")) |> str.split("\n\n") let rules_left: Array = [] let rules_right: Array = [] diff --git a/examples/puzzles/aoc2024_07.zr b/examples/puzzles/aoc2024_07.zr index 620b9ff..da0538b 100644 --- a/examples/puzzles/aoc2024_07.zr +++ b/examples/puzzles/aoc2024_07.zr @@ -61,7 +61,7 @@ func part2[equations: Array] : void io.println_i64(out) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") let equations: Array = [] for i in 0..lines->size diff --git a/examples/puzzles/aoc2025_01.zr b/examples/puzzles/aoc2025_01.zr index 5c76760..ca8b257 100644 --- a/examples/puzzles/aoc2025_01.zr +++ b/examples/puzzles/aoc2025_01.zr @@ -47,7 +47,7 @@ func part2[lines: Array] : void io.println_i64(password) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") part1(lines) part2(lines) diff --git a/examples/puzzles/aoc2025_02.zr b/examples/puzzles/aoc2025_02.zr index 07a4643..85e519d 100644 --- a/examples/puzzles/aoc2025_02.zr +++ b/examples/puzzles/aoc2025_02.zr @@ -54,7 +54,7 @@ func part2[ranges: Array] : void io.println_i64(sum) func main[] : i64 - let ranges: Array = io.read_file("input.txt") |> str.split(",") + let ranges: Array = must(io.read_file?("input.txt")) |> str.split(",") part1(ranges) part2(ranges) diff --git a/examples/puzzles/aoc2025_03.zr b/examples/puzzles/aoc2025_03.zr index fe16acf..1dab04c 100644 --- a/examples/puzzles/aoc2025_03.zr +++ b/examples/puzzles/aoc2025_03.zr @@ -45,7 +45,7 @@ func part2[lines: Array] : void io.println_i64(sum) func main[] : i64 - let lines: Array = io.read_file("input.txt") |> str.split("\n") + let lines: Array = must(io.read_file?("input.txt")) |> str.split("\n") part1(lines) part2(lines) diff --git a/examples/tcp_server.zr b/examples/tcp_server.zr index 8cb3b65..de94fb7 100644 --- a/examples/tcp_server.zr +++ b/examples/tcp_server.zr @@ -1,7 +1,5 @@ func main[] : i64 - let s: i64 = net.listen(net.pack_addr(127, 0, 0, 1), 8000) - if s < 0 - panic("failed to bind a socket") + let s: i64 = must(net.listen?(net.pack_addr(127, 0, 0, 1), 8000)) io.println("Listening on port 8000...") let resp: str = mem.alloc(60000) diff --git a/examples/udp_server.zr b/examples/udp_server.zr index 3b75702..5f2f345 100644 --- a/examples/udp_server.zr +++ b/examples/udp_server.zr @@ -1,7 +1,5 @@ func main[] : i64 - let s: i64 = net.udp_listen(net.pack_addr(127, 0, 0, 1), 8000) - if !s - panic("failed to bind a socket") + let s: i64 = must(net.udp_listen?(net.pack_addr(127, 0, 0, 1), 8000)) io.println("Listening on port 8000...") while true diff --git a/src/analyzer.rs b/src/analyzer.rs index b59d5c3..2abb0e7 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -17,6 +17,8 @@ impl Analyzer { functions: HashMap::from([ ("_builtin_heap_head".into(), 0), ("_builtin_heap_tail".into(), 0), + ("_builtin_err_code".into(), 0), + ("_builtin_err_msg".into(), 0), ("_builtin_read64".into(), 1), ("_builtin_set64".into(), 2), ("_builtin_syscall".into(), -1), diff --git a/src/codegen_x86_64.rs b/src/codegen_x86_64.rs index f80af6e..c0736a7 100644 --- a/src/codegen_x86_64.rs +++ b/src/codegen_x86_64.rs @@ -110,6 +110,8 @@ section .bss _heap_head: resq 1 _heap_tail: resq 1 _environ: resq 1 + _err_code: resq 1 + _err_msg: resq 1 section .text._builtin_heap_head _builtin_heap_head: @@ -121,6 +123,16 @@ _builtin_heap_tail: lea rax, [rel _heap_tail] 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 _builtin_read64: mov rax, qword [rdi] diff --git a/src/std/net.zr b/src/std/net.zr index 2ca9f88..a5c57b0 100644 --- a/src/std/net.zr +++ b/src/std/net.zr @@ -4,14 +4,17 @@ const SOCK_DGRAM = 2 const SOL_SOCKET = 1 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) if s < 0 + err.set(ERR_SYSCALL_FAILED, "net.listen?: failed to create a socket") return -1 let optval = 1 if _builtin_syscall(SYS_setsockopt, s, SOL_SOCKET, SO_REUSEADDR, ^optval, 8) < 0 _builtin_syscall(SYS_close, s) + err.set(ERR_SYSCALL_FAILED, "net.listen?: setsockopt() failed") return -1 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 _builtin_syscall(SYS_close, s) mem.free(sa) + err.set(ERR_SYSCALL_FAILED, "net.listen?: failed to bind") return -1 mem.free(sa) if _builtin_syscall(SYS_listen, s, 128) < 0 _builtin_syscall(SYS_close, s) + err.set(ERR_SYSCALL_FAILED, "net.listen?: listen() failed") return -1 return s // 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) if s < 0 + err.set(ERR_SYSCALL_FAILED, "net.connect?: failed to create a socket") return -1 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 mem.free(sa) _builtin_syscall(SYS_close, s) + err.set(ERR_CONNECTION_FAILED, "net.connect?: connection failed") return -1 mem.free(sa) @@ -86,12 +94,14 @@ struct net.UDPPacket source_addr: ptr 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 s->fd = _builtin_syscall(SYS_socket, AF_INET, SOCK_DGRAM, 0) if s->fd < 0 mem.free(s) + err.set(ERR_SYSCALL_FAILED, "net.create_udp_client?: failed to create a socket") return 0 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 return s -func net.udp_listen[packed_host: i64, port: i64] : net.UDPSocket - let s: net.UDPSocket = net.create_udp_client(packed_host, port) - if !s +func net.udp_listen?[packed_host: i64, port: i64] : net.UDPSocket + err.clear() + let s: net.UDPSocket = net.create_udp_client?(packed_host, port) + if err.check() return 0 if _builtin_syscall(SYS_bind, s->fd, s->addr, 16) < 0 net.UDPSocket.close_and_free(s) + err.set(ERR_SYSCALL_FAILED, "net.udp_listen?: failed to bind") return 0 return s @@ -184,13 +196,15 @@ func net.build_dns_query[domain_name: str, record_type: i64] : io.Buffer io.Buffer.free(name) 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) // TODO: this probably shouldnt be hardcoded - let s: net.UDPSocket = net.create_udp_client(net.pack_addr(8, 8, 8, 8), 53) - if s == 0 - return 0 + let s: net.UDPSocket = net.create_udp_client?(net.pack_addr(8, 8, 8, 8), 53) + if err.check() + return -1 net.udp_send_to(s, s->addr, query->data, query->size) io.Buffer.free(query) diff --git a/src/std/std.zr b/src/std/std.zr index df6ee96..084930c 100644 --- a/src/std/std.zr +++ b/src/std/std.zr @@ -1,7 +1,37 @@ +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 io.print("PANIC: ") 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) const MEM_BLOCK_SIZE = 32 @@ -32,7 +62,8 @@ func mem._split_block[blk: mem.Block, needed: i64] : void blk->size = needed blk->next = new_blk -func mem._request_space[size: i64] : mem.Block +func mem._request_space?[size: i64] : mem.Block + err.clear() let needed: i64 = size + MEM_BLOCK_SIZE let alloc_size: i64 = (needed + 4095) & -4096 @@ -41,6 +72,7 @@ func mem._request_space[size: i64] : mem.Block // fd = -1, offset = 0 let blk: mem.Block = _builtin_syscall(SYS_mmap, 0, alloc_size, 3, 34, -1, 0) if blk == -1 + err.set(ERR_ALLOC_FAILED, "mem._request_space: mmap failed") return 0 blk->size = alloc_size - MEM_BLOCK_SIZE @@ -56,7 +88,8 @@ func mem._request_space[size: i64] : mem.Block mem.write64(_builtin_heap_tail(), blk) return blk -func mem.alloc[size: i64] : ptr +func mem.alloc?[size: i64] : ptr + err.clear() if size == 0 return 0 @@ -70,8 +103,8 @@ func mem.alloc[size: i64] : ptr return cur + MEM_BLOCK_SIZE cur = cur->next - let blk: mem.Block = mem._request_space(size) - if !blk + let blk: mem.Block = mem._request_space?(size) + if err.check() return 0 if !mem.read64(_builtin_heap_head()) @@ -81,6 +114,9 @@ func mem.alloc[size: i64] : ptr return blk + MEM_BLOCK_SIZE +func mem.alloc[size: i64] : ptr + return must(mem.alloc?(size)) + func mem.free[x: ptr] : void if !x return 0 @@ -127,9 +163,10 @@ func mem.free[x: ptr] : void mem.write64(_builtin_heap_tail(), blk->prev) _builtin_syscall(SYS_munmap, blk, block_total) -func mem.realloc[x: ptr, new_size: i64] : ptr +func mem.realloc?[x: ptr, new_size: i64] : ptr + err.clear() if !x - return mem.alloc(new_size) + return mem.alloc?(new_size) if new_size == 0 mem.free(x) @@ -159,11 +196,10 @@ func mem.realloc[x: ptr, new_size: i64] : ptr return x let new_ptr: ptr = mem.alloc(new_size) - if !new_ptr + if err.check() return 0 mem.copy(x, new_ptr, math.min(blk->size, new_size)) - mem.free(x) return new_ptr @@ -306,10 +342,12 @@ func io.Buffer.free[buf: io.Buffer] : void func io.file_exists[path: str] : bool return _builtin_syscall(SYS_faccessat, -100, path, 0, 0) == 0 -func io.read_file[path: str] : str +func io.read_file?[path: str] : str + err.clear() let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 0) if fd < 0 - panic("failed to open file") // TODO + err.set(ERR_OPEN_FAILED, "io.read_file?: failed to open file") + return 0 let size: i64 = _builtin_syscall(SYS_lseek, fd, 0, 2) _builtin_syscall(SYS_lseek, fd, 0, 0) @@ -320,15 +358,18 @@ func io.read_file[path: str] : str if n < 0 mem.free(buffer) - panic("failed to read file") // TODO + err.set(ERR_READ_FAILED, "io.read_file?: failed to read file") + return 0 buffer[n] = 0 return buffer -func io.read_binary_file[path: str] : io.Buffer +func io.read_binary_file?[path: str] : io.Buffer + err.clear() let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0, 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 let buffer: io.Buffer = new io.Buffer buffer->size = _builtin_syscall(SYS_lseek, fd, 0, 2) @@ -340,22 +381,27 @@ func io.read_binary_file[path: str] : io.Buffer if n < 0 mem.free(buffer) - panic("failed to read file") // TODO + err.set(ERR_READ_FAILED, "io.read_binary_file?: failed to read file") + return 0 return buffer -func io.write_file[path: str, content: str] : void +func io.write_file?[path: str, content: str] : void + err.clear() let fd: i64 = _builtin_syscall(SYS_openat, -100, path, 0x241, 0o644) if fd < 0 - panic("failed to open file") // TODO + err.set(ERR_OPEN_FAILED, "io.write_file?: failed to open file") + return 0 _builtin_syscall(SYS_write, fd, content, str.len(content)) _builtin_syscall(SYS_close, fd) -func io.write_binary_file[path: str, content: ptr, size: i64] : void +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) 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) _builtin_syscall(SYS_close, fd) @@ -419,6 +465,9 @@ func str.find[haystack: str, needle: str] : i64 if needle_len == 0 return 0 + + if needle_len > haystack_len + return -1 for i in 0..(haystack_len - needle_len + 1) let match: bool = true @@ -695,7 +744,7 @@ func array.new[] : Array func array.new_with_size_zeroed[size: i64] : Array let xs: Array = new Array if size > 0 - xs->data = mem.realloc(xs->data, size * 8) + xs->data = must(mem.realloc?(xs->data, size * 8)) mem.zero(xs->data, size * 8) xs->size = size xs->capacity = size @@ -716,7 +765,7 @@ func array.push[xs: Array, x: any] : void let new_capacity = 4 if xs->capacity != 0 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 mem.write64(xs->data + xs->size * 8, x) @@ -819,19 +868,28 @@ func os.sleep[ms: i64] : void _builtin_syscall(SYS_nanosleep, req, 0) mem.free(req) -func os.urandom[n: i64]: ptr +func os.urandom?[n: i64]: ptr + err.clear() let buffer: ptr = mem.alloc(n) + if err.check() + return 0 + let fd: i64 = _builtin_syscall(SYS_openat, -100, "/dev/urandom", 0, 0) + if fd < 0 + 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) - if fd < 0 || n != bytes_read - panic("failed to read /dev/urandom") + if n != bytes_read + err.set(ERR_READ_FAILED, "os.urandom?: failed to read /dev/urandom") + return 0 return buffer func os.urandom_i64[]: i64 - let buffer: ptr = os.urandom(8) + let buffer: ptr = must(os.urandom?(8)) let n: i64 = mem.read64(buffer) mem.free(buffer) return n diff --git a/src/tokenizer.rs b/src/tokenizer.rs index abd43d6..3056a7a 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -346,7 +346,11 @@ impl Tokenizer { } 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(); }