From dd35c65007c0cc75bb1874c2cf47f2511b862abf Mon Sep 17 00:00:00 2001 From: ctxcode Date: Thu, 12 Mar 2026 20:23:51 +0100 Subject: [PATCH 01/32] rework errors --- ROADMAP.md | 2 ++ lib/src/http/client-request.valk | 10 +++--- lib/src/net/connection.valk | 16 +++++---- src/build/build.valk | 1 + src/build/enum.valk | 1 + src/build/error-type.valk | 51 ++++++++++++++++++++++++++++ src/build/idf.valk | 8 +++++ src/build/stage-1-fc.valk | 58 ++++++++++++++++++++++++++++++++ src/build/stage-2-2-props.valk | 3 ++ src/helper/hash.valk | 48 ++++++++++++++++---------- 10 files changed, 169 insertions(+), 29 deletions(-) create mode 100644 src/build/error-type.valk diff --git a/ROADMAP.md b/ROADMAP.md index 0af360b2..9e07de67 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,6 +7,8 @@ - Release 0.1.9 - Rework errors - Rework namespaces +- Nullable int +- fnptr -> fn without allocation # Upcoming version diff --git a/lib/src/http/client-request.valk b/lib/src/http/client-request.valk index 4de2fd97..200f89d7 100644 --- a/lib/src/http/client-request.valk +++ b/lib/src/http/client-request.valk @@ -27,7 +27,7 @@ use url // Request initialization ////////////////////////////// - + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !connection_failed !ssl !invalid_output_path { + + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !connection !ssl !invalid_output_path { // Validate URL let u = url:parse(url) @@ -44,12 +44,12 @@ use url } // Connect - let con = net:Socket.client(net:SOCKET_TYPE.TCP, host, port) ! throw connection_failed, "Failed to create socket: " + EMSG + let con = net:Socket.client(net:SOCKET_TYPE.TCP, host, port) ! throw connection if is_https { con.ssl_connect(host, net:ca_cert) ! { con.close() - throw ssl, EMSG + throw ssl } } @@ -61,7 +61,7 @@ use url if isset(options) { let out_path = options.output_to_file if isset(out_path) { - let file = fs:stream(out_path, true, true, false, true) ! throw invalid_output_path, "Failed to open file: " + out_path + let file = fs:stream(out_path, true, true, false, true) ! throw invalid_output_path ctx.save_to_file = file } } @@ -164,7 +164,7 @@ use url // Send request let sent_count = con.send_buffer(this.payload, this.bytes_sent, false) ! { this.stop() - throw disconnect, "Unexpected disconnect during request" + throw disconnect } this.bytes_sent += sent_count this.sent_percent = this.bytes_sent * 100 / this.bytes_to_send diff --git a/lib/src/net/connection.valk b/lib/src/net/connection.valk index c1c16e07..8b08d7be 100644 --- a/lib/src/net/connection.valk +++ b/lib/src/net/connection.valk @@ -6,6 +6,8 @@ use io ~ fd: FD ~ ssl: ?SSL (null) ~ ssl_enabled: bool (false) + ~ ssl_error: String ("") + ~ ssl_error_code: i32 (0) - closed: bool (false) + static fn new(fd: FD) Connection { @@ -27,7 +29,7 @@ use io this.close() } - + fn ssl_connect(host: String, ca_cert_path: ?String (null)) !ssl_error { + + fn ssl_connect(host: String, ca_cert_path: ?String (null)) !ssl { if this.ssl_enabled : return @@ -39,17 +41,19 @@ use io let err = SSL_get_error(ssl.ssl, res) if err == ERROR.WANT_READ { let fd = SSL_get_fd(ssl.ssl) - if fd == -1 : throw ssl_error, "SSL missing FD" + if fd == -1 : throw ssl io:await_fd(fd, true, false) continue } if err == ERROR.WANT_WRITE { let fd = SSL_get_fd(ssl.ssl) - if fd == -1 : throw ssl_error, "SSL missing FD" + if fd == -1 : throw ssl io:await_fd(fd, false, true) continue } - throw ssl_error, "SSL connect failed | Error code: " + err + " | Message: " + SSL.last_error_msg() + this.ssl_error = SSL.last_error_msg() + this.ssl_error_code = err + throw ssl } break } @@ -130,7 +134,7 @@ use io return bytes_sent } - + fn recv(buffer: ByteBuffer, bytes: uint) uint !connection !closed { + + fn recv(buffer: ByteBuffer, bytes: uint) uint !connection !ssl !closed { if this.closed : throw closed @@ -159,7 +163,7 @@ use io continue #end } - throw connection, "SSL Error #" + err; + throw ssl } count = rcvd.@cast(uint) buffer.length += count diff --git a/src/build/build.valk b/src/build/build.valk index 918a7c1e..86a0244b 100644 --- a/src/build/build.valk +++ b/src/build/build.valk @@ -40,6 +40,7 @@ class Build { tests: Array[Func] (.{}) vtable_indexes: Map[uint] (.{}) enums: Array[Enum] (.{}) + error_types: Array[ErrorType] (.{}) allocators: Map[Allocator] (.{}) // functions_tracking_globals: Array[Func] (.{}) diff --git a/src/build/enum.valk b/src/build/enum.valk index 8d46c573..aea4f0fa 100644 --- a/src/build/enum.valk +++ b/src/build/enum.valk @@ -151,6 +151,7 @@ enum IDF { idf_group class_prop prop + error_type } // Class types diff --git a/src/build/error-type.valk b/src/build/error-type.valk new file mode 100644 index 00000000..3e392f08 --- /dev/null +++ b/src/build/error-type.valk @@ -0,0 +1,51 @@ + +class ErrorType { + build: Build + fc: Fc + act: int + name: String + display_name: String + chunk_define: ?Chunk + chunk_extends: ?Chunk + chunk_payload: ?Chunk + codes: HashMap[u32, String] (.{}) + payload: Map[Type] (.{}) + + + fn parse() { + let che = this.chunk_extends + let chp = this.chunk_payload + let scope = this.fc.scope + let codes = this.codes + if isset(che) { + let p = Parser.new(che, null) + while true { + if p.next_word_is(")", true, true, true) : break + let idf = p.read_idf(scope, true, true) + if idf.for != IDF.error_type : p.error("Not an error-type") + let et = idf.error_type ?! p.error("Missing error type") + each et.codes as code, val { + codes.get(val) -> name { + if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") + } + codes.set(val, code) + } + each et.payload as type, name { + this.add_payload(p.ctx, name, type) + } + } + } + if isset(chp) { + let p = Parser.new(chp, null) + while true { + if p.next_word_is("}", true, true, true) : break + let name = p.read_word(true, true) + p.expect(":", true, false) + let type = read_type(p, scope, true, false) + } + } + } + + fn add_payload(ctx: Context, name: String, type: Type) { + } +} diff --git a/src/build/idf.valk b/src/build/idf.valk index 028f2500..da2c6661 100644 --- a/src/build/idf.valk +++ b/src/build/idf.valk @@ -15,6 +15,7 @@ class Idf { macro_values: ?Array[Map[Idf]] (null) idf_group: ?Map[Idf] (null) prop: ?Prop (null) + error_type: ?ErrorType (null) scope: ?Scope (null) location: ?Chunk (null) used: bool (false) @@ -131,6 +132,13 @@ class Idf { } } + static fn for_error(error: ErrorType) Idf { + return Idf { + for: IDF.error_type + error_type: error_type + } + } + fn get_decl() Decl { let item = this.decl if !isset(item) : build_error("Missing decl value (bug)") diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index 86a0533c..6b5f0cf5 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -157,6 +157,10 @@ fn stage_fc(fc: Fc) { parse_enum(p, fc, act) continue } + if p.word_is("error") { + parse_error(p, fc, act) + continue + } // Without act if isset(act_word) { @@ -615,3 +619,57 @@ fn parse_enum(p: Parser, fc: Fc, act: int) { values.set(name, chunk) } } + +fn parse_error(p: Parser, fc: Fc, act: int) { + let chunk_define = p.clone_chunk() + let name = p.read_word(true, false) + + let payload_chunk : ?Chunk = null + let extend_chunk : ?Chunk = null + let codes = HashMap[u32, String]{} + + while true { + if p.next_word_is("extends", true, true, true) { + if isset(extend_chunk) : p.error("Extending twice") + p.expect("(", true, true) + extend_chunk = p.clone_chunk() + p.skip_body(")") + } else if p.next_word_is("codes", true, true, true) { + if codes.length > 0 : p.error("Double use of 'codes' keyword") + p.expect("(", true, true) + while true { + let code = p.read_word(true, true) + let val = helper:ctxhash_u32(code) + codes.get(val) -> name { + if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") + } + codes.set(val, code) + p.expect2(",", ")", true, true) + if p.word_is(")") : break + } + } else if p.next_word_is("payload", true, true, true) { + if isset(payload_chunk) : p.error("Defining payload twice") + p.expect("{", true, true) + payload_chunk = p.clone_chunk() + p.skip_body("}") + } else { + break + } + } + + + let e = ErrorType { + build: p.build + fc: fc + act: act + name: name + display_name: fc.nsc.display_name(name) + chunk_define: chunk_define + chunk_extends: extend_chunk + chunk_payload: payload_chunk + codes: codes + } + + p.build.error_types.append(e) + fc.set_idf(p.ctx, name, Idf.for_error(e)) +} diff --git a/src/build/stage-2-2-props.valk b/src/build/stage-2-2-props.valk index 64ff9fa6..f2d395d9 100644 --- a/src/build/stage-2-2-props.valk +++ b/src/build/stage-2-2-props.valk @@ -44,6 +44,9 @@ fn stage_props(b: Build) { await (co class_parse_props(class)) ! continue } } + each b.error_types as et { + et.parse() + } stage_class_sizes(b) } diff --git a/src/helper/hash.valk b/src/helper/hash.valk index 09238606..ef795b5b 100644 --- a/src/helper/hash.valk +++ b/src/helper/hash.valk @@ -1,54 +1,66 @@ fn ctxhash(content: String) String { + let mix = "TMpUivZnQsHw1klS3Ah5d6qr7tjKxJOIEmYP8VgGzcDR0f2uBe4aobWLNCFy9X".data + let base = ctxhash_base(content) + let i: u8 = 0 + while i < 32 { + let val = @ptrv(base, u8, i) + @ptrv(base, u8, i) = @ptrv(mix, u8, val % 62) + i++ + } + return String.make_from_ptr(base, 32) +} + +fn ctxhash_base(content: String) [u8 x 32] { let data = content.data let len: u8 = 32 - let buf = @stack([u8 x 32]) + let buf : [u8 x 32] = { 0... } let dpos: uint = 0 let rpos: u8 = 0 let diff: u8 = 0 - let end = false + let end1 = false + let end2 = false + // Forwards while true { let ch = @ptrv(data, u8, dpos++) if ch == 0 { - end = true + end1 = true dpos = 0 - continue } - diff += (ch + dpos.@cast(u8)) * 21 + rpos + diff += (ch + dpos.@cast(u8)) ^ rpos @ptrv(buf, u8, rpos++) = ch + diff if rpos == len { - if end : break + end2 = true rpos = 0 } + if end1 && end2 : break } - let mix = "TMpUivZnQsHw1klS3Ah5d6qr7tjKxJOIEmYP8VgGzcDR0f2uBe4aobWLNCFy9X".data - + // Backwards let i = len while i-- > 0 { let ch = @ptrv(buf, u8, i) - diff += (ch + i) * 11 + i - @ptrv(buf, u8, i) = @ptrv(mix, u8, (ch + diff) % 62) + diff += (ch + i) * 11 + @ptrv(buf, u8, i) = ch + diff } - let str = String.make_from_ptr(buf, len) - return str + return buf } fn ctxhash_u32(content: String) u32 { - let len: u8 = 32 - let hash = ctxhash(content) + let base = ctxhash_base(content) let result: u32 = 0 - let ref = @ref(result) - let data = hash.data - while len-- > 0 { - @ptrv(ref, u8, len % 4) += @ptrv(data, u8, len) + let ref = @ref(result).@cast(&[u8 x 4]) + let i : u8 = 0 + while i < 32 { + @ptrv(ref, u8, i % 4) += @ptrv(base, u8, i) + i++ } return result; } From cde4773140a583338796729aeccbb9b82fe527f6 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Thu, 12 Mar 2026 20:36:19 +0100 Subject: [PATCH 02/32] update --- src/build/error-type.valk | 9 +++++++++ src/build/idf.valk | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index 3e392f08..b523eb38 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -47,5 +47,14 @@ class ErrorType { } fn add_payload(ctx: Context, name: String, type: Type) { + let pay = this.payload + let etype = pay.get(name) ! { + pay.set(name, type) + return + } + if !etype.compat(type) { + if !type.compat(etype) : ctx.error("You have 2 payloads with the same name but with different types. Either rename one of them or use the same type. Property: %name | Type: %etype <> %type") + pay.set(name, type) + } } } diff --git a/src/build/idf.valk b/src/build/idf.valk index da2c6661..a8791d66 100644 --- a/src/build/idf.valk +++ b/src/build/idf.valk @@ -132,7 +132,7 @@ class Idf { } } - static fn for_error(error: ErrorType) Idf { + static fn for_error(error_type: ErrorType) Idf { return Idf { for: IDF.error_type error_type: error_type From c8e9b546241136c31db3b15dd5c5e5049882ce59 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Fri, 13 Mar 2026 13:59:13 +0100 Subject: [PATCH 03/32] update --- lib/src/core/errors.valk | 2 ++ src/build/error-type.valk | 6 +++++- src/build/stage-1-fc.valk | 45 +++++++++++++++++---------------------- 3 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 lib/src/core/errors.valk diff --git a/lib/src/core/errors.valk b/lib/src/core/errors.valk new file mode 100644 index 00000000..f453522f --- /dev/null +++ b/lib/src/core/errors.valk @@ -0,0 +1,2 @@ + +error SomeError (error) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index b523eb38..d109d1ef 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -5,7 +5,7 @@ class ErrorType { act: int name: String display_name: String - chunk_define: ?Chunk + chunk_define: Chunk chunk_extends: ?Chunk chunk_payload: ?Chunk codes: HashMap[u32, String] (.{}) @@ -44,6 +44,10 @@ class ErrorType { let type = read_type(p, scope, true, false) } } + if codes.length == 0 { + let p = Parser.new(this.chunk_define, null) + p.error("Errors need to have atleast 1 error code defined") + } } fn add_payload(ctx: Context, name: String, type: Type) { diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index 6b5f0cf5..c81991bd 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -628,35 +628,30 @@ fn parse_error(p: Parser, fc: Fc, act: int) { let extend_chunk : ?Chunk = null let codes = HashMap[u32, String]{} + p.expect("(", true, true) while true { - if p.next_word_is("extends", true, true, true) { - if isset(extend_chunk) : p.error("Extending twice") - p.expect("(", true, true) - extend_chunk = p.clone_chunk() - p.skip_body(")") - } else if p.next_word_is("codes", true, true, true) { - if codes.length > 0 : p.error("Double use of 'codes' keyword") - p.expect("(", true, true) - while true { - let code = p.read_word(true, true) - let val = helper:ctxhash_u32(code) - codes.get(val) -> name { - if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") - } - codes.set(val, code) - p.expect2(",", ")", true, true) - if p.word_is(")") : break - } - } else if p.next_word_is("payload", true, true, true) { - if isset(payload_chunk) : p.error("Defining payload twice") - p.expect("{", true, true) - payload_chunk = p.clone_chunk() - p.skip_body("}") - } else { - break + let code = p.read_word(true, true) + let val = helper:ctxhash_u32(code) + codes.get(val) -> name { + if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") } + codes.set(val, code) + p.expect2(",", ")", true, true) + if p.word_is(")") : break } + if p.next_word_is("extends", true, true, true) { + if isset(extend_chunk) : p.error("Extending twice") + p.expect("(", true, true) + extend_chunk = p.clone_chunk() + p.skip_body(")") + } + if p.next_word_is("payload", true, true, true) { + if isset(payload_chunk) : p.error("Defining payload twice") + p.expect("{", true, true) + payload_chunk = p.clone_chunk() + p.skip_body("}") + } let e = ErrorType { build: p.build From a118faa1955f2ae3b9427ef30e1cf8979fbdffc4 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Fri, 13 Mar 2026 19:42:06 +0100 Subject: [PATCH 04/32] update --- src/build/error-type.valk | 3 +++ src/build/stage-1-fc.valk | 1 + 2 files changed, 4 insertions(+) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index d109d1ef..f7c697fe 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -51,7 +51,10 @@ class ErrorType { } fn add_payload(ctx: Context, name: String, type: Type) { + if name == "code" : ctx.error("You cannot use 'code' for a payload property name, this is a reserved name for the language.") let pay = this.payload + let codes = this.codes + if codes.has_value(name) : ctx.error("The payload property name '%name' is already used as an error code.") let etype = pay.get(name) ! { pay.set(name, type) return diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index c81991bd..be97dfa9 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -631,6 +631,7 @@ fn parse_error(p: Parser, fc: Fc, act: int) { p.expect("(", true, true) while true { let code = p.read_word(true, true) + if code == "code" : p.error("You cannot use 'code' as an error code, this is a reserved name for the language.") let val = helper:ctxhash_u32(code) codes.get(val) -> name { if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") From 1424b7e0e32878ba44dcbc0f435ce7722d0f835a Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sat, 14 Mar 2026 00:43:47 +0100 Subject: [PATCH 05/32] unstable update --- src/build/build.valk | 6 +++ src/build/coro-gen.valk | 6 +-- src/build/error-handling.valk | 4 +- src/build/error-type.valk | 12 +++++ src/build/func.valk | 20 +++++--- src/build/stage-2-4-types.valk | 14 ++--- src/build/type.valk | 93 ++++++++++++++-------------------- src/build/value.valk | 40 ++++++++------- 8 files changed, 101 insertions(+), 94 deletions(-) diff --git a/src/build/build.valk b/src/build/build.valk index 86a0244b..18608e6b 100644 --- a/src/build/build.valk +++ b/src/build/build.valk @@ -217,6 +217,12 @@ class Build { let class = this.valk_class(ns, name) return class.get_type() } + fn valk_error(ns: String, name: String) ErrorType { + let idf = this.valk_idf(ns, name) + let etype = idf.error_type + if !isset(etype) : this.error("Compiler bug | Identifier is not an error-type") + return etype + } fn error_code_type() Type { return this.valk_type("type", "u32") } diff --git a/src/build/coro-gen.valk b/src/build/coro-gen.valk index c6d86d90..f2cd3d39 100644 --- a/src/build/coro-gen.valk +++ b/src/build/coro-gen.valk @@ -235,7 +235,7 @@ fn coro_generate(p: Parser, scope: Scope, on: Value) Value { idf_scope.func = scope.func let res = read_value(sp, idf_scope, 0) - res.rett = type_promise(b, fi.rett_types, fi.errors) + res.rett = type_promise(b, fi.rett_types, fi.error_type) let rvs = res.values if isset(rvs) { let v1 = rvs.get(0) ! p.error("Missing first value from value scope for coroutine (bug)") @@ -255,7 +255,7 @@ fn coro_await(p: Parser, scope: Scope, on: Value) Value { if on.rett.type != TYPE.promise : p.error("Cannot use await on this type: " + on.rett) let retts = on.rett.sub_types ?? Array[Type]{} - let errors = on.rett.errors ?? Map[ERR_TYPE]{} + let etype = on.rett.error_type ?? b.valk_error("core", "SomeError") let can_error = on.rett.can_error let sub = scope.sub_scope(SCOPE.default) @@ -356,7 +356,7 @@ fn coro_await(p: Parser, scope: Scope, on: Value) Value { // Error handling if can_error || p.has_error_handler_ahead() { - res = value_error_handling(p, scope, res, errors) + res = value_error_handling(p, scope, res, etype) } else { let path = p.chunk.path() let line = p.line diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index e3812207..ac0d9a73 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -14,7 +14,7 @@ class ErrorHandler { global errh_tokens : ?Array[String] (null) -fn value_error_handling(p: Parser, scope: Scope, on: Value, errors: Map[ERR_TYPE]) Value { +fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorType) Value { if !on.can_error() : p.error("Error handler on a value that does not return errors") @@ -56,7 +56,7 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, errors: Map[ERR_TYPE let code = on.func_err_code ?! ctx.error("Missing error code (bug)") let msg = on.func_err_msg ?! ctx.error("Missing error message (bug)") let w = vgen_wrap(code) - w.rett = type_error(b, errors) + w.rett = type_error(b, error_type) sub.set_idf(p.ctx, "E", Idf.for_value(w)) sub.set_idf(p.ctx, "EMSG", Idf.for_value(msg)) sub.ast.append(tgen_statement(code)) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index f7c697fe..c3e037dd 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -8,6 +8,7 @@ class ErrorType { chunk_define: Chunk chunk_extends: ?Chunk chunk_payload: ?Chunk + extends: Array[ErrorType] (.{}) codes: HashMap[u32, String] (.{}) payload: Map[Type] (.{}) @@ -24,6 +25,10 @@ class ErrorType { let idf = p.read_idf(scope, true, true) if idf.for != IDF.error_type : p.error("Not an error-type") let et = idf.error_type ?! p.error("Missing error type") + if et.extends.contains(this) : p.error("Circular error types. Type A extends B and B extends A.") + each et.extends as subet { + this.extends.append(subet) + } each et.codes as code, val { codes.get(val) -> name { if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") @@ -64,4 +69,11 @@ class ErrorType { pay.set(name, type) } } + + fn is_compat(with: ErrorType) bool { + if this != with { + if !this.extends.contains(with) : return false + } + return true + } } diff --git a/src/build/func.valk b/src/build/func.valk index ab18d9c8..f556fd91 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -13,7 +13,8 @@ type ERR_TYPE (u32) class FuncInfo { args: Array[Type] (Array[Type].new()) rett_types: Array[Type] (Array[Type].new()) - errors: Map[ERR_TYPE] (Map[ERR_TYPE].new()) + // errors: Map[ERR_TYPE] (Map[ERR_TYPE].new()) + error_type: ?ErrorType infinite_args: bool (false) can_error: bool (false) @@ -25,9 +26,10 @@ class FuncInfo { return FuncInfo { args: this.args.copy() rett_types: this.rett_types.copy() - errors: this.errors.copy() + // errors: this.errors.copy() + error_type: this.error_type infinite_args: this.infinite_args - can_error: this.errors.length > 0 + can_error: isset(this.error_type) } } @@ -125,7 +127,8 @@ class Func { args: Array[Arg] (Array[Arg]{}) rett: Type rett_real: Type - errors: Map[u32] (Map[u32]{}) + // errors: Map[u32] (Map[u32]{}) + error_type: ?ErrorType arg_scope: Scope // Generics @@ -185,7 +188,8 @@ class Func { return FuncInfo { args: this.arg_types() rett_types: this.rett.unroll() - errors: this.errors + // errors: this.errors + error_type: this.error_type infinite_args: this.infinite_args can_error: this.can_error } @@ -230,10 +234,10 @@ class Func { } t = p.tok(true, true, false) - while p.sign_is("!") { + if p.sign_is("!") { t = p.tok(true, true) - t = p.tok(false, false) - if t != TOK.word : p.error("Invalid error name: " + p.word()) + // let idf = p.read_idf(this.fc.scope, false, false) + p.skip_id(false) t = p.tok(true, true, false) } diff --git a/src/build/stage-2-4-types.valk b/src/build/stage-2-4-types.valk index e28d2c82..562e1bdf 100644 --- a/src/build/stage-2-4-types.valk +++ b/src/build/stage-2-4-types.valk @@ -146,14 +146,11 @@ fn parse_func_args(func: Func) { } t = p.tok(true, true) - while p.sign_is("!") { - t = p.tok(false, false) - if t != TOK.word : p.error("Invalid error name syntax: " + p.word()) - let name = p.word() - let val = helper:ctxhash_u32(name) - if func.errors.has(name) : p.error("Duplicate error name: " + name) - if func.errors.has_value(val) : p.error("It seems that 2 different error names are resolving to the same hash, try picking a different error name: " + name) - func.errors.set(name, val) + if p.sign_is("!") { + let idf = p.read_idf(func.fc.scope, false, false) + if idf.for != IDF.error_type : p.error("Identifier must be an error type") + let etype = idf.error_type ?! p.bug("Missing error type for identifier") + func.error_type = etype if !func.can_error { func.can_error = true let types = func.rett.unroll().copy() @@ -161,7 +158,6 @@ fn parse_func_args(func: Func) { types.append(p.build.valk_type("type", "String")) func.rett_real = type_multi(p.build, types) } - t = p.tok(true, true) } while t == TOK.flag || t == TOK.at_word { diff --git a/src/build/type.valk b/src/build/type.valk index 23168047..6a791185 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -7,7 +7,7 @@ class Type { sub_type: ?Type (null) array_size: uint (0) func_info: ?FuncInfo (null) - errors: ?Map[ERR_TYPE] (null) + error_type: ?ErrorType (null) class: ?Class (null) group: ?Group (null) enum: ?Enum (null) @@ -214,11 +214,9 @@ class Type { } result += ")" } - let errors = this.errors - if isset(errors) { - each errors as name, err { - result += " !" + name - } + let etype = this.error_type + if isset(etype) { + result += " !" + etype.display_name } return result } @@ -370,13 +368,10 @@ class Type { // Error types if base.type == TYPE.error || base.type == TYPE.error_item { if type.type != TYPE.error && type.type != TYPE.error_item : return false - let base_errors = base.errors - let type_errors = type.errors - if !isset(base_errors) || !isset(type_errors) : return false - each type_errors as code, name { - if !base_errors.has(name) : return false - } - return true + let base_et = base.error_type + let type_et = type.error_type + if !isset(base_et) || !isset(type_et) : return false + return base_et.is_compat(type_et) } // co[u32]!err if base.type == TYPE.promise { @@ -392,15 +387,10 @@ class Type { } } // Errors - let base_errors = base.errors - let type_errors = type.errors - if isset(type_errors) { - if !isset(base_errors) : return false - each type_errors as code, name { - if !base_errors.has(name) : return false - } - } - return true + let base_et = base.error_type + let type_et = type.error_type + if !isset(base_et) || !isset(type_et) : return false + return base_et.is_compat(type_et) } if base.type == TYPE.func || base.type == TYPE.closure { let fi_base = this.func_info @@ -705,15 +695,15 @@ fn read_type(p: Parser, scope: Scope, allow_newline: bool (true), allow_multi: b p.expect("(", false, false) let rett_types = p.read_types(scope, ")", true) // Errors - let errors = Map[u32]{} - read_errors(p, errors) + let error_type : ?ErrorType = null + read_errors(p, scope, &error_type) // Result let info = FuncInfo { args: types rett_types: rett_types - errors: errors - can_error: errors.length > 0 + error_type: error_type + can_error: isset(error_type) } let type = Type.new(p.build, is_closure ? TYPE.closure : TYPE.func) type.func_info = info @@ -725,9 +715,9 @@ fn read_type(p: Parser, scope: Scope, allow_newline: bool (true), allow_multi: b if p.next_word_is("(", false, false, true) { rett_types = p.read_types(scope, ")", true) } - let errors = Map[u32]{} - read_errors(p, errors) - return type_promise(p.build, rett_types, errors) + let error_type : ?ErrorType = null + read_errors(p, scope, &error_type) + return type_promise(p.build, rett_types, error_type) } if p.word_is("typeof") { p.expect("(", false, false) @@ -765,19 +755,12 @@ fn read_fixed_array_type(p: Parser, scope: Scope) Type { return type } -fn read_errors(p: Parser, errors: Map[ERR_TYPE]) { - p.tok(true, false, false) - while p.sign_is("!") { - p.tok(true, false) - let t = p.tok(false, false) - if t != TOK.word : p.error("Invalid error name syntax: " + p.word()) - let name = p.word() - let val = helper:ctxhash_u32(name) - if errors.has(name) : p.error("Duplicate error name: " + name) - if errors.has_value(val) : p.error("It seems that 2 different error names are resolving to the same hash, try picking a different error name: " + name) - errors.set(name, val) - // Next - p.tok(true, false, false) +fn read_errors(p: Parser, scope: Scope, etype_ref: &?ErrorType) { + if p.next_word_is("!", true, false, true) { + let idf = p.read_idf(scope, false, false) + if idf.for != IDF.error_type : p.error("Identifier must be an error type") + let etype = idf.error_type ?! p.bug("Missing error type for identifier") + etype_ref[0] = etype } } @@ -881,11 +864,11 @@ fn type_func_info(b: Build, info: FuncInfo) Type { t.func_info = info return t } -fn type_promise(b: Build, return_types: ?Array[Type], errors: ?Map[ERR_TYPE]) Type { +fn type_promise(b: Build, return_types: ?Array[Type], error_type: ?ErrorType) Type { let t = Type.new(b, TYPE.promise) t.sub_types = return_types - t.errors = errors - t.can_error = isset(errors) && errors.length > 0 + t.error_type = error_type + t.can_error = isset(error_type) return t } fn type_closure(b: Build, fi: FuncInfo) Type { @@ -942,20 +925,20 @@ fn type_number(b: Build, size: uint, is_float: bool, is_signed: bool) Type { b.error("Cannot generate numeric type for size: " + size) } -fn type_error(b: Build, errors: Map[ERR_TYPE]) Type { +fn type_error(b: Build, etype: ErrorType) Type { let type = Type.new(b, TYPE.error) - type.errors = errors - type.class = b.valk_class("type", "u32") - return type -} -fn type_error_item(b: Build, name: String, val: ERR_TYPE) Type { - let type = Type.new(b, TYPE.error_item) - let errors = Map[ERR_TYPE].new() - errors.set(name, val) - type.errors = errors + type.error_type = etype type.class = b.valk_class("type", "u32") return type } +// fn type_error_item(b: Build, name: String, val: ERR_TYPE) Type { +// let type = Type.new(b, TYPE.error_item) +// let errors = Map[ERR_TYPE].new() +// errors.set(name, val) +// type.error_type = errors +// type.class = b.valk_class("type", "u32") +// return type +// } fn match_alt_value_types(t1: Type, t2: Type, return_void_if_not_compat: bool) Type !compat { if t2.nullable : t1 = t1.get_nullable() diff --git a/src/build/value.valk b/src/build/value.valk index 4ea09f43..182eb462 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -368,7 +368,7 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals let name = p.read_word(true, true) let nr = helper:ctxhash_u32(name) - let right = vgen_int(nr, type_error_item(b, name, nr)) + let right = vgen_int(nr, b.valk_type("type", "u32")) // let l, r = match_op_values(b, left, right) // l.rett.compat_check(r.rett, p) @@ -380,7 +380,7 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals let name = p.read_word(true, true) let nr = helper:ctxhash_u32(name) - let right = vgen_int(nr, type_error_item(b, name, nr)) + let right = vgen_int(nr, b.valk_type("type", "u32")) // let l, r = match_op_values(b, left, right) // l.rett.compat_check(r.rett, p) @@ -1507,7 +1507,8 @@ fn value_func_call(p: Parser, scope: Scope, on: Value, read_co: bool) Value { let res = vgen_func_call(p.build, scope, on, values) if func_info.can_error && !read_co { - res = value_error_handling(p, scope, res, func_info.errors) + let etype = func_info.error_type ?! p.error("Missing error type in function info for error handler") + res = value_error_handling(p, scope, res, etype) } return res @@ -1537,8 +1538,13 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false if lsp.is_completion { // Error if on.rett.type == TYPE.error { - let errors = on.rett.errors - if isset(errors) : lsp.check_completion_simple(errors.keys()) + let etype = on.rett.error_type + if isset(etype) { + let comps = Array[String]{ "code" } + comps.append_many(etype.codes.values()) + comps.append_many(etype.payload.keys()) + lsp.check_completion_simple(comps) + } } // $length if size > 0 : lsp.check_completion_simple(Array[String]{ "$length" }) @@ -1575,21 +1581,21 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false } if on.rett.type == TYPE.error { - let errors = on.rett.errors - if !isset(errors) : p.error("The compiler seems to have lost essential information about this error value, so you cannot access any properties on this error value. (bug)") + let etype = on.rett.error_type + if !isset(etype) : p.bug("Missing error compiler information") - let errv = errors.get(name) ! { - let options = "" - let i = 0 - each errors as v, k { - if i > 0 : options += ", " - i++ - options += k - } - p.error("This value has no error named '" + name + "'. Valid options are: " + options) + if name == "code" { + return vgen_cast(on, b.valk_type("type", "u32")) } - return vgen_int(errv, type_error_item(b, name, errv)) + if etype.codes.has_value(name) { + let errv = helper:ctxhash_u32(name) + return vgen_int(errv, b.valk_type("type", "u32")) + } + if etype.payload.has(name) { + panic("TODO: payload data") + } + p.error("Error type has no error code or payload property named: '" + name + "'. Valid options are: " + etype.codes.values().join(", ")) } let e = on.rett.enum From c38adf347e3352b05ef4bb5eea19da41cf9302aa Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sat, 14 Mar 2026 14:25:36 +0100 Subject: [PATCH 06/32] update unstable --- lib/src/core/errors.valk | 1 + src/build/ast-gen.valk | 18 ++++++--- src/build/ast.valk | 22 ++++++----- src/build/build.valk | 7 +--- src/build/coro-gen.valk | 8 ++-- src/build/enum.valk | 5 ++- src/build/error-handling.valk | 33 ++++++++++++---- src/build/error-type.valk | 50 +++++++++++++++++++++--- src/build/error-value.valk | 6 +++ src/build/func.valk | 7 ++-- src/build/idf.valk | 9 ++++- src/build/ir-type.valk | 1 - src/build/match.valk | 19 ++++----- src/build/type.valk | 23 +++++------ src/build/value-gen.valk | 14 ++++--- src/build/value.valk | 72 ++++++++++++++++++++++------------- 16 files changed, 197 insertions(+), 98 deletions(-) create mode 100644 src/build/error-value.valk diff --git a/lib/src/core/errors.valk b/lib/src/core/errors.valk index f453522f..8cf1b651 100644 --- a/lib/src/core/errors.valk +++ b/lib/src/core/errors.valk @@ -1,2 +1,3 @@ error SomeError (error) +error IterError (end) diff --git a/src/build/ast-gen.valk b/src/build/ast-gen.valk index d2b4b5fe..ad40e7d4 100644 --- a/src/build/ast-gen.valk +++ b/src/build/ast-gen.valk @@ -15,10 +15,11 @@ fn ast_gen_return(ctx: Context, func: Func, scope: Scope, retv: Value) { func.rett.compat_check(retv.rett, ctx) - if func.can_error { + let etype = func.error_type + if isset(etype) { let values = retv.unroll().copy() - values.append(vgen_int(0, b.error_code_type())) - values.append(vgen_null(b.error_msg_type())) + values.append(vgen_int(0, etype.get_type(null))) + // values.append(vgen_null(b.error_msg_type())) retv = vgen_grouped_values(b, values) } @@ -45,7 +46,7 @@ fn ast_return_value(func: Func, scope: Scope, value: ?Value) { scope.did_return = true } -fn ast_gen_throw(ctx: Context, scope: Scope, code: Value, msg: Value, location: String) { +fn ast_gen_throw(ctx: Context, scope: Scope, etype: ErrorType, code: Value, payload: ?Map[Value], location: String) { let b = ctx.build let func = ctx.getFunc() @@ -58,7 +59,14 @@ fn ast_gen_throw(ctx: Context, scope: Scope, code: Value, msg: Value, location: values.append(vgen_empty_value(rett) ?! continue) } values.append(code) - values.append(msg) + // values.append(msg) + + if isset(payload) { + each payload as val, name { + let g = etype.payload_globals.get(name) ! ctx.error("Bug: Missing payload store") + ast_gen_assign(ctx, scope, vgen_global(g), val, false, null) + } + } ast_return_value(func, scope, vgen_grouped_values(b, values)) } diff --git a/src/build/ast.valk b/src/build/ast.valk index f64724aa..fbb4253e 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -700,19 +700,23 @@ fn ast_throw(p: Parser, scope: Scope) { let b = p.build let err = p.read_word(true, false) - let hash = func.errors.get(err) ! p.error("Unknown error '" + err + "'. Please add the error to your function declaration.") + let etype = func.error_type + if !isset(etype) : p.error("This function has no error type") + if !etype.codes.has_value(err) : p.error("Unknown error '" + err + "'. Please add the error to your function declaration.") + let hash = helper:ctxhash_u32(err) + let payload : ?Map[Value] = null let msg = Str.new_value(err, p.ctx) p.tok(true, false, false) - if p.word_is(",") { - p.tok(true, false) - msg = read_value(p, scope) - if (msg.rett.get_class() !? null) != b.valk_class("type", "String") { - p.error("Invalid throw message value. Expected a 'String' instead of: " + msg.rett) - } - } + // if p.word_is(",") { + // p.tok(true, false) + // msg = read_value(p, scope) + // if (msg.rett.get_class() !? null) != b.valk_class("type", "String") { + // p.error("Invalid throw message value. Expected a 'String' instead of: " + msg.rett) + // } + // } - ast_gen_throw(p.ctx, scope, vgen_int(hash, b.error_code_type()), msg, p.clone_chunk().path_and_line()) + ast_gen_throw(p.ctx, scope, etype, vgen_int(hash, etype.get_type(null)), payload, p.clone_chunk().path_and_line()) } fn ast_cothrow(p: Parser, scope: Scope) { diff --git a/src/build/build.valk b/src/build/build.valk index 18608e6b..a01a91ce 100644 --- a/src/build/build.valk +++ b/src/build/build.valk @@ -41,6 +41,7 @@ class Build { vtable_indexes: Map[uint] (.{}) enums: Array[Enum] (.{}) error_types: Array[ErrorType] (.{}) + payload_globals: Map[Global] (.{}) allocators: Map[Allocator] (.{}) // functions_tracking_globals: Array[Func] (.{}) @@ -223,12 +224,6 @@ class Build { if !isset(etype) : this.error("Compiler bug | Identifier is not an error-type") return etype } - fn error_code_type() Type { - return this.valk_type("type", "u32") - } - fn error_msg_type() Type { - return this.valk_type("type", "String") - } fn array_of_string_type() Type { let a = this.valk_class("type", "Array") let s = this.valk_type("type", "String") diff --git a/src/build/coro-gen.valk b/src/build/coro-gen.valk index f2cd3d39..2468b695 100644 --- a/src/build/coro-gen.valk +++ b/src/build/coro-gen.valk @@ -350,9 +350,11 @@ fn coro_await(p: Parser, scope: Scope, on: Value) Value { let res = read_value(sp, sub, 0) let prop = coro_class.get_prop("error_code") ! p.error("Missing error_code property") - let prop_msg = coro_class.get_prop("error_msg") ! p.error("Missing error_msg property") - res.func_err_code = vgen_prop(prop, on) - res.func_err_msg = vgen_prop(prop_msg, on) + // let prop_msg = coro_class.get_prop("error_msg") ! p.error("Missing error_msg property") + let codev = vgen_prop(prop, on) + codev.rett = etype.get_type(null) + res.func_err_code = codev + // res.func_err_msg = vgen_prop(prop_msg, on) // Error handling if can_error || p.has_error_handler_ahead() { diff --git a/src/build/enum.valk b/src/build/enum.valk index aea4f0fa..8b0c234f 100644 --- a/src/build/enum.valk +++ b/src/build/enum.valk @@ -120,8 +120,8 @@ enum TYPE { float undefined array - error - error_item + // error + error_code func multi bool @@ -152,6 +152,7 @@ enum IDF { class_prop prop error_type + error_value } // Class types diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index ac0d9a73..d2684927 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -53,14 +53,31 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp let sub = scope.sub_scope(SCOPE.default) // Variables - let code = on.func_err_code ?! ctx.error("Missing error code (bug)") - let msg = on.func_err_msg ?! ctx.error("Missing error message (bug)") - let w = vgen_wrap(code) - w.rett = type_error(b, error_type) - sub.set_idf(p.ctx, "E", Idf.for_value(w)) - sub.set_idf(p.ctx, "EMSG", Idf.for_value(msg)) - sub.ast.append(tgen_statement(code)) - sub.ast.append(tgen_statement(msg)) + let codev = on.func_err_code ?! ctx.error("Missing error code (bug)") + let ev = ErrorValue { + error_type: error_type + codev: codev + } + sub.ast.append(tgen_statement(codev)) + if error_type.payload.length > 0 { + let payload = Map[Value]{} + each error_type.payload_globals as g, name { + let gtype = g.type ?! ctx.error("Missing payload global type in error handler") + let decl = Decl.new(gtype, false, null) + sub.add_decl(decl) + let vdecl = vgen_decl(decl) + let v = vgen_global(g) + ast_gen_assign(ctx, sub, vdecl, v, true, p.chunk) + payload.set(name, v) + } + } + + // let msg = on.func_err_msg ?! ctx.error("Missing error message (bug)") + // let w = vgen_wrap(code) + // w.rett = type_error(b, error_type) + sub.set_idf(p.ctx, "E", Idf.for_error_value(ev)) + // sub.set_idf(p.ctx, "EMSG", Idf.for_value(msg)) + // sub.ast.append(tgen_statement(msg)) // Left return value let left = on.trim_errors() diff --git a/src/build/error-type.valk b/src/build/error-type.valk index c3e037dd..c83154aa 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -1,4 +1,6 @@ +use helper + class ErrorType { build: Build fc: Fc @@ -11,7 +13,27 @@ class ErrorType { extends: Array[ErrorType] (.{}) codes: HashMap[u32, String] (.{}) payload: Map[Type] (.{}) + payload_globals: Map[Global] (.{}) + payload_global_names: Array[String] (.{}) + cache_type: ?Type (null) + cache_code_types: Map[Type] (.{}) + fn get_type(code: ?String) Type { + if !isset(code) { + let type = this.cache_type + if isset(type) : return type + type = Type.new(this.build, TYPE.error_code) + type.error_type = this + this.cache_type = type + return type + } + this.cache_code_types.get(code) -> t : return t + let type = Type.new(this.build, TYPE.error_code) + type.error_type = this + type.error_code_name = code + this.cache_code_types.set(code, type) + return type + } fn parse() { let che = this.chunk_extends @@ -60,14 +82,13 @@ class ErrorType { let pay = this.payload let codes = this.codes if codes.has_value(name) : ctx.error("The payload property name '%name' is already used as an error code.") - let etype = pay.get(name) ! { - pay.set(name, type) - return - } - if !etype.compat(type) { + pay.get(name) -> etype { + if etype.compat(type) : return if !type.compat(etype) : ctx.error("You have 2 payloads with the same name but with different types. Either rename one of them or use the same type. Property: %name | Type: %etype <> %type") - pay.set(name, type) } + pay.set(name, type) + let g = get_payload_global(ctx.build, type, this.payload_global_names) + this.payload_globals.set(name, g) } fn is_compat(with: ErrorType) bool { @@ -77,3 +98,20 @@ class ErrorType { return true } } + +fn get_payload_global(b: Build, type: Type, exclude: Array[String]) Global { + type = type.get_nullable() + let base = type.to_str(true) + let key = base + let i = 0 + while exclude.contains(key) { + key = base + "_" + (i++) + } + b.payload_globals.get(key) -> g : return g + let u = b.generated_unit() + let dname = "ErrorPayload_" + key + let g = u.new_global(b.generated_fc(), act_public_all, dname, "error_payload_" + helper:ctxhash(key), dname, null, null, false) + exclude.append(key) + b.payload_globals.set(key, g) + return g +} diff --git a/src/build/error-value.valk b/src/build/error-value.valk new file mode 100644 index 00000000..540e047b --- /dev/null +++ b/src/build/error-value.valk @@ -0,0 +1,6 @@ + +class ErrorValue { + error_type: ErrorType + codev: Value + payload: ?Map[Value] +} \ No newline at end of file diff --git a/src/build/func.valk b/src/build/func.valk index f556fd91..0335a626 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -46,10 +46,11 @@ class FuncInfo { } fn get_real_types(b: Build) Array[Type] { - if !this.can_error : return this.rett_types + let etype = this.error_type + if !isset(etype) : return this.rett_types let res = this.rett_types.copy() - res.append(b.error_code_type()) - res.append(b.error_msg_type()) + res.append(etype.get_type(null)) + // res.append(b.error_msg_type()) return res } fn rett(b: Build) Type { diff --git a/src/build/idf.valk b/src/build/idf.valk index a8791d66..626da9e4 100644 --- a/src/build/idf.valk +++ b/src/build/idf.valk @@ -16,6 +16,7 @@ class Idf { idf_group: ?Map[Idf] (null) prop: ?Prop (null) error_type: ?ErrorType (null) + error_value: ?ErrorValue (null) scope: ?Scope (null) location: ?Chunk (null) used: bool (false) @@ -132,12 +133,18 @@ class Idf { } } - static fn for_error(error_type: ErrorType) Idf { + static fn for_error_type(error_type: ErrorType) Idf { return Idf { for: IDF.error_type error_type: error_type } } + static fn for_error_value(error_value: ErrorValue) Idf { + return Idf { + for: IDF.error_value + error_value: error_value + } + } fn get_decl() Decl { let item = this.decl diff --git a/src/build/ir-type.valk b/src/build/ir-type.valk index 67157347..4eea8e00 100644 --- a/src/build/ir-type.valk +++ b/src/build/ir-type.valk @@ -3,7 +3,6 @@ fn ir_type(type: Type) String { if type.is_pointer() : return "ptr" let tt = type.type if tt == TYPE.void : return "void" - if tt == TYPE.error : return "i32" if tt == TYPE.int { let size = type.size() return ir_type_int(size) diff --git a/src/build/match.valk b/src/build/match.valk index 55097319..50f10db0 100644 --- a/src/build/match.valk +++ b/src/build/match.valk @@ -9,12 +9,12 @@ fn parse_match_value(p: Parser, scope: Scope) Value { let decls = Array[Decl].new() let errors : ?Map[ERR_TYPE] = null - let on_errors = on.rett.errors - if isset(on_errors) { + let etype = on.rett.error_type + if isset(etype) { // TODO: use copy/clone function to make this code shorter let errs = Map[ERR_TYPE].new() - each on_errors as v, k { - errs.set(k, v) + each etype.codes as name, val { + errs.set(name, val) } errors = errs } @@ -69,13 +69,10 @@ fn parse_match_value(p: Parser, scope: Scope) Value { let itemv = read_value(p, main_ast) p.pop_suggest() - if isset(errors) && itemv.rett.type == TYPE.error_item { - let item_errors = itemv.rett.errors - if isset(item_errors) { - each item_errors as code, name { - errors.remove(name) - break - } + if isset(errors) && itemv.rett.type == TYPE.error_code { + let name = itemv.rett.error_code_name + if isset(name) { + errors.remove(name) } } diff --git a/src/build/type.valk b/src/build/type.valk index 6a791185..c298c64c 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -8,6 +8,7 @@ class Type { array_size: uint (0) func_info: ?FuncInfo (null) error_type: ?ErrorType (null) + error_code_name: ?String (null) class: ?Class (null) group: ?Group (null) enum: ?Enum (null) @@ -200,8 +201,8 @@ class Type { if t == TYPE.none : return "none" if t == TYPE.void : return "void" if t == TYPE.undefined : return "@undefined" - if t == TYPE.error : return "" - if t == TYPE.error_item : return "" + // if t == TYPE.error : return "" + if t == TYPE.error_code : return "" if t == TYPE.promise { let result = "co" @@ -343,7 +344,7 @@ class Type { if base.type != type.type { if base.type == TYPE.ref && type.type == TYPE.ptr { // allow: &X <-> *X - } else if base.type == TYPE.error && type.type == TYPE.error_item { + // } else if base.type == TYPE.error && type.type == TYPE.error_code { // allow: error <-> error-item } else { return false @@ -366,8 +367,8 @@ class Type { return sub.compat(sub2, ignore_imut) } // Error types - if base.type == TYPE.error || base.type == TYPE.error_item { - if type.type != TYPE.error && type.type != TYPE.error_item : return false + if base.type == TYPE.error_code { + if type.type != TYPE.error_code : return false let base_et = base.error_type let type_et = type.error_type if !isset(base_et) || !isset(type_et) : return false @@ -925,12 +926,12 @@ fn type_number(b: Build, size: uint, is_float: bool, is_signed: bool) Type { b.error("Cannot generate numeric type for size: " + size) } -fn type_error(b: Build, etype: ErrorType) Type { - let type = Type.new(b, TYPE.error) - type.error_type = etype - type.class = b.valk_class("type", "u32") - return type -} +// fn type_error(b: Build, etype: ErrorType) Type { +// let type = Type.new(b, TYPE.error) +// type.error_type = etype +// type.class = type_void(b) +// return type +// } // fn type_error_item(b: Build, name: String, val: ERR_TYPE) Type { // let type = Type.new(b, TYPE.error_item) // let errors = Map[ERR_TYPE].new() diff --git a/src/build/value-gen.valk b/src/build/value-gen.valk index 7cb5c230..300df727 100644 --- a/src/build/value-gen.valk +++ b/src/build/value-gen.valk @@ -159,15 +159,17 @@ fn vgen_func_call(b: Build, decl_scope: Scope, on: Value, values: Array[Value]) fcall.unrolls = unrolls } - if func_info.can_error { + let etype = func_info.error_type + if isset(etype) { let real_retts = retts.copy() let index = retts.length - let code_type = b.error_code_type() - let msg_type = b.error_msg_type() - fcall.func_err_code = vgen_rett_value(fcall, index, code_type) - fcall.func_err_msg = vgen_rett_value(fcall, index + 1, msg_type) + let code_type = etype.get_type(null) + // let msg_type = b.error_msg_type() + let codev = vgen_rett_value(fcall, index, code_type) + fcall.func_err_code = codev + // fcall.func_err_msg = vgen_rett_value(fcall, index + 1, msg_type) real_retts.append(code_type) - real_retts.append(msg_type) + // real_retts.append(msg_type) fcall.rett = type_multi(b, real_retts) // fcall.type1 = type_multi(b, real_retts) } diff --git a/src/build/value.valk b/src/build/value.valk index 182eb462..35e4a253 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -359,7 +359,7 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals let idf = p.read_idf(scope, true, true) let left = idf.value if !isset(left) : p.error("First value in 'error_is' is not an error value") - if left.rett.type != TYPE.error : p.error("First value in 'error_is' is not an error type") + if left.rett.type != TYPE.error_code : p.error("First value in 'error_is' is not an error type") // Left // let left = vgen_decl(decl) @@ -1438,6 +1438,26 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { return propv } } + if for == IDF.error_value { + let ev = idf.error_value + if !isset(ev) : p.bug("Missing error value information for identifier") + p.expect(".", false, false) + let key = p.read_word(false, false) + let etype = ev.error_type + + if key == "code" { + return ev.codev + } + + if etype.codes.has_value(key) { + let errv = helper:ctxhash_u32(key) + return vgen_int(errv, etype.get_type(key)) + } + if etype.payload.has(key) { + panic("TODO: payload data") + } + p.error("Error type has no error code or payload property named: '" + key + "'. Valid options are: " + etype.codes.values().join(", ")) + } p.error("Unhandled identifier type (type: " + for + ") (compiler bug)") } @@ -1536,16 +1556,16 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false if is_lsp { let lsp = p.build.getlsp() if lsp.is_completion { - // Error - if on.rett.type == TYPE.error { - let etype = on.rett.error_type - if isset(etype) { - let comps = Array[String]{ "code" } - comps.append_many(etype.codes.values()) - comps.append_many(etype.payload.keys()) - lsp.check_completion_simple(comps) - } - } + // // Error + // if on.rett.type == TYPE.error { + // let etype = on.rett.error_type + // if isset(etype) { + // let comps = Array[String]{ "code" } + // comps.append_many(etype.codes.values()) + // comps.append_many(etype.payload.keys()) + // lsp.check_completion_simple(comps) + // } + // } // $length if size > 0 : lsp.check_completion_simple(Array[String]{ "$length" }) // Enum @@ -1580,23 +1600,23 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false return vgen_cast(on, type) } - if on.rett.type == TYPE.error { - let etype = on.rett.error_type - if !isset(etype) : p.bug("Missing error compiler information") + // if on.rett.type == TYPE.error { + // let etype = on.rett.error_type + // if !isset(etype) : p.bug("Missing error compiler information") - if name == "code" { - return vgen_cast(on, b.valk_type("type", "u32")) - } + // if name == "code" { + // return vgen_cast(on, b.valk_type("type", "u32")) + // } - if etype.codes.has_value(name) { - let errv = helper:ctxhash_u32(name) - return vgen_int(errv, b.valk_type("type", "u32")) - } - if etype.payload.has(name) { - panic("TODO: payload data") - } - p.error("Error type has no error code or payload property named: '" + name + "'. Valid options are: " + etype.codes.values().join(", ")) - } + // if etype.codes.has_value(name) { + // let errv = helper:ctxhash_u32(name) + // return vgen_int(errv, b.valk_type("type", "u32")) + // } + // if etype.payload.has(name) { + // panic("TODO: payload data") + // } + // p.error("Error type has no error code or payload property named: '" + name + "'. Valid options are: " + etype.codes.values().join(", ")) + // } let e = on.rett.enum if isset(e) { From 15395ab25cde28a96a4b2c0892bc469587f8067b Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sat, 14 Mar 2026 14:27:39 +0100 Subject: [PATCH 07/32] update unstable --- src/build/closures.valk | 2 +- src/doc/gen.valk | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/build/closures.valk b/src/build/closures.valk index d3566999..ed1f74ba 100644 --- a/src/build/closures.valk +++ b/src/build/closures.valk @@ -80,7 +80,7 @@ fn generate_closure_outer_function(parent: Func, inner_func: Value, fi: FuncInfo // Errors func.can_error = fi.can_error - func.errors = fi.errors + func.error_type = fi.error_type // let ctx = Context { diff --git a/src/doc/gen.valk b/src/doc/gen.valk index a554355b..f8410348 100644 --- a/src/doc/gen.valk +++ b/src/doc/gen.valk @@ -146,11 +146,8 @@ fn jfunc(funcs: json:Value, func: build:Func, fake_func: build:Func) { f.set("arguments", json:new_array(args)) f.set("return-type", json:new_string(func.rett)) - let errors = Array[json:Value]{} - each func.errors as err, name { - errors.append(json:new_string(name)) - } - f.set("errors", json:new_array(errors)) + let etype = func.error_type + f.set("error_type", isset(etype) ? json:new_string(etype.display_name) : json:new_null()) } fn jval(v: build:Value) json:Value { From bb52fdda20b728949259d14444c896c243702b63 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sat, 14 Mar 2026 22:49:26 +0100 Subject: [PATCH 08/32] update still broken --- lib/src/core/env.valk | 4 +- lib/src/core/errors.valk | 4 ++ lib/src/core/mutex.valk | 6 +-- lib/src/core/sysinfo.valk | 8 +-- lib/src/crypto/bcrypt.valk | 11 ++-- lib/src/crypto/blake2b.valk | 12 ++--- lib/src/crypto/blowfish.valk | 6 +-- lib/src/crypto/error.valk | 2 + lib/src/crypto/radix64.valk | 6 +-- lib/src/fs/file.valk | 82 +++++++++++++++--------------- lib/src/fs/in-memory.valk | 7 +-- lib/src/fs/path.valk | 6 +-- lib/src/fs/stream.valk | 16 +++--- lib/src/gc/blocks.valk | 8 +-- lib/src/gc/lifo.valk | 4 +- lib/src/gc/ptr-ring.valk | 4 +- lib/src/http/client-request.valk | 32 ++++++------ lib/src/http/client-short.valk | 58 ++++----------------- lib/src/http/connection.valk | 14 ++--- lib/src/http/error.valk | 6 +++ lib/src/http/parser.valk | 58 ++++++++++----------- lib/src/http/response-writer.valk | 8 ++- lib/src/http/server.valk | 10 ++-- lib/src/io/error.valk | 2 + lib/src/io/io.valk | 44 ++++++++-------- lib/src/io/uring.valk | 4 +- lib/src/mem/mem.valk | 16 +++--- lib/src/net/addrinfo.valk | 6 +-- lib/src/net/connection.valk | 41 ++++++++------- lib/src/net/error.valk | 4 ++ lib/src/net/io.valk | 80 ++++++++++++++--------------- lib/src/net/socket-server.valk | 8 ++- lib/src/net/socket.valk | 30 ++++++----- lib/src/thread/suspend-gate.valk | 2 +- lib/src/thread/task.valk | 10 ++-- lib/src/thread/thread.valk | 12 ++--- lib/src/type/ByteBuffer.valk | 10 ++-- lib/src/type/array.valk | 24 ++++----- lib/src/type/c-string.valk | 4 +- lib/src/type/flatmap.valk | 16 +++--- lib/src/type/hashmap.valk | 10 ++-- lib/src/type/pool.valk | 4 +- lib/src/type/ptr.valk | 22 ++++---- lib/src/type/slice.valk | 8 +-- lib/src/type/string-char-step.valk | 4 +- lib/src/type/string.valk | 70 ++++++++++++------------- src/build/ast.valk | 6 ++- src/build/error-handling.valk | 2 +- src/build/error-type.valk | 18 +++++-- src/build/ir-type.valk | 1 + src/build/offset.valk | 5 +- src/build/parser.valk | 1 + src/build/scope.valk | 2 +- src/build/stage-1-fc.valk | 2 +- src/build/value.valk | 13 +++-- src/lsp/sighelp.valk | 8 ++- tests/coros.valk | 13 +++-- tests/error-handlers.valk | 34 +++++++------ tests/success-handlers.valk | 12 +++-- 59 files changed, 458 insertions(+), 462 deletions(-) create mode 100644 lib/src/crypto/error.valk create mode 100644 lib/src/http/error.valk create mode 100644 lib/src/io/error.valk create mode 100644 lib/src/net/error.valk diff --git a/lib/src/core/env.valk b/lib/src/core/env.valk index cff019c9..be065949 100644 --- a/lib/src/core/env.valk +++ b/lib/src/core/env.valk @@ -1,10 +1,10 @@ use ext -+ fn getenv(var: String) String !not_found { ++ fn getenv(var: String) String !LookupError { let res = ext:getenv(var.data_cstring) if isset(res) { return res.to_string() } - throw not_found + throw .missing } diff --git a/lib/src/core/errors.valk b/lib/src/core/errors.valk index 8cf1b651..8c721f86 100644 --- a/lib/src/core/errors.valk +++ b/lib/src/core/errors.valk @@ -1,3 +1,7 @@ error SomeError (error) +error ExternError (error) error IterError (end) +error InitError (init) +error LookupError (missing, exists, range, empty) +error SyntaxError (syntax) diff --git a/lib/src/core/mutex.valk b/lib/src/core/mutex.valk index c158f5ea..3da9418e 100644 --- a/lib/src/core/mutex.valk +++ b/lib/src/core/mutex.valk @@ -21,12 +21,12 @@ class MutexWait { - waits: Array[MutexWait] (.new(128)) #end - + static fn new() SELF !create { + + static fn new() SELF !InitError { let m = SELF {} #if OS == linux || OS == macos let res = ext:pipe(@ref(m.pipe)) - if res != 0 : throw create - io:write_string(m.pipe[1], "v") ! throw create + if res != 0 : throw .init + io:write_string(m.pipe[1], "v") ! throw .init #if OS == macos io:set_non_block(m.pipe[0], true) #end diff --git a/lib/src/core/sysinfo.valk b/lib/src/core/sysinfo.valk index 282f615c..8d4a8b64 100644 --- a/lib/src/core/sysinfo.valk +++ b/lib/src/core/sysinfo.valk @@ -1,16 +1,16 @@ use ext -fn cpu_core_count() uint !unknown { - let res = cpu_thread_count() ! throw unknown +fn cpu_core_count() uint !ExternError { + let res = cpu_thread_count() ! throw .error res = res / 2 if res == 0 : return 1 return res } -fn cpu_thread_count() uint !unknown { +fn cpu_thread_count() uint !ExternError { #if OS == linux let res = ext:sysconf(SYSCONF._SC_NPROCESSORS_ONLN) - if res < 0 : throw unknown + if res < 0 : throw .error return res.to(uint) #else throw unknown diff --git a/lib/src/crypto/bcrypt.valk b/lib/src/crypto/bcrypt.valk index 104f604d..909fb8de 100644 --- a/lib/src/crypto/bcrypt.valk +++ b/lib/src/crypto/bcrypt.valk @@ -95,14 +95,11 @@ value BCRYPT_MAX_COST (31) } // Expensive key setup -fn eks_blowfish_setup(context: BlowfishContext, cost: uint, salt: ByteBuffer, password: ByteBuffer) !invalid_salt !invalid_password { +fn eks_blowfish_setup(context: BlowfishContext, cost: uint, salt: ByteBuffer, password: ByteBuffer) !CryptoError { // Initialize Blowfish state blowfish_init_state(context) // Perform the first key expansion - blowfish_expand_key(context, salt, password) ! match(E) { - E.invalid_salt => throw invalid_salt - E.invalid_key => throw invalid_password - } + blowfish_expand_key(context, salt, password) !> // The cost parameter specifies a key expansion iteration count as a power of two let n : u32 = 1.to(u32) << cost.to(u32) @@ -110,9 +107,9 @@ fn eks_blowfish_setup(context: BlowfishContext, cost: uint, salt: ByteBuffer, pa let i : u32 = 0 while i < n { // Perform key expansion with password - blowfish_expand_key(context, null, password) ! throw invalid_password + blowfish_expand_key(context, null, password) !> // Perform key expansion with salt - blowfish_expand_key(context, null, salt) ! throw invalid_salt + blowfish_expand_key(context, null, salt) !> i++ } } diff --git a/lib/src/crypto/blake2b.valk b/lib/src/crypto/blake2b.valk index 096be39c..54f57379 100644 --- a/lib/src/crypto/blake2b.valk +++ b/lib/src/crypto/blake2b.valk @@ -52,9 +52,9 @@ macro LOAD64LE "(" V:val ")" <{ buf_len: uint (0) hash_size: uint - + static fn hash_str(input: String, key: ?String (null), lowercase: bool (true)) String !invalid_key { + + static fn hash_str(input: String, key: ?String (null), lowercase: bool (true)) String !CryptoError { let result : [u8 x 64] = {0...} - let b = Blake2b.new(64, key) ! throw invalid_key + let b = Blake2b.new(64, key) ! throw .invalid_input let data = input.data b.update(data, input.length) b.finalize(result) @@ -75,14 +75,14 @@ macro LOAD64LE "(" V:val ")" <{ return r } - + static fn new(hash_size: uint, key: ?String (null)) Blake2b !invalid_hash_size !invalid_key { - if hash_size == 0 : throw invalid_hash_size - if hash_size > 64 : throw invalid_hash_size + + static fn new(hash_size: uint, key: ?String (null)) Blake2b !CryptoError { + if hash_size == 0 : throw .invalid_input + if hash_size > 64 : throw .invalid_input let keylen = 0 if isset(key) { keylen = key.length - if keylen > 64 : throw invalid_key + if keylen > 64 : throw .invalid_input } let state : [u64 x 8] = IV diff --git a/lib/src/crypto/blowfish.valk b/lib/src/crypto/blowfish.valk index 3db40343..b9352dc5 100644 --- a/lib/src/crypto/blowfish.valk +++ b/lib/src/crypto/blowfish.valk @@ -210,7 +210,7 @@ value BLOWFISH_BLOCK_SIZE (8) } -+ fn blowfish_expand_key(context: BlowfishContext, salt: ?ByteBuffer, key: ByteBuffer) !invalid_salt !invalid_key { ++ fn blowfish_expand_key(context: BlowfishContext, salt: ?ByteBuffer, key: ByteBuffer) !CryptoError { let i : uint = 0 let value : u32 = 0 let keyIndex : uint = 0 @@ -219,11 +219,11 @@ value BLOWFISH_BLOCK_SIZE (8) // Check the length of the salt let saltlen = isset(salt) ? salt.length : 0 - if saltlen != 0 && saltlen < BLOWFISH_BLOCK_SIZE : throw invalid_salt + if saltlen != 0 && saltlen < BLOWFISH_BLOCK_SIZE : throw .invalid_input // Check the length of the key let keylen = key.length - if keylen == 0 : throw invalid_key + if keylen == 0 : throw .invalid_input // In the first phase of setting up the P-array, the secret key K is cyclically XOR'd into the P-array while i < 18 { diff --git a/lib/src/crypto/error.valk b/lib/src/crypto/error.valk new file mode 100644 index 00000000..3d2fed38 --- /dev/null +++ b/lib/src/crypto/error.valk @@ -0,0 +1,2 @@ + +error CryptoError (invalid_input) \ No newline at end of file diff --git a/lib/src/crypto/radix64.valk b/lib/src/crypto/radix64.valk index c40cd9cc..070988c1 100644 --- a/lib/src/crypto/radix64.valk +++ b/lib/src/crypto/radix64.valk @@ -68,14 +68,14 @@ fn radix64_encode(input: ByteBuffer, output: ByteBuffer) { } } -fn radix64_decode(input: ByteBuffer, output: ByteBuffer) !invalid_input { +fn radix64_decode(input: ByteBuffer, output: ByteBuffer) !CryptoError { let value : u32 = 0 let i : uint = 0 output.clear() // Check the length of the input string let len = input.length - if (len % 4) == 1 : throw invalid_input + if (len % 4) == 1 : throw .invalid_input // Process the Radix64-encoded string while i < len { @@ -98,7 +98,7 @@ fn radix64_decode(input: ByteBuffer, output: ByteBuffer) !invalid_input { } } else { // Implementations must reject the encoded data if it contains characters outside the base alphabet - throw invalid_input + throw .invalid_input } i++ } diff --git a/lib/src/fs/file.valk b/lib/src/fs/file.valk index a220b5e9..dfb5e929 100644 --- a/lib/src/fs/file.valk +++ b/lib/src/fs/file.valk @@ -8,10 +8,10 @@ use core value default_read_size (32 * 1024) -+ fn open(path: String, writable: bool, append_on_write: bool) FD !open { - return open_extend(path, writable, append_on_write, false) ! throw open ++ fn open(path: String, writable: bool, append_on_write: bool) FD !io:IoError { + return open_extend(path, writable, append_on_write, false) ! throw .open } -+ fn open_extend(path: String, writable: bool, append_on_write: bool, create_file_if_doesnt_exist: bool (false), create_file_permissions: u32 (0c644)) FD !open !access { ++ fn open_extend(path: String, writable: bool, append_on_write: bool, create_file_if_doesnt_exist: bool (false), create_file_permissions: u32 (0c644)) FD !io:IoError { // let flags : i32 = (ext:O_RDONLY | ext:O_NONBLOCK).@cast(i32) #if OS == win @@ -44,7 +44,7 @@ value default_read_size (32 * 1024) let fd = ext:open(path.data_cstring, flags, create_file_permissions.to(i32)) if fd < 0 { - throw open + throw .open } #end @@ -52,9 +52,9 @@ value default_read_size (32 * 1024) return fd.@cast(FD) } -- fn stat(path: String, buf: ext:libc_stat) !fail { +- fn stat(path: String, buf: ext:libc_stat) !io:IoError { let res = ext:stat(path.data_cstring, buf) - if res == -1 : throw fail + if res == -1 : throw .open } + fn size(path: String) uint { let buf = @stack() @@ -66,9 +66,9 @@ value default_read_size (32 * 1024) ext:sync() } -+ fn read(path: String) String !open !read !close { ++ fn read(path: String) String !io:IoError { - let fd = open(path, false, false) ! throw open + let fd = open(path, false, false) ! throw .open let size : uint = size(path) let offset : uint = 0 let buffer = ByteBuffer.new(default_read_size) @@ -76,7 +76,7 @@ value default_read_size (32 * 1024) while offset < size { let readcount = io:read(fd, buffer, default_read_size, offset) ! { io:close(fd) - throw read + throw .read } offset += readcount } @@ -85,20 +85,20 @@ value default_read_size (32 * 1024) return buffer } -+ fn write(path: String, content: String, append: bool (false)) !open !write { - let fd = open_extend(path, true, append, true) ! throw open ++ fn write(path: String, content: String, append: bool (false)) !io:IoError { + let fd = open_extend(path, true, append, true) ! throw .open io:write_string(fd, content) ! { io:close(fd) - throw write + throw .write } io:close(fd) } -+ fn write_from_ptr(path: String, data: ptr, size: uint, append: bool (false)) !open !write { - let fd = open_extend(path, true, append, true) ! throw open ++ fn write_from_ptr(path: String, data: ptr, size: uint, append: bool (false)) !io:IoError { + let fd = open_extend(path, true, append, true) ! throw .open io:write_from_ptr(fd, data, size) ! { io:close(fd) - throw write + throw .write } io:close(fd) } @@ -111,10 +111,10 @@ value default_read_size (32 * 1024) return ext:access(path.data_cstring, ext:F_OK) == 0 #end } -+ fn delete(path: String) !delete { ++ fn delete(path: String) !io:IoError { // ext:unlink(path.data_cstring) let res = ext:unlink(path.data_cstring) - if res == -1 : throw delete + if res == -1 : throw .access } + fn delete_recursive(path: String) { let isdir = is_dir(path) @@ -136,29 +136,29 @@ value default_read_size (32 * 1024) delete(path) _ } } -+ fn move(from_path: String, to_path: String) !fail { ++ fn move(from_path: String, to_path: String) !io:IoError { let res = ext:rename(from_path.data_cstring, to_path.data_cstring) - if res != 0 : throw fail + if res != 0 : throw .access } -+ fn copy(from_path: String, to_path: String, recursive: bool (false)) !fail { ++ fn copy(from_path: String, to_path: String, recursive: bool (false)) !io:IoError { if from_path == to_path : return let isdir = is_dir(from_path) if isdir { - if !exists(to_path) : mkdir(to_path) ! throw fail - else if !is_dir(to_path) : throw fail + if !exists(to_path) : mkdir(to_path) ! throw .access + else if !is_dir(to_path) : throw .open // Copy files let files = files_in(from_path, false, true, true, "") each files as fn { let from = add(from_path, fn) let to = add(to_path, fn) - copy(from, to, recursive) ! throw fail + copy(from, to, recursive) ! throw .write } } else { let buf = ByteBuffer.new(32000) - let in = open_extend(from_path, false, false) ! throw fail - let out = open_extend(to_path, true, false, true) ! throw fail + let in = open_extend(from_path, false, false) ! throw .open + let out = open_extend(to_path, true, false, true) ! throw .open let offset : uint = 0 @@ -166,7 +166,7 @@ value default_read_size (32 * 1024) let rcount = io:read(in, buf, 32000, offset) ! { io:close(in) io:close(out) - throw fail + throw .read } if rcount == 0 { io:close(in) @@ -176,7 +176,7 @@ value default_read_size (32 * 1024) io:write(out, buf, buf.length) ! { io:close(in) io:close(out) - throw fail + throw .write } buf.clear() offset += rcount @@ -189,13 +189,13 @@ value default_read_size (32 * 1024) } } -+ fn mkdir(path: String, permissions: u32 (0c755)) !fail { ++ fn mkdir(path: String, permissions: u32 (0c755)) !io:IoError { if ext:mkdir(path.data_cstring, permissions) == -1 { - throw fail + throw .access } } -+ fn rmdir(path: String) !fail { - if ext:rmdir(path.data_cstring) == -1 : throw fail ++ fn rmdir(path: String) !io:IoError { + if ext:rmdir(path.data_cstring) == -1 : throw .access } + fn is_file(path: String) bool { @@ -208,12 +208,12 @@ value default_read_size (32 * 1024) stat(path, buf) ! return false return (buf.st_mode & ext:S_IFDIR) == ext:S_IFDIR } -+ fn modified_time(path: String) uint !file_not_found { ++ fn modified_time(path: String) uint !io:IoError { #if OS == win let ftMod : = @undefined let hndl = ext:CreateFileA(path, ext:FILE_READ_ATTRIBUTES, ext:FILE_SHARE_READ | ext:FILE_SHARE_WRITE, null, ext:OPEN_EXISTING, ext:FILE_FLAG_BACKUP_SEMANTICS, 0) - if hndl == ext:INVALID_HANDLE_VALUE : throw file_not_found - if !ext:GetFileTime(hndl, null, null, &ftMod) : throw file_not_found + if hndl == ext:INVALID_HANDLE_VALUE : throw .open + if !ext:GetFileTime(hndl, null, null, &ftMod) : throw .open // FILETIME = 100-ns ticks since 1601-01-01 UTC let ticks : u64 = (ftMod.dwHighDateTime.@cast(u64) << 32) | ftMod.dwLowDateTime // Subtract difference between 1601-01-01 and 1970-01-01 @@ -221,7 +221,7 @@ value default_read_size (32 * 1024) return ns100 * 100 // to nanoseconds #else let buf = @stack() - stat(path, buf) ! throw file_not_found + stat(path, buf) ! throw .open #if OS == linux let ns = buf.st_mtim.tv_sec.to(u64) * 1_000_000_000 + buf.st_mtim.tv_nsec #elif OS == macos @@ -310,20 +310,20 @@ value default_read_size (32 * 1024) } -+ fn symlink(link: String, target: String, is_directory: bool) !permissions !exists !other { ++ fn symlink(link: String, target: String, is_directory: bool) !io:IoError { #if OS == win let flag : u32 = ext:SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE if is_directory : flag = flag | ext:SYMBOLIC_LINK_FLAG_DIRECTORY if ext:CreateSymbolicLinkA(link.data_cstring, target.data_cstring, flag) == 1 : return let error = ext:GetLastError() - if error == ext:ERROR_PRIVILEGE_NOT_HELD : throw permissions - if error == ext:ERROR_ALREADY_EXISTS : throw exists + if error == ext:ERROR_PRIVILEGE_NOT_HELD : throw .access + if error == ext:ERROR_ALREADY_EXISTS : throw .exists #else if ext:symlink(target.data_cstring, link.data_cstring) == 0 : return let error = ext:errno - if error == ext:EACCES : throw permissions - if error == ext:EEXIST : throw exists + if error == ext:EACCES : throw .access + if error == ext:EEXIST : throw .exists #end - throw other + throw .write } diff --git a/lib/src/fs/in-memory.valk b/lib/src/fs/in-memory.valk index c8b15ac0..da8a09f6 100644 --- a/lib/src/fs/in-memory.valk +++ b/lib/src/fs/in-memory.valk @@ -1,5 +1,6 @@ use mem +use io + class InMemoryFile { ~ data: ptr @@ -21,10 +22,10 @@ use mem + static fn create_from_buffer(buffer: ByteBuffer) SELF { return SELF.create_from_ptr(buffer.data, buffer.length) } - + static fn create_from_file(path: String) SELF !load { + + static fn create_from_file(path: String) SELF !io:IoError { let buffer = ByteBuffer.new(128) - let stream = stream(path, true, false) ! throw load - while stream.read(32000, buffer) ! throw load {} + let stream = stream(path, true, false) ! throw .open + while stream.read(32000, buffer) ! throw .read {} return SELF.create_from_buffer(buffer) } diff --git a/lib/src/fs/path.valk b/lib/src/fs/path.valk index 28a95da4..caf70e4d 100644 --- a/lib/src/fs/path.valk +++ b/lib/src/fs/path.valk @@ -204,9 +204,9 @@ use core return path.part(start, len - start - rtrim) } -+ fn home_dir() String !not_found { ++ fn home_dir() String !ExternError { #if OS == win - return core:getenv("USERPROFILE") ! throw not_found + return core:getenv("USERPROFILE") ! throw .error #end - return core:getenv("HOME") ! throw not_found + return core:getenv("HOME") ! throw .error } diff --git a/lib/src/fs/stream.valk b/lib/src/fs/stream.valk index c90e501d..c10d090e 100644 --- a/lib/src/fs/stream.valk +++ b/lib/src/fs/stream.valk @@ -1,8 +1,8 @@ use io -+ fn stream(path: String, read: bool, write: bool, append: bool (false), auto_create: bool (false)) FileStream !err_open { - let fd = open_extend(path, write, append, auto_create) ! throw err_open ++ fn stream(path: String, read: bool, write: bool, append: bool (false), auto_create: bool (false)) FileStream !io:IoError { + let fd = open_extend(path, write, append, auto_create) ! throw .open return FileStream{ path: path, fd: fd, can_read: read, can_write: write } } @@ -14,26 +14,26 @@ use io ~ reading: bool (true) + read_offset: uint (0) - + fn read(bytes: uint (10240), buffer: ByteBuffer) bool !read_err { + + fn read(bytes: uint (10240), buffer: ByteBuffer) bool !io:IoError { if !this.can_read || !this.reading : return false - let rcount = io:read(this.fd, buffer, bytes, this.read_offset) ! throw read_err + let rcount = io:read(this.fd, buffer, bytes, this.read_offset) ! throw .read if rcount == 0 : this.close() this.read_offset += rcount return true } - + fn write(str: String) !write_err { + + fn write(str: String) !io:IoError { this.write_from_ptr(str.data, str.length) !> } - + fn write_buffer(buffer: ByteBuffer) !write_err { + + fn write_buffer(buffer: ByteBuffer) !io:IoError { this.write_from_ptr(buffer.data, buffer.length) !> } - + fn write_from_ptr(from: ptr, len: uint) !write_err { + + fn write_from_ptr(from: ptr, len: uint) !io:IoError { let rcount : uint = 0 while len - rcount > 0 { - rcount += io:write_from_ptr(this.fd, from + rcount, len - rcount) ! throw write_err + rcount += io:write_from_ptr(this.fd, from + rcount, len - rcount) ! throw .write } } diff --git a/lib/src/gc/blocks.valk b/lib/src/gc/blocks.valk index a7940c52..d91a76eb 100644 --- a/lib/src/gc/blocks.valk +++ b/lib/src/gc/blocks.valk @@ -57,15 +57,15 @@ fn add_unused_block(block: Block) { list.add_ptr(block) } -fn get_unused_block(isize: uint) ptr !none { +fn get_unused_block(isize: uint) ptr !LookupError { let index = get_pool_index_for_size(isize) let list = @ptrv(unused_blocks, ?Bump, index) - if !isset(list) : throw none - if list.index == 0 : throw none + if !isset(list) : throw .empty + if list.index == 0 : throw .empty pool_lock.lock() if list.index == 0 { pool_lock.unlock() - throw none + throw .empty } list.index -= size_of(ptr) let block = @ptrv(list.data, Block, list.index / size_of(ptr)) diff --git a/lib/src/gc/lifo.valk b/lib/src/gc/lifo.valk index bb8f184c..a92769e7 100644 --- a/lib/src/gc/lifo.valk +++ b/lib/src/gc/lifo.valk @@ -28,9 +28,9 @@ struct Lifo { this.size = new_size } } - fn pop() ptr !empty { + fn pop() ptr !LookupError { let index = this.index - if index == 0 : throw empty + if index == 0 : throw .empty let new_index = index - 1 this.index = new_index return @ptrv(this.data, ptr, new_index) diff --git a/lib/src/gc/ptr-ring.valk b/lib/src/gc/ptr-ring.valk index 12355384..195b5425 100644 --- a/lib/src/gc/ptr-ring.valk +++ b/lib/src/gc/ptr-ring.valk @@ -32,10 +32,10 @@ class PtrRing { this.head = new_head } - fn pop() ptr !empty { + fn pop() ptr !LookupError { let slots = this.slots let tail = this.tail - if tail == this.head : throw empty + if tail == this.head : throw .empty let new_tail = tail + 1 if new_tail == slots : new_tail = 0 this.tail = new_tail diff --git a/lib/src/http/client-request.valk b/lib/src/http/client-request.valk index 200f89d7..b058f6ef 100644 --- a/lib/src/http/client-request.valk +++ b/lib/src/http/client-request.valk @@ -27,7 +27,7 @@ use url // Request initialization ////////////////////////////// - + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !connection !ssl !invalid_output_path { + + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !HttpError { // Validate URL let u = url:parse(url) @@ -36,20 +36,20 @@ use url let split = u.host.split(":") let host = u.host let port : u16 = is_https ? 443 : 80 - if split.length > 2 : throw invalid_url + if split.length > 2 : throw .invalid_url if split.length == 2 { host = split.get(0) !? "" - let p = split.get(1) ! throw invalid_url - port = (p.to_uint() ! throw invalid_url).@cast(u16) + let p = split.get(1) ! throw .invalid_url + port = (p.to_uint() ! throw .invalid_url).@cast(u16) } // Connect - let con = net:Socket.client(net:SOCKET_TYPE.TCP, host, port) ! throw connection + let con = net:Socket.client(net:SOCKET_TYPE.TCP, host, port) !> if is_https { con.ssl_connect(host, net:ca_cert) ! { con.close() - throw ssl + throw .ssl } } @@ -61,7 +61,7 @@ use url if isset(options) { let out_path = options.output_to_file if isset(out_path) { - let file = fs:stream(out_path, true, true, false, true) ! throw invalid_output_path + let file = fs:stream(out_path, true, true, false, true) !> ctx.save_to_file = file } } @@ -154,7 +154,7 @@ use url // this.recv_buffer.reduce_size(2048) } - + fn progress() bool !disconnect !invalid_response { + + fn progress() bool !HttpError { if this.response_received : return false @@ -164,7 +164,7 @@ use url // Send request let sent_count = con.send_buffer(this.payload, this.bytes_sent, false) ! { this.stop() - throw disconnect + throw .write } this.bytes_sent += sent_count this.sent_percent = this.bytes_sent * 100 / this.bytes_to_send @@ -183,7 +183,7 @@ use url if !context.done { let bytes = con.recv(recv_buf, 1024 * 32) ! { this.stop() - throw invalid_response, EMSG + throw .read } this.bytes_received += bytes if context.content_length > 0 { @@ -192,13 +192,13 @@ use url if bytes == 0 { this.stop() - throw invalid_response, "Empty packet" + throw .read } // Parse bytes parse_http(recv_buf, context, true) ! { - if error_is(E, incomplete) : return true + if E.code == E.incomplete : return true this.stop() - throw invalid_response, "Invalid HTTP syntax" + throw .invalid_response } this.recv_percent = 100 @@ -217,8 +217,8 @@ use url return false } - + fn response() ClientResponse !in_progress !invalid_response { - if !this.response_received : throw in_progress - return this.resp ?! throw invalid_response + + fn response() ClientResponse !HttpError { + if !this.response_received : throw .in_progress + return this.resp ?! throw .invalid_response } } diff --git a/lib/src/http/client-short.valk b/lib/src/http/client-short.valk index a910c936..70943c00 100644 --- a/lib/src/http/client-short.valk +++ b/lib/src/http/client-short.valk @@ -1,70 +1,30 @@ -+ fn create_request(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !ssl !connect { - - return ClientRequest.create(method, url, options) ! { - match E { - E.invalid_url => throw invalid_url, EMSG - E.ssl => throw ssl, EMSG - default => throw connect, EMSG - } - } ++ fn create_request(method: String, url: String, options: ?Options (null)) ClientRequest !HttpError { + return ClientRequest.create(method, url, options) !> } -+ fn request(method: String, url: String, options: ?Options (null)) ClientResponse - !invalid_url !invalid_output_path !ssl !connect !disconnect !invalid_response -{ ++ fn request(method: String, url: String, options: ?Options (null)) ClientResponse !HttpError { - let req = ClientRequest.create(method, url, options) ! { - match E { - E.invalid_output_path => throw invalid_output_path, EMSG - E.invalid_url => throw invalid_url, EMSG - E.ssl => throw ssl, EMSG - default => throw connect, EMSG - } - } + let req = ClientRequest.create(method, url, options) !> - while (req.progress() ! { - match E { - E.disconnect => throw disconnect, EMSG - default => throw invalid_response, EMSG - } - }) {} + while (req.progress() !>) {} - let res = req.response() ! throw invalid_response + let res = req.response() !> if res.status == 301 { let follow = !isset(options) if isset(options) : follow = options.follow_redirects if follow { let location = res.headers.get("location") !? null if isset(location) { - return request(method, location, options) ! { - match E { - E.invalid_output_path => throw invalid_output_path, EMSG - E.invalid_url => throw invalid_url, EMSG - E.ssl => throw ssl, EMSG - default => throw connect, EMSG - } - } + return request(method, location, options) !> } } } return res } -+ fn download(url: String, to_path: String, method: String ("GET"), options: ?Options (null)) - !invalid_path !invalid_url !ssl !connect !disconnect !invalid_response -{ ++ fn download(url: String, to_path: String, method: String ("GET"), options: ?Options (null)) !HttpError { if !isset(options) : options = Options{} options.output_to_file = to_path - - request(method, url, options) ! { - match E { - E.invalid_output_path => throw invalid_path, EMSG - E.invalid_url => throw invalid_url, EMSG - E.ssl => throw ssl, EMSG - E.connect => throw connect, EMSG - E.disconnect => throw disconnect, EMSG - default => throw invalid_response, EMSG - } - } + request(method, url, options) !> } diff --git a/lib/src/http/connection.valk b/lib/src/http/connection.valk index 1f48d450..99886deb 100644 --- a/lib/src/http/connection.valk +++ b/lib/src/http/connection.valk @@ -30,12 +30,10 @@ value READ_SIZE (32000) parse_http(input, context, false) ! { // Check if we need more data - if error_is(E, incomplete) { - let bytes = this.read_more(input) ! { - break - } + if E.code == E.incomplete { + let bytes = this.read_more(input) ! break if bytes > 0 : continue - } else if error_is(E, http413) { + } else if E.code == E.http413 { this.send_error(413, res) } else { this.send_error(400, res) @@ -114,10 +112,8 @@ value READ_SIZE (32000) // atomic(server.connections - 1) } - fn read_more(buffer: ByteBuffer) uint !err { - let bytes = this.netcon.recv(buffer, READ_SIZE) ! { - throw err - } + fn read_more(buffer: ByteBuffer) uint !net:NetError { + let bytes = this.netcon.recv(buffer, READ_SIZE) !> return bytes } diff --git a/lib/src/http/error.valk b/lib/src/http/error.valk new file mode 100644 index 00000000..10d6b64f --- /dev/null +++ b/lib/src/http/error.valk @@ -0,0 +1,6 @@ + +use net +use io + +error HttpParseError (invalid, http413, incomplete, missing_host_header) extends (io:IoError) +error HttpError (invalid_url, invalid_response, in_progress) extends (net:NetError, HttpParseError) diff --git a/lib/src/http/parser.valk b/lib/src/http/parser.valk index 6963364e..e8ff47ee 100644 --- a/lib/src/http/parser.valk +++ b/lib/src/http/parser.valk @@ -1,12 +1,12 @@ use mem -+ fn parse_http(input: ByteBuffer, context: Context, is_response: bool) !invalid !http413 !incomplete !missing_host_header !file_error { ++ fn parse_http(input: ByteBuffer, context: Context, is_response: bool) !HttpParseError { if context.done : return let pos = context.parsed_index - if pos >= input.length : throw incomplete + if pos >= input.length : throw .incomplete let stage = context.stage let data = input.data @@ -15,33 +15,33 @@ use mem // Header if stage == 0 { let index = input.index_of('\r', pos) ! { - if length - pos > 4096 : throw http413 - throw incomplete + if length - pos > 4096 : throw .http413 + throw .incomplete } // let version = "" if is_response { - let sp1 = input.index_of(' ', pos) ! throw invalid + let sp1 = input.index_of(' ', pos) ! throw .invalid sp1++ - let sp2 = input.index_of(' ', sp1) ! throw invalid + let sp2 = input.index_of(' ', sp1) ! throw .invalid sp2++ - if sp1 >= index || sp2 >= index : throw invalid + if sp1 >= index || sp2 >= index : throw .invalid // Status let len = sp2 - sp1 - 1 - context.status = (input.data.@offset(sp1)).read_uint_value(len) ! throw invalid + context.status = (input.data.@offset(sp1)).read_uint_value(len) ! throw .invalid // Protocol // let proto = input.part(pos, sp1 - pos - 1) } else { - if index + 1 == length : throw incomplete - if @ptrv(data, u8, index + 1) != '\n' { throw invalid } + if index + 1 == length : throw .incomplete + if @ptrv(data, u8, index + 1) != '\n' { throw .invalid } - let space1 = input.index_of(' ', pos) ! { throw invalid } - let space2 = input.index_of(' ', space1 + 1) ! { throw invalid } + let space1 = input.index_of(' ', pos) ! { throw .invalid } + let space2 = input.index_of(' ', space1 + 1) ! { throw .invalid } - if space1 > index { throw invalid } - if space2 > index { throw invalid } + if space1 > index { throw .invalid } + if space2 > index { throw .invalid } // Request "GET / HTTP/1.1" let method = context.method @@ -87,11 +87,11 @@ use mem // Headers while stage == 1 { let index = input.index_of('\r', pos) ! { - if input.length - pos > 4096 : throw http413 - throw incomplete + if input.length - pos > 4096 : throw .http413 + throw .incomplete } - if index + 1 == length : throw incomplete - if @ptrv(data, u8, index + 1) != '\n' : throw invalid + if index + 1 == length : throw .incomplete + if @ptrv(data, u8, index + 1) != '\n' : throw .invalid if index == pos { // End of headers @@ -102,7 +102,7 @@ use mem context.stage = stage // Host header = required - if !is_response && !context.has_host : throw missing_host_header + if !is_response && !context.has_host : throw .missing_host_header hdata.length += 2 bdata.offset = pos @@ -111,10 +111,10 @@ use mem let key_adr = input.data.@offset(pos) let header_len = index - pos - if header_len > 4095 : throw http413 + if header_len > 4095 : throw .http413 let headers_length = hdata.length + header_len + 2 - if headers_length > 8192 : throw http413 + if headers_length > 8192 : throw .http413 hdata.length = headers_length // Check certain headers @@ -127,7 +127,7 @@ use mem let value_adr = key_adr.$offset(h1.bytes + 1); while @ptrv(value_adr, u8).is_html_spacing() : value_adr = value_adr.$offset(1) let value_len = input.data.@cast(uint) + index - value_adr.@cast(uint) - let content_len = mem:bytes_to_uint(value_adr, value_len) ! { throw invalid } + let content_len = mem:bytes_to_uint(value_adr, value_len) ! { throw .invalid } bdata.length = content_len context.content_length = content_len } @@ -166,10 +166,10 @@ use mem let chlen = bdata.length if chlen == 0 { let index = input.index_of('\r', pos) ! { - throw incomplete + throw .incomplete } let line = input.part(pos, index - pos) - let len = (line.hex_to_uint() ! throw invalid) + 2 + let len = (line.hex_to_uint() ! throw .invalid) + 2 pos = index + 2 context.parsed_index = pos @@ -186,7 +186,7 @@ use mem bdata.length = len } // Read chunk content - if input_left < chlen : throw incomplete + if input_left < chlen : throw .incomplete let part_len = chlen - 2 context.body_received += part_len @@ -194,7 +194,7 @@ use mem // Save to file or append to body buffer let file = context.save_to_file if isset(file) { - file.write_from_ptr(input.data.@offset(pos), part_len) ! throw file_error + file.write_from_ptr(input.data.@offset(pos), part_len) !> } else { chunks.append_from_ptr(input.data.@offset(pos), part_len) } @@ -220,15 +220,13 @@ use mem context.body_received += body_bytes if body_bytes > 0 { - file.write_from_ptr(input.data.@offset(pos), body_bytes) ! { - throw file_error - } + file.write_from_ptr(input.data.@offset(pos), body_bytes) !> input.clear_part(pos, body_bytes) } } if input_left < content_len && context.body_received < content_len { - throw incomplete + throw .incomplete } pos += content_len diff --git a/lib/src/http/response-writer.valk b/lib/src/http/response-writer.valk index faae579c..319ee770 100644 --- a/lib/src/http/response-writer.valk +++ b/lib/src/http/response-writer.valk @@ -110,7 +110,7 @@ use net return this.count_bytes_to_send() > (128 * 1024) || this.file_response != null } - - fn send_bytes(connection: net:Connection) !write { + - fn send_bytes(connection: net:Connection) !HttpError { let out = this.output let file = this.file_response @@ -120,7 +120,7 @@ use net if isset(file) { if this.count_bytes_to_send() == 0 { if file.reading { - file.read(65535, out) ! throw write + file.read(65535, out) !> } if !file.reading { this.file_response = null @@ -133,9 +133,7 @@ use net let bytes = out.length - pos if bytes == 0 : break - connection.send_bytes(out.data.@offset(pos), bytes, true) ! { - throw write - } + connection.send_bytes(out.data.@offset(pos), bytes, true) !> this.output_pos += bytes } } diff --git a/lib/src/http/server.valk b/lib/src/http/server.valk index 5238a3d6..b08c5cf8 100644 --- a/lib/src/http/server.valk +++ b/lib/src/http/server.valk @@ -27,12 +27,10 @@ fn fast_handler_default(req: Context, res: ResponseWriter) { max_server_wide_body_size: uint (2 * 1024 .@cast(uint)) // 2 GB + show_info: bool (false) - + static fn new(host: String, port: u16, handler: fn(Request)(Response)) Server !socket_init_error !socket_bind_error { + + static fn new(host: String, port: u16, handler: fn(Request)(Response)) Server !HttpError { let max_connections : uint = 10000 - let socket = net:Socket.server(net:SOCKET_TYPE.TCP, host, port) ! { - throw socket_init_error - } + let socket = net:Socket.server(net:SOCKET_TYPE.TCP, host, port) !> return Server{ host: host, @@ -76,9 +74,9 @@ fn fast_handler_default(req: Context, res: ResponseWriter) { worker(this) } - + fn add_static_dir(path: String) !notfound { + + fn add_static_dir(path: String) !LookupError { let full = fs:resolve(path) - if !fs:is_dir(path) : throw notfound + if !fs:is_dir(path) : throw .missing if this.show_info : println("[+] Add static dir: " + full) this.static_dirs.append(full) } diff --git a/lib/src/io/error.valk b/lib/src/io/error.valk new file mode 100644 index 00000000..256a916d --- /dev/null +++ b/lib/src/io/error.valk @@ -0,0 +1,2 @@ + +error IoError (open, access, read, write, exists, os) diff --git a/lib/src/io/io.valk b/lib/src/io/io.valk index 1f7c8223..964cb105 100644 --- a/lib/src/io/io.valk +++ b/lib/src/io/io.valk @@ -20,24 +20,24 @@ use mem } // Read -+ fn read(fd: FD, buf: ByteBuffer, amount: uint, offset: uint) uint !fail { ++ fn read(fd: FD, buf: ByteBuffer, amount: uint, offset: uint) uint !IoError { buf.minimum_free_space(amount) let res = read_to_ptr(fd, buf.data.@offset(buf.length), amount, offset) !> buf.length += res return res } -+ fn read_to_ptr_sync(fd: FD, buf: ptr, amount: uint, offset: uint) uint !fail { ++ fn read_to_ptr_sync(fd: FD, buf: ptr, amount: uint, offset: uint) uint !IoError { let rcvd = ext:read(fd.to(i32), buf, amount.to(u32)) - if rcvd < 0 : throw fail + if rcvd < 0 : throw .read return rcvd } -+ fn read_to_ptr(fd: FD, buf: ptr, amount: uint, offset: uint) uint !fail { ++ fn read_to_ptr(fd: FD, buf: ptr, amount: uint, offset: uint) uint !IoError { let coro = coro:current_coro if !isset(coro) : return read_to_ptr_sync(fd, buf, amount, offset) !> #if OS == linux - let sqe = sqe(coro) ! throw fail + let sqe = sqe(coro) ! throw .os io_uring_prep_read(sqe, fd.@cast(i32), buf, amount.to(u32), offset) #elif OS == win @@ -48,16 +48,16 @@ use mem ov.base.sys.OffsetHigh = (offset >> 32).to(u32) let ok = ext:ReadFile(fd, buf, amount.to(u32), null, ov) - if !ok && ext:GetLastError() != ext:ERROR_IO_PENDING : throw fail + if !ok && ext:GetLastError() != ext:ERROR_IO_PENDING : throw .read coro:yield() ok = ext:GetOverlappedResult(fd, ov, &ov.rcvd, true) if !ok || ov.base.sys.Internal != 0 { - throw fail + throw .read } let rcvd = ov.rcvd - if rcvd == 0 : throw fail + if rcvd == 0 : throw .read return rcvd #elif OS == macos @@ -66,10 +66,10 @@ use mem if res < 0 { if ext:errno == ext:EAGAIN { let ev = await_fd(fd, true, false) - if ev.is_closed() || !ev.is_readable() : throw fail + if ev.is_closed() || !ev.is_readable() : throw .read continue } - throw fail + throw .read } return res.to(uint) } @@ -100,39 +100,39 @@ use mem #else let rcvd = coro.completion_res #end - if rcvd < 0 : throw fail + if rcvd < 0 : throw .read return rcvd.@cast(uint) } // Write -+ fn write(fd: FD, buf: ByteBuffer, amount: uint) uint !fail { ++ fn write(fd: FD, buf: ByteBuffer, amount: uint) uint !IoError { if amount > buf.length : amount = buf.length return write_from_ptr(fd, buf.data, amount) !> } -+ fn write_string(fd: FD, str: String) uint !fail { ++ fn write_string(fd: FD, str: String) uint !IoError { return write_from_ptr(fd, str.data, str.length) !> } -+ fn write_from_ptr_sync(fd: FD, buf: ptr, amount: uint) uint !fail { ++ fn write_from_ptr_sync(fd: FD, buf: ptr, amount: uint) uint !IoError { let sent = ext:write(fd.to(i32), buf, amount.to(u32)) - if sent < 0 : throw fail + if sent < 0 : throw .write return sent } -+ fn write_from_ptr(fd: FD, buf: ptr, amount: uint) uint !fail { ++ fn write_from_ptr(fd: FD, buf: ptr, amount: uint) uint !IoError { let coro = coro:current_coro #if OS == linux if !isset(coro) { let sent = ext:write(fd, buf, amount.to(u32)) - if sent < 0 : throw fail + if sent < 0 : throw .write return sent } // - let sqe = sqe(coro) ! throw fail + let sqe = sqe(coro) ! throw .os io_uring_prep_write(sqe, fd.@cast(i32), buf, amount.to(u32), -1) coro:yield() let sent = coro.completion_res - if sent < 0 : throw fail + if sent < 0 : throw .write return sent.@cast(uint) #elif OS == win @@ -145,13 +145,13 @@ use mem let ok = ext:WriteFile(fd, buf, amount.to(u32), null, ov) if !ok && ext:GetLastError() != ext:ERROR_IO_PENDING { - throw fail + throw .write } coro:yield() ok = ext:GetOverlappedResult(fd, ov, &ov.sent, true) if !ok || ov.base.sys.Internal != 0 { - throw fail + throw .write } let sent = ov.sent @@ -159,7 +159,7 @@ use mem #elif OS == macos let sent = ext:write(fd, buf, amount.to(u32)) - if sent < 0 : throw fail + if sent < 0 : throw .write return sent.@cast(uint) #else diff --git a/lib/src/io/uring.valk b/lib/src/io/uring.valk index 19f38d65..d94e4e61 100644 --- a/lib/src/io/uring.valk +++ b/lib/src/io/uring.valk @@ -26,7 +26,7 @@ fn uring_submit(ring: ext:io_uring) uint { } -fn sqe(coro: ?coro:Coro, ring: ?ext:io_uring (null)) ext:io_uring_sqe !full { +fn sqe(coro: ?coro:Coro, ring: ?ext:io_uring (null)) ext:io_uring_sqe !IoError { if !isset(coro) : coro = coro:current_coro if !isset(ring) : ring = uring() let sqe = ext:io_uring_get_sqe(ring) @@ -34,7 +34,7 @@ fn sqe(coro: ?coro:Coro, ring: ?ext:io_uring (null)) ext:io_uring_sqe !full { uring_submit(ring) sqe = ext:io_uring_get_sqe(ring) } - if !isset(sqe) : throw full + if !isset(sqe) : throw .os // Set coro sqe.user_data = coro.@cast(u64) // Submit after x events diff --git a/lib/src/mem/mem.valk b/lib/src/mem/mem.valk index 6ab84bd4..6bb95e7f 100644 --- a/lib/src/mem/mem.valk +++ b/lib/src/mem/mem.valk @@ -83,29 +83,29 @@ use coro } } -+ fn bytes_to_uint(adr: ptr, len: uint) uint !not_a_number { ++ fn bytes_to_uint(adr: ptr, len: uint) uint !SyntaxError { let result : uint = 0 let mult : uint = 1 while len-- > 0 { let ch = @ptrv(adr, u8, len) - if ch < 48 || ch > 57 : throw not_a_number + if ch < 48 || ch > 57 : throw .syntax result += ((ch.@cast(uint)) - 48) * mult mult *= 10 } return result } -+ fn find_char(adr: ptr, ch: u8, length: uint) uint !not_found { ++ fn find_char(adr: ptr, ch: u8, length: uint) uint !LookupError { let i : uint = 0 while i < length { let ii = i if @ptrv(adr, u8, ii) == ch : return ii i = ii + 1 } - throw not_found + throw .missing } -fn find_char_(adr: ptr, ch: u8, length: uint) uint !not_found { +fn find_char_(adr: ptr, ch: u8, length: uint) uint !LookupError { let offset : uint = 0 let end1 : uint = 8 - (adr % 8) // @@ -156,11 +156,11 @@ fn find_char_(adr: ptr, ch: u8, length: uint) uint !not_found { if @ptrv(adr, u8, offset) == ch : return offset offset++ } - throw not_found + throw .missing } // GNU libc version for find_char aka. memchar (same performance, just here in case we need it) -fn gnu_libc_memchar(adr: ptr, ch: u8, length: uint) ptr !not_found { +fn gnu_libc_memchar(adr: ptr, ch: u8, length: uint) ptr !LookupError { while (adr % size_of(ptr) != 0) { if @ptrv(adr, u8) == ch : return adr @@ -187,5 +187,5 @@ fn gnu_libc_memchar(adr: ptr, ch: u8, length: uint) ptr !not_found { adr = adr.$offset(1) } - throw not_found + throw .missing } diff --git a/lib/src/net/addrinfo.valk b/lib/src/net/addrinfo.valk index 9f7f91b1..6f274b08 100644 --- a/lib/src/net/addrinfo.valk +++ b/lib/src/net/addrinfo.valk @@ -6,7 +6,7 @@ use core + class AddrInfo { ~ data: ext:libc_addrinfo_fix - + static fn new(host: String, port: u16) AddrInfo !fail { + + static fn new(host: String, port: u16) AddrInfo !NetError { // WSA init #if OS == win WSA.init() @@ -24,8 +24,8 @@ use core let chost = host.data_cstring let cport = port.to_str().data_cstring let err = ext:getaddrinfo(chost, cport, hints, @ref(addrinfo)) - if err != 0 : throw fail - if !isset(addrinfo) : throw fail + if err != 0 : throw .invalid_host + if !isset(addrinfo) : throw .invalid_host return AddrInfo { data: addrinfo diff --git a/lib/src/net/connection.valk b/lib/src/net/connection.valk index 8b08d7be..a11d3799 100644 --- a/lib/src/net/connection.valk +++ b/lib/src/net/connection.valk @@ -1,5 +1,4 @@ -use ext use io + class Connection { @@ -29,7 +28,7 @@ use io this.close() } - + fn ssl_connect(host: String, ca_cert_path: ?String (null)) !ssl { + + fn ssl_connect(host: String, ca_cert_path: ?String (null)) !NetError { if this.ssl_enabled : return @@ -41,19 +40,19 @@ use io let err = SSL_get_error(ssl.ssl, res) if err == ERROR.WANT_READ { let fd = SSL_get_fd(ssl.ssl) - if fd == -1 : throw ssl + if fd == -1 : throw .ssl io:await_fd(fd, true, false) continue } if err == ERROR.WANT_WRITE { let fd = SSL_get_fd(ssl.ssl) - if fd == -1 : throw ssl + if fd == -1 : throw .ssl io:await_fd(fd, false, true) continue } this.ssl_error = SSL.last_error_msg() this.ssl_error_code = err - throw ssl + throw .ssl } break } @@ -61,16 +60,16 @@ use io this.ssl_enabled = true } - + fn send(data: String) !connection { - this.send_bytes(data.data, data.bytes, true) ! throw connection + + fn send(data: String) !NetError { + this.send_bytes(data.data, data.bytes, true) !> } - + fn send_buffer(data: ByteBuffer, skip_bytes: uint, send_all: bool) uint !connection { - return this.send_bytes(data.data.@offset(skip_bytes), data.length - skip_bytes, send_all) ! throw connection + + fn send_buffer(data: ByteBuffer, skip_bytes: uint, send_all: bool) uint !NetError { + return this.send_bytes(data.data.@offset(skip_bytes), data.length - skip_bytes, send_all) !> } - + fn send_bytes(data: ptr, bytes: uint, send_all: bool) uint !connection !closed { + + fn send_bytes(data: ptr, bytes: uint, send_all: bool) uint !NetError { - if this.closed : throw closed + if this.closed : throw .closed if bytes == 0 : return 0 let ssl = this.ssl @@ -92,11 +91,11 @@ use io continue #else let ev = io:await_fd(this.fd, false, true) - if ev.is_closed() || !ev.is_writable() : throw closed + if ev.is_closed() || !ev.is_writable() : throw .closed continue #end } - throw connection; + throw .write } let new_bytes = wbytes.@cast(uint) @@ -109,14 +108,14 @@ use io break } - throw connection + throw .write } ////////////// // NO SSL ////////////// - let wbytes = send_from_ptr(this.fd, data, bytes_to_send) ! throw connection + let wbytes = send_from_ptr(this.fd, data, bytes_to_send) !> let new_bytes = wbytes.@cast(uint) bytes_sent += new_bytes @@ -128,15 +127,15 @@ use io break } - throw connection + throw .write } return bytes_sent } - + fn recv(buffer: ByteBuffer, bytes: uint) uint !connection !ssl !closed { + + fn recv(buffer: ByteBuffer, bytes: uint) uint !NetError { - if this.closed : throw closed + if this.closed : throw .closed buffer.minimum_free_space(bytes) let count : uint = 0 @@ -159,11 +158,11 @@ use io continue #else let ev = io:await_fd(this.fd, true, false) - if ev.is_closed() || !ev.is_readable() : throw closed + if ev.is_closed() || !ev.is_readable() : throw .closed continue #end } - throw ssl + throw .ssl } count = rcvd.@cast(uint) buffer.length += count @@ -174,7 +173,7 @@ use io // NO SSL ////////////// - count = recv(this.fd, buffer, bytes) ! throw connection + count = recv(this.fd, buffer, bytes) !> break } diff --git a/lib/src/net/error.valk b/lib/src/net/error.valk new file mode 100644 index 00000000..421e81bf --- /dev/null +++ b/lib/src/net/error.valk @@ -0,0 +1,4 @@ + +use io + +error NetError (init, connect, disconnected, closed, invalid_host, ssl, port_in_use, max_connections) extends (io:IoError) diff --git a/lib/src/net/io.valk b/lib/src/net/io.valk index ebf0e798..d6210cc5 100644 --- a/lib/src/net/io.valk +++ b/lib/src/net/io.valk @@ -12,7 +12,7 @@ type AcceptExFn (fnptr(ext:SOCKET, ext:SOCKET, ?ptr, u32, u32, u32, &u32, ?ptr)( global pConnectEx : ?ConnectExFn global pAcceptEx : ?AcceptExFn -fn LoadConnectEx(s: ext:SOCKET) ConnectExFn !fail { +fn LoadConnectEx(s: ext:SOCKET) ConnectExFn !NetError { let p = pConnectEx if isset(p) : return p // {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} @@ -24,12 +24,12 @@ fn LoadConnectEx(s: ext:SOCKET) ConnectExFn !fail { let bytes : u32 = 0 let res = ext:WSAIoctl(s, ext:SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, size_of(), &pConnectEx, size_of(ptr), &bytes, null, null) if res != 0 { - throw fail + throw .os } return pConnectEx.@cast(ConnectExFn) } -fn LoadAcceptEx(s: ext:SOCKET) AcceptExFn !fail { +fn LoadAcceptEx(s: ext:SOCKET) AcceptExFn !NetError { let p = pAcceptEx if isset(p) : return p // {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} @@ -41,22 +41,22 @@ fn LoadAcceptEx(s: ext:SOCKET) AcceptExFn !fail { let bytes : u32 = 0 let res = ext:WSAIoctl(s, ext:SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, size_of(), &pAcceptEx, size_of(ptr), &bytes, null, null) if res != 0 { - throw fail + throw .os } return pAcceptEx.@cast(AcceptExFn) } #end -fn accept(sock_fd: FD, addr: AddrInfo) FD !error { +fn accept(sock_fd: FD, addr: AddrInfo) FD !NetError { #if OS == win let fd = ext:WSASocketA(ext:AF_INET, ext:SOCK_STREAM, ext:IPPROTO_TCP, null, 0, ext:WSA_FLAG_OVERLAPPED) - if fd == ext:INVALID_SOCKET : throw error + if fd == ext:INVALID_SOCKET : throw .init ext:CreateIoCompletionPort(fd, io:iocp(), 0, 0) let acfn = LoadAcceptEx(sock_fd) ! { Socket.close_fd(fd) - throw error + throw .os } let ov = io:overlap[io:OverlapAcceptEx] @@ -68,7 +68,7 @@ fn accept(sock_fd: FD, addr: AddrInfo) FD !error { if ov.base.sys.Internal != 0 { Socket.close_fd(fd) - throw error + throw .init } return fd @@ -76,11 +76,11 @@ fn accept(sock_fd: FD, addr: AddrInfo) FD !error { #elif OS == linux let coro = coro:current_coro.@cast(coro:Coro) - let sqe = io:sqe(coro) ! throw error + let sqe = io:sqe(coro) ! throw .os io:io_uring_prep_accept(sqe, sock_fd.@cast(i32), null, 0, 0) coro:yield() let fd = coro.completion_res.to(FD) - if (fd < 0) : throw error + if (fd < 0) : throw .init return fd #elif OS == macos @@ -94,44 +94,44 @@ fn accept(sock_fd: FD, addr: AddrInfo) FD !error { } if err == ext:EMFILE || err == ext:ENFILE { // Too many connections - throw error + throw .max_connections } - throw error + throw .init } return fd } - throw error + throw .init #else #error "Socket accept: Unsupported OS" #end } -fn connect(fd: FD, addr: AddrInfo) !error { +fn connect(fd: FD, addr: AddrInfo) !NetError { #if OS == win let empty_addr : = @undefined mem:clear(empty_addr, size_of()) empty_addr.sa_family = ext:AF_INET let err = ext:bind(fd, @ref(empty_addr), size_of()) - if err != 0 : throw error + if err != 0 : throw .connect - let confn = LoadConnectEx(fd) ! throw error + let confn = LoadConnectEx(fd) !> let ov = io:overlap[io:OverlapConnectEx] let ok = confn(fd, addr.sock_addr(), addr.addr_len(), null, 0, &ov.sent, ov) coro:yield() if ov.base.sys.Internal != 0 { - throw error + throw .connect } #elif OS == linux let coro = coro:current_coro.@cast(coro:Coro) - let sqe = io:sqe(coro) ! throw error + let sqe = io:sqe(coro) !> io:io_uring_prep_connect(sqe, fd, addr.sock_addr(), addr.addr_len()) coro:yield() let res = coro.completion_res - if res < 0 : throw error + if res < 0 : throw .connect #elif OS == macos while true { @@ -143,7 +143,7 @@ fn connect(fd: FD, addr: AddrInfo) !error { io:await_fd(fd, false, true) continue } - throw error + throw .connect } #else #error "Socket connect: Unsupported OS" @@ -151,40 +151,40 @@ fn connect(fd: FD, addr: AddrInfo) !error { } // Receive -+ fn recv(fd: FD, buf: ByteBuffer, amount: uint) uint !fail { ++ fn recv(fd: FD, buf: ByteBuffer, amount: uint) uint !NetError { buf.minimum_free_space(amount) let res = recv_to_ptr(fd, buf.data.@offset(buf.length), amount) !> buf.length += res return res } -+ fn recv_to_ptr(fd: FD, buf: ptr, amount: uint) uint !fail { ++ fn recv_to_ptr(fd: FD, buf: ptr, amount: uint) uint !NetError { let coro = coro:current_coro.@cast(coro:Coro) #if OS == macos while true { let res = ext:read(fd, buf, amount.to(u32)) - if res == 0 : throw fail + if res == 0 : throw .read if res < 0 { if ext:errno == ext:EAGAIN { let ev = io:await_fd(fd, true, false) - if ev.is_closed() || !ev.is_readable() : throw fail + if ev.is_closed() || !ev.is_readable() : throw .closed continue } if ext:errno != ext:EINTR : break - throw fail + throw .read } return res.@cast(uint) } - throw fail + throw .read #elif OS == linux - let sqe = io:sqe(coro) ! throw fail + let sqe = io:sqe(coro) ! throw .os io:io_uring_prep_recv(sqe, fd.@cast(i32), buf, amount.to(u32), 0) coro:yield() let rcvd = coro.completion_res - if rcvd <= 0 : throw fail + if rcvd <= 0 : throw .read return rcvd.@cast(uint) #elif OS == win @@ -197,11 +197,11 @@ fn connect(fd: FD, addr: AddrInfo) !error { let ok = ext:WSAGetOverlappedResult(fd, ov, &ov.rcvd, true, &ov.flags) if ov.base.sys.Internal != 0 { - throw fail + throw .read } let rcvd = ov.rcvd - if rcvd == 0 : throw fail + if rcvd == 0 : throw .read return rcvd #else #error "Socket recv: Unsupported OS" @@ -209,41 +209,41 @@ fn connect(fd: FD, addr: AddrInfo) !error { } // Receive -+ fn send(fd: FD, buf: ByteBuffer, amount: uint) uint !fail { ++ fn send(fd: FD, buf: ByteBuffer, amount: uint) uint !NetError { if amount > buf.length : amount = buf.length return send_from_ptr(fd, buf.data, amount) !> } -+ fn send_string(fd: FD, str: String) uint !fail { ++ fn send_string(fd: FD, str: String) uint !NetError { return send_from_ptr(fd, str.data, str.length) !> } -+ fn send_from_ptr(fd: FD, buf: ptr, amount: uint) uint !fail { ++ fn send_from_ptr(fd: FD, buf: ptr, amount: uint) uint !NetError { let coro = coro:current_coro.@cast(coro:Coro) #if OS == macos while true { let res = ext:write(fd, buf, amount.to(u32)) - if res == 0 : throw fail + if res == 0 : throw .write if res < 0 { if ext:errno == ext:EAGAIN { let ev = io:await_fd(fd, false, true) - if ev.is_closed() || !ev.is_readable() : throw fail + if ev.is_closed() || !ev.is_readable() : throw .closed continue } if ext:errno != ext:EINTR : break - throw fail + throw .write } return res.@cast(uint) } - throw fail + throw .write #elif OS == linux - let sqe = io:sqe(coro) ! throw fail + let sqe = io:sqe(coro) ! throw .os io:io_uring_prep_send(sqe, fd.@cast(i32), buf, amount.to(u32), 0) coro:yield() let sent = coro.completion_res - if sent <= 0 : throw fail + if sent <= 0 : throw .write return sent.@cast(uint) #elif OS == win @@ -257,7 +257,7 @@ fn connect(fd: FD, addr: AddrInfo) !error { let flags : u32 = 0 let ok = ext:WSAGetOverlappedResult(fd, ov, &ov.sent, true, &flags) if ov.base.sys.Internal != 0 { - throw fail + throw .write } let sent = ov.sent diff --git a/lib/src/net/socket-server.valk b/lib/src/net/socket-server.valk index 1d2d5bed..d843ffdb 100644 --- a/lib/src/net/socket-server.valk +++ b/lib/src/net/socket-server.valk @@ -1,16 +1,14 @@ -use ext - + class SocketServer { ~ socket: Socket - + fn accept() Connection !too_many_connections !error !closed { + + fn accept() Connection !NetError { let sock = this.socket - if sock.closed : throw closed + if sock.closed : throw .closed let sock_fd = sock.fd - let fd = accept(sock_fd, sock.addrinfo) ! throw error + let fd = accept(sock_fd, sock.addrinfo) !> return Connection.new(fd) } diff --git a/lib/src/net/socket.valk b/lib/src/net/socket.valk index 298ae1f0..c4408e0b 100644 --- a/lib/src/net/socket.valk +++ b/lib/src/net/socket.valk @@ -13,7 +13,7 @@ use io - addrinfo: AddrInfo - closed: bool (false) - + static fn server(type: SOCKET_TYPE, host: String, port: u16) SocketServer !invalid_host !create !bind !listen !closed { + + static fn server(type: SOCKET_TYPE, host: String, port: u16) SocketServer !NetError { let socket = SELF.create(type, host, port) !> socket.bind() !> @@ -23,20 +23,20 @@ use io } } - + static fn client(type: SOCKET_TYPE, host: String, port: u16) Connection !invalid_host !create !connect !closed { + + static fn client(type: SOCKET_TYPE, host: String, port: u16) Connection !NetError { let socket = SELF.create(type, host, port) !> let con = socket.connect() !> return con } - static fn create(type: SOCKET_TYPE, host: String, port: u16) Socket !invalid_host !create { + static fn create(type: SOCKET_TYPE, host: String, port: u16) Socket !NetError { // #if OS == win WSA.init() #end // Get host info - let addrinfo = AddrInfo.new(host, port) ! throw invalid_host + let addrinfo = AddrInfo.new(host, port) ! throw .invalid_host #if OS == win let fd = ext:WSASocketA(ext:AF_INET, ext:SOCK_STREAM, ext:IPPROTO_TCP, null, 0, ext:WSA_FLAG_OVERLAPPED) @@ -47,7 +47,7 @@ use io let fd = ext:socket(ext:AF_INET, ext:SOCK_STREAM, 0) #end - if fd == -1 : throw create + if fd == -1 : throw .init #if OS == macos io:set_non_block(fd, true) #end @@ -77,9 +77,9 @@ use io } } - fn bind() !closed !bind !listen { + fn bind() !NetError { - if this.closed : throw closed + if this.closed : throw .closed let yes : i32 = 1 let err : i32 = 0 @@ -87,20 +87,26 @@ use io ext:setsockopt(this.fd, ext:SOL_SOCKET, ext:SO_REUSEADDR, @ref(yes), size_of(i32)) err = ext:bind(this.fd, this.addrinfo.sock_addr(), this.addrinfo.addr_len()) - if err != 0 : throw bind + if err != 0 { + // TODO: return correct error based on error + throw .port_in_use + } err = ext:listen(this.fd, 2000000) - if err != 0 : throw listen + if err != 0 { + // TODO: return correct error based on error + throw .port_in_use + } } - fn connect() Connection !closed !connect { + fn connect() Connection !NetError { - if this.closed : throw closed + if this.closed : throw .closed let fd = this.fd connect(fd, this.addrinfo) ! { this.close() - throw connect + throw .connect } this.closed = true // The connection will close the FD diff --git a/lib/src/thread/suspend-gate.valk b/lib/src/thread/suspend-gate.valk index 72f02198..13779542 100644 --- a/lib/src/thread/suspend-gate.valk +++ b/lib/src/thread/suspend-gate.valk @@ -12,7 +12,7 @@ use gc #end ready: bool (false) - + static fn new() SELF !error { + + static fn new() SELF { let sig = SELF{} #if OS == win ext:InitializeCriticalSection(sig.crit) diff --git a/lib/src/thread/task.valk b/lib/src/thread/task.valk index 0a476807..2d0b8ec4 100644 --- a/lib/src/thread/task.valk +++ b/lib/src/thread/task.valk @@ -6,15 +6,15 @@ shared task_gate : ThreadSuspendGate (.new() ! panic("Failed to create task sche shared tasks : Array[Task] (.{}) shared threads_waiting : uint (0) -+ fn task(handler: fn()()) Task !error { ++ fn task(handler: fn()()) Task !InitError { let task = Task { handler: handler - mutex: .new() ! throw error + mutex: .new() ! throw .init } tasks.lock() tasks.append(task) tasks.unlock() - start_taskrunner() ! throw error + start_taskrunner() ! throw .init return task } @@ -31,7 +31,7 @@ shared threads_waiting : uint (0) } } -fn start_taskrunner() !error { +fn start_taskrunner() !InitError { if threads_waiting == 0 { // Start new runner @@ -57,7 +57,7 @@ fn start_taskrunner() !error { task.mutex.unlock() } }) ! { - throw error + throw .init } } // Continue existing runners diff --git a/lib/src/thread/thread.valk b/lib/src/thread/thread.valk index 013846a3..75935911 100644 --- a/lib/src/thread/thread.valk +++ b/lib/src/thread/thread.valk @@ -11,7 +11,7 @@ use time global current_thread: ?Thread (null) shared threads: Array[Thread] (.new()) -+ fn start(func: fn()()) Thread !start { ++ fn start(func: fn()()) Thread !InitError { return Thread.start(func) !> } @@ -26,10 +26,10 @@ shared threads: Array[Thread] (.new()) ~ finished: bool (false) ~ started: bool (false) - + static fn start(func: fn()()) SELF !start { + + static fn start(func: fn()()) SELF !InitError { let t = SELF { handler: func - mutex: .new() ! throw start + mutex: .new() ! throw .init } threads.lock() threads.append(t) @@ -38,14 +38,14 @@ shared threads: Array[Thread] (.new()) return t } - fn init() !start $undefined { + fn init() !InitError $undefined { #if OS == win this.thread = ext:CreateThread(null, 0, this.entry, this, 0, null) - if this.thread == 0 : throw start + if this.thread == 0 : throw .init #else let thr = ext:pthread_t{} let err = ext:pthread_create(thr, null, this.entry, this) - if err != 0 : throw start + if err != 0 : throw .init this.thread = thr #end } diff --git a/lib/src/type/ByteBuffer.valk b/lib/src/type/ByteBuffer.valk index 1d18c8d2..e633d0bc 100644 --- a/lib/src/type/ByteBuffer.valk +++ b/lib/src/type/ByteBuffer.valk @@ -127,15 +127,15 @@ use gc } // Find - + fn index_of(byte: u8, start_index: uint (0)) uint !not_found { + + fn index_of(byte: u8, start_index: uint (0)) uint !LookupError { let len = this.length - if start_index >= len : throw not_found + if start_index >= len : throw .missing let data : ptr = this.data - let res = mem:find_char(data + start_index, byte, len - start_index) ! throw not_found + let res = mem:find_char(data + start_index, byte, len - start_index) ! throw .missing return res + start_index } - + fn index_where_byte_is_not(byte: u8, start_index: uint (0)) uint !not_found { + + fn index_where_byte_is_not(byte: u8, start_index: uint (0)) uint !LookupError { let index = start_index let len = this.length let data : ptr = this.data @@ -144,7 +144,7 @@ use gc if ch != byte : return index index++ } - throw not_found + throw .missing } ///////////////////////// diff --git a/lib/src/type/array.valk b/lib/src/type/array.valk index be5855fc..0aa9fd96 100644 --- a/lib/src/type/array.valk +++ b/lib/src/type/array.valk @@ -188,13 +188,13 @@ use gc return new_data } - + fn get(index: uint) T !not_found $offset { - if index >= this.length : throw not_found + + fn get(index: uint) T !LookupError $offset { + if index >= this.length : throw .missing return @property_get(@ptrv(this.data, T, index)) } - + fn set(index: uint, value: T) !out_of_range $offset_assign { - if index > this.length : throw out_of_range + + fn set(index: uint, value: T) !LookupError $offset_assign { + if index > this.length : throw .range if index == this.length { this.append(value); return @@ -262,8 +262,8 @@ use gc return this } - + fn pop_last() T !empty { - if this.length == 0 : throw empty + + fn pop_last() T !LookupError { + if this.length == 0 : throw .missing this.each_slice = null let index = --this.length @@ -274,8 +274,8 @@ use gc return item } - + fn pop_first() T !empty { - if this.length == 0 : throw empty + + fn pop_first() T !LookupError { + if this.length == 0 : throw .missing this.each_slice = null let data : ptr = this.data @@ -305,7 +305,7 @@ use gc @ptrv(data, T, index_b) = a } - + fn index_of(item: T) uint !not_found { + + fn index_of(item: T) uint !LookupError { let index : uint = 0 let adr = this.data let len = this.length @@ -314,7 +314,7 @@ use gc if x == item : return index index++ } - throw not_found + throw .missing } + fn clear(reduce_size: bool (false)) SELF { @@ -495,8 +495,8 @@ use gc this.each_slice = n return n } - fn _next(index: uint) T !end $inline { - if index >= this.length : throw end + fn _next(index: uint) T !IterError $inline { + if index >= this.length : throw .end return @property_get(@ptrv(this.data, T, index)) } #if is_type_of_class(T, u32) diff --git a/lib/src/type/c-string.valk b/lib/src/type/c-string.valk index cbbba4dc..b8fe8995 100644 --- a/lib/src/type/c-string.valk +++ b/lib/src/type/c-string.valk @@ -4,14 +4,14 @@ + fn get(index: uint) u8 $offset { return @ptrv(this, u8, index) } - + fn index_of(find: u8) uint !notfound { + + fn index_of(find: u8) uint !LookupError { let i : uint = 0 while true { let ch = this[i++] if ch == find : return i - 1 if ch == 0 : break } - throw notfound + throw .missing } + fn length() uint { diff --git a/lib/src/type/flatmap.valk b/lib/src/type/flatmap.valk index 882800d8..2e3c597c 100644 --- a/lib/src/type/flatmap.valk +++ b/lib/src/type/flatmap.valk @@ -48,15 +48,15 @@ return this } - + mut fn set_unique(key: K, value: T) !not_unique { - if this._keys.contains(key) : throw not_unique + + mut fn set_unique(key: K, value: T) !LookupError { + if this._keys.contains(key) : throw .exists this._keys.append(key) this._values.append(value) } - + fn get(key: K) T !not_found $offset { - let index = this._keys.index_of(key) ! { throw not_found } - return this._values.get(index) ! { throw not_found } + + fn get(key: K) T !LookupError $offset { + let index = this._keys.index_of(key) ! { throw .missing } + return this._values.get(index) ! { throw .missing } } + fn has(key: K) bool { @@ -90,9 +90,9 @@ } } - fn _next(index: uint) (T, K) !end { - let k = this._keys.get(index) ! throw end - let v = this._values.get(index) ! throw end + fn _next(index: uint) (T, K) !IterError { + let k = this._keys.get(index) ! throw .end + let v = this._values.get(index) ! throw .end return (v, k) } } diff --git a/lib/src/type/hashmap.valk b/lib/src/type/hashmap.valk index 46b56b30..e1ae1cc6 100644 --- a/lib/src/type/hashmap.valk +++ b/lib/src/type/hashmap.valk @@ -85,9 +85,9 @@ use core return this } - + fn get(key: K) T !not_found $offset { + + fn get(key: K) T !LookupError $offset { let zone = this.zone(key) - return zone.get(key) ! throw not_found + return zone.get(key) ! throw .missing } + fn has(key: K) bool { @@ -129,8 +129,8 @@ use core return copy } - fn _next(index: uint) (T, K) !end { - let k = this._keys.get(index) ! throw end - return ((this.get(k) ! throw end), k) + fn _next(index: uint) (T, K) !IterError { + let k = this._keys.get(index) ! throw .end + return ((this.get(k) ! throw .end), k) } } diff --git a/lib/src/type/pool.valk b/lib/src/type/pool.valk index 6d6e11a2..77f139b6 100644 --- a/lib/src/type/pool.valk +++ b/lib/src/type/pool.valk @@ -27,9 +27,9 @@ use mem this.count = count + 1 } - + fn get() T !empty { + + fn get() T !LookupError { let count = this.count - if count == 0 : throw empty + if count == 0 : throw .empty let new_count = count - 1 let item = @ptrv(this.data, T, new_count) this.count = new_count diff --git a/lib/src/type/ptr.valk b/lib/src/type/ptr.valk index d5454e00..d03b31cc 100644 --- a/lib/src/type/ptr.valk +++ b/lib/src/type/ptr.valk @@ -15,7 +15,7 @@ use mem io:print_from_ptr(this, length) } - + fn index_of_byte(byte: u8, memory_size: uint) uint !not_found { + + fn index_of_byte(byte: u8, memory_size: uint) uint !LookupError { let index : uint = 0 let len = memory_size while index < len { @@ -23,7 +23,7 @@ use mem if ch == byte : return index index++ } - throw not_found + throw .missing } + fn print_bytes(length: uint, end_with_newline: bool (true)) { @@ -44,25 +44,25 @@ use mem return str } - fn read_uint_value(len: uint) uint !invalid { + fn read_uint_value(len: uint) uint !SyntaxError { let i = len - if i == 0 : throw invalid + if i == 0 : throw .syntax let result : uint = 0 let mult : uint = 1 while i > 0 { let ch = @ptrv(this, u8, --i) let v = ch - '0' - if v > 9 : throw invalid + if v > 9 : throw .syntax result += v.@cast(uint) * mult mult *= 10 } return result } - fn read_octal_value(len: uint) uint !invalid { + fn read_octal_value(len: uint) uint !SyntaxError { let i = len - if i == 0 : throw invalid + if i == 0 : throw .syntax let result : uint = 0 let mult : uint = 1 @@ -73,16 +73,16 @@ use mem } else if ch == 'c' && i == 1 && @ptrv(this, u8, 0) == '0' { break } else { - throw invalid + throw .syntax } mult *= 8 } return result } - fn read_hex_value(len: uint) uint !invalid { + fn read_hex_value(len: uint) uint !SyntaxError { let i = len - if i == 0 : throw invalid + if i == 0 : throw .syntax let result : uint = 0 let mult : uint = 1 @@ -97,7 +97,7 @@ use mem } else if ch == 'x' && i == 1 && @ptrv(this, u8, 0) == '0' { break } else { - throw invalid + throw .syntax } mult *= 16 } diff --git a/lib/src/type/slice.valk b/lib/src/type/slice.valk index ebcf895d..405a77af 100644 --- a/lib/src/type/slice.valk +++ b/lib/src/type/slice.valk @@ -25,13 +25,13 @@ slice Slice[T] { return s } - + fn get(index: uint) T !not_found $offset { - if index >= this.length : throw not_found + + fn get(index: uint) T !LookupError $offset { + if index >= this.length : throw .missing return @property_get(@ptrv(this.data, T, index)) } - + fn set(index: uint, value: T) !out_of_range $offset_assign { - if index >= this.length : throw out_of_range + + fn set(index: uint, value: T) !LookupError $offset_assign { + if index >= this.length : throw .range @property_update(this, @ptrv(this.data, T, index), value) } + fn set_all(value: T) { diff --git a/lib/src/type/string-char-step.valk b/lib/src/type/string-char-step.valk index 85897da6..342e87b3 100644 --- a/lib/src/type/string-char-step.valk +++ b/lib/src/type/string-char-step.valk @@ -13,11 +13,11 @@ struct CharStep { this.pos += len } - fn next() u8 !end { + fn next() u8 !IterError { let pos = this.pos let bytes = this.bytes let data = this.data - if pos == bytes : throw end + if pos == bytes : throw .end let byte : u8 = @ptrv(data, u8, pos) let bytec : u8 = 1 if((byte & 128) > 0){ diff --git a/lib/src/type/string.valk b/lib/src/type/string.valk index 649214bb..22fcb943 100644 --- a/lib/src/type/string.valk +++ b/lib/src/type/string.valk @@ -36,31 +36,31 @@ use gc // Convert ///////////////////// - + fn to_uint() uint !invalid { - return this.data.read_uint_value(this.bytes) ! throw invalid + + fn to_uint() uint !SyntaxError { + return this.data.read_uint_value(this.bytes) ! throw .syntax } - + fn to_int() int !invalid { - if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_uint_value(this.bytes - 1) ! throw invalid).@cast(int) * -1 - return (this.data.read_uint_value(this.bytes) ! throw invalid).@cast(int) + + fn to_int() int !SyntaxError { + if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_uint_value(this.bytes - 1) ! throw .syntax).@cast(int) * -1 + return (this.data.read_uint_value(this.bytes) ! throw .syntax).@cast(int) } - + fn hex_to_uint() uint !invalid { - return this.data.read_hex_value(this.bytes) ! throw invalid + + fn hex_to_uint() uint !SyntaxError { + return this.data.read_hex_value(this.bytes) ! throw .syntax } - + fn hex_to_int() int !invalid { - if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_hex_value(this.bytes - 1) ! throw invalid).@cast(int) * -1 - return (this.data.read_hex_value(this.bytes) ! throw invalid).@cast(int) + + fn hex_to_int() int !SyntaxError { + if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_hex_value(this.bytes - 1) ! throw .syntax).@cast(int) * -1 + return (this.data.read_hex_value(this.bytes) ! throw .syntax).@cast(int) } - + fn octal_to_uint() uint !invalid { - return this.data.read_octal_value(this.bytes) ! throw invalid + + fn octal_to_uint() uint !SyntaxError { + return this.data.read_octal_value(this.bytes) ! throw .syntax } - + fn octal_to_int() int !invalid { - if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_octal_value(this.bytes - 1) ! throw invalid).@cast(int) * -1 - return (this.data.read_octal_value(this.bytes) ! throw invalid).@cast(int) + + fn octal_to_int() int !SyntaxError { + if this.to(String).get(0) == '-' : return (((this.data + 1).@cast(ptr)).read_octal_value(this.bytes - 1) ! throw .syntax).@cast(int) * -1 + return (this.data.read_octal_value(this.bytes) ! throw .syntax).@cast(int) } - + fn to_float() f64 !invalid { + + fn to_float() f64 !SyntaxError { let result : f64 = 0 let sign : f64 = 1 @@ -70,11 +70,11 @@ use gc let decimals = 0 let ascii = this.to(String) - if i == len : throw invalid + if i == len : throw .syntax if ascii.get(0) == '-' { sign = -1 i++ - if i == len : throw invalid + if i == len : throw .syntax } while i < len { @@ -83,10 +83,10 @@ use gc result = result * 10 + (ch - '0'); if has_dot : decimals++ } else if ch == '.' { - if has_dot : throw invalid + if has_dot : throw .syntax has_dot = true } else { - throw invalid + throw .syntax } } @@ -291,9 +291,9 @@ use gc // Index of ///////////////////// - + fn index_of(part: String, start_index: uint (0)) uint !not_found { + + fn index_of(part: String, start_index: uint (0)) uint !LookupError { let part_bytes = part.bytes - if part_bytes > this.bytes : throw not_found + if part_bytes > this.bytes : throw .missing let index = start_index let len = this.bytes - part_bytes + 1 let data = this.data @@ -302,10 +302,10 @@ use gc if mem:equal(data.@offset(index), part_data, part_bytes) : return index index++ } - throw not_found + throw .missing } - + fn index_of_byte(byte: u8, start_index: uint (0)) uint !not_found { + + fn index_of_byte(byte: u8, start_index: uint (0)) uint !LookupError { let index = start_index let len = this.bytes let data = this.data @@ -314,7 +314,7 @@ use gc if ch == byte : return index index++ } - throw not_found + throw .missing } + fn contains(part: String) bool { @@ -377,8 +377,8 @@ use gc return result } - fn split_on_first_occurance_of_byte(byte: u8) (String, String) !not_found { - let pos = this.index_of_byte(byte) ! throw not_found + fn split_on_first_occurance_of_byte(byte: u8) (String, String) !LookupError { + let pos = this.index_of_byte(byte) ! throw .missing let p2 = this.part(pos + 1, this.bytes - pos - 1) let p1 = this.part(0, pos) return (p1, p2) @@ -533,8 +533,8 @@ use gc return buf.to_string() } - fn _next(index: uint) u8 !end { - if index >= this.bytes : throw end + fn _next(index: uint) u8 !IterError { + if index >= this.bytes : throw .end return this.data[index] } @@ -559,8 +559,8 @@ use gc // Index of ///////////////////// - + fn index_of(part: String, start_index: uint (0)) uint !not_found { - if part.bytes > this.bytes : throw not_found + + fn index_of(part: String, start_index: uint (0)) uint !LookupError { + if part.bytes > this.bytes : throw .missing let step = @stack() step.set(this) let count : uint = 0 @@ -570,14 +570,14 @@ use gc let part_bytes = part.bytes let last_offset = this.bytes - part_bytes while count < start_index { - offset += step.next() ! throw not_found - if offset > last_offset : throw not_found + offset += step.next() ! throw .missing + if offset > last_offset : throw .missing count++ } while true { if mem:equal(adr.@offset(offset), part_adr, part_bytes) : return count - offset += step.next() ! throw not_found - if offset > last_offset : throw not_found + offset += step.next() ! throw .missing + if offset > last_offset : throw .missing count++ } return 0 diff --git a/src/build/ast.valk b/src/build/ast.valk index fbb4253e..7c7e506f 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -699,10 +699,12 @@ fn ast_throw(p: Parser, scope: Scope) { let func = p.func() let b = p.build - let err = p.read_word(true, false) let etype = func.error_type if !isset(etype) : p.error("This function has no error type") - if !etype.codes.has_value(err) : p.error("Unknown error '" + err + "'. Please add the error to your function declaration.") + + p.expect(".", true, false) + let err = p.read_word(false, false) + if !etype.codes.has_value(err) : p.error("No error code named '" + err + "' in error type: " + etype.display_name) let hash = helper:ctxhash_u32(err) let payload : ?Map[Value] = null diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index d2684927..d6cd1477 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -216,7 +216,7 @@ fn value_pass_handler(ctx: Context, scope: Scope, on: Value) Value { values.append(vgen_empty_value(type) ?! continue) } values.append(on.func_err_code ?! ctx.error("Missing error code (bug)")) - values.append(on.func_err_msg ?! ctx.error("Missing error message (bug)")) + // values.append(on.func_err_msg ?! ctx.error("Missing error message (bug)")) ast_return_value(func, sub, vgen_grouped_values(b, values)) // Err check diff --git a/src/build/error-type.valk b/src/build/error-type.valk index c83154aa..6f193839 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -17,6 +17,8 @@ class ErrorType { payload_global_names: Array[String] (.{}) cache_type: ?Type (null) cache_code_types: Map[Type] (.{}) + parsing: bool (false) + parsed: bool (false) fn get_type(code: ?String) Type { if !isset(code) { @@ -36,6 +38,9 @@ class ErrorType { } fn parse() { + if this.parsed : return + if this.parsing : this.build.error("Circular extend for error type: " + this.display_name) + this.parsing = true let che = this.chunk_extends let chp = this.chunk_payload let scope = this.fc.scope @@ -47,7 +52,8 @@ class ErrorType { let idf = p.read_idf(scope, true, true) if idf.for != IDF.error_type : p.error("Not an error-type") let et = idf.error_type ?! p.error("Missing error type") - if et.extends.contains(this) : p.error("Circular error types. Type A extends B and B extends A.") + if !et.parsed : et.parse() + if et.extends.contains(this) : p.error("Circular extend for error type: " + this.display_name + " <> " + et.display_name) each et.extends as subet { this.extends.append(subet) } @@ -60,6 +66,8 @@ class ErrorType { each et.payload as type, name { this.add_payload(p.ctx, name, type) } + p.expect2(",", ")", true, true) + if p.word_is(")") : break } } if isset(chp) { @@ -69,25 +77,27 @@ class ErrorType { let name = p.read_word(true, true) p.expect(":", true, false) let type = read_type(p, scope, true, false) + this.add_payload(p.ctx, name, type) } } if codes.length == 0 { let p = Parser.new(this.chunk_define, null) p.error("Errors need to have atleast 1 error code defined") } + this.parsed = true } fn add_payload(ctx: Context, name: String, type: Type) { if name == "code" : ctx.error("You cannot use 'code' for a payload property name, this is a reserved name for the language.") - let pay = this.payload let codes = this.codes if codes.has_value(name) : ctx.error("The payload property name '%name' is already used as an error code.") - pay.get(name) -> etype { + this.payload.get(name) -> etype { if etype.compat(type) : return if !type.compat(etype) : ctx.error("You have 2 payloads with the same name but with different types. Either rename one of them or use the same type. Property: %name | Type: %etype <> %type") } - pay.set(name, type) + this.payload.set(name, type) let g = get_payload_global(ctx.build, type, this.payload_global_names) + g.type = type this.payload_globals.set(name, g) } diff --git a/src/build/ir-type.valk b/src/build/ir-type.valk index 4eea8e00..06eac271 100644 --- a/src/build/ir-type.valk +++ b/src/build/ir-type.valk @@ -3,6 +3,7 @@ fn ir_type(type: Type) String { if type.is_pointer() : return "ptr" let tt = type.type if tt == TYPE.void : return "void" + if tt == TYPE.error_code : return "i32" if tt == TYPE.int { let size = type.size() return ir_type_int(size) diff --git a/src/build/offset.valk b/src/build/offset.valk index 28c000e6..1a890aca 100644 --- a/src/build/offset.valk +++ b/src/build/offset.valk @@ -41,9 +41,10 @@ fn parse_offset_value(p: Parser, scope: Scope, on: Value) Value { let get_call = vgen_func_call(p.build, scope, fptr, argvs) // Error handling let info = fptr.rett.get_func_info() - if info.can_error { + let etype = info.error_type + if isset(etype) { if p.has_error_handler_ahead() { - get_call = value_error_handling(p, scope, get_call, info.errors) + get_call = value_error_handling(p, scope, get_call, etype) } else { let left = get_call.trim_errors() let altv = vgen_empty_value(left.rett) ?! return left diff --git a/src/build/parser.valk b/src/build/parser.valk index 5f8c6f36..8aa70fef 100644 --- a/src/build/parser.valk +++ b/src/build/parser.valk @@ -446,6 +446,7 @@ class Parser { fn skip_id(allow_newline: bool) { this.read_word(true, allow_newline) if this.char(0) == ':' { + this.i++ this.col++ this.read_word(false, false) } diff --git a/src/build/scope.valk b/src/build/scope.valk index 00f15b26..e432bfee 100644 --- a/src/build/scope.valk +++ b/src/build/scope.valk @@ -98,7 +98,7 @@ class Scope { return b.valk_idf("type", name) } // core - if name == "exit" || name == "panic" { + if name == "exit" || name == "panic" || name == "SomeError" || name == "ExternError" || name == "IterError" || name == "InitError" || name == "LookupError" || name == "SyntaxError" { return b.valk_idf("core", name) } diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index be97dfa9..7d39f25e 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -667,5 +667,5 @@ fn parse_error(p: Parser, fc: Fc, act: int) { } p.build.error_types.append(e) - fc.set_idf(p.ctx, name, Idf.for_error(e)) + fc.set_idf(p.ctx, name, Idf.for_error_type(e)) } diff --git a/src/build/value.valk b/src/build/value.valk index 35e4a253..aa1dbb5c 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -356,9 +356,9 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals } else if p.word_is("error_is") { p.expect("(", false, false) - let idf = p.read_idf(scope, true, true) - let left = idf.value - if !isset(left) : p.error("First value in 'error_is' is not an error value") + // let idf = p.read_idf(scope, true, true) + let left = read_value(p, scope) + // if !isset(left) : p.error("First value in 'error_is' is not an error value") if left.rett.type != TYPE.error_code : p.error("First value in 'error_is' is not an error type") // Left @@ -1456,7 +1456,12 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { if etype.payload.has(key) { panic("TODO: payload data") } - p.error("Error type has no error code or payload property named: '" + key + "'. Valid options are: " + etype.codes.values().join(", ")) + let message = "Error type has no error code or payload property named: '" + key + "'. Valid options are: " + etype.codes.values().join(", ") + let payload = etype.payload + if payload.length > 0 { + message += " or (payload) " + payload.keys().join(", ") + } + p.error(message) } p.error("Unhandled identifier type (type: " + for + ") (compiler bug)") diff --git a/src/lsp/sighelp.valk b/src/lsp/sighelp.valk index 2aca5200..548cfa39 100644 --- a/src/lsp/sighelp.valk +++ b/src/lsp/sighelp.valk @@ -20,7 +20,7 @@ extend Lsp { fn check_sig_help_func(active: uint, types: Array[build:Type], inf_type: ?build:Type, args: ?Array[build:Arg], func_info: build:FuncInfo) { let rett_types = func_info.rett_types - let errors = func_info.errors + let etype = func_info.error_type if !this.is_sig_help : return let all_types = types.copy() @@ -53,10 +53,8 @@ extend Lsp { if rett_types.length > 1 : full += ")" } - if func_info.can_error { - each errors as code, name { - full += " !" + name - } + if isset(etype) { + full += " !" + etype.display_name } this.send_sig_help(full, parts, active) diff --git a/tests/coros.valk b/tests/coros.valk index 07ee0952..a607f4fb 100644 --- a/tests/coros.valk +++ b/tests/coros.valk @@ -5,6 +5,10 @@ use valk:time global coro_test_str: String ("") +error TsetCoroError (err) payload { + message: String +} + fn coro_test() { coro_test_str += "_2" coro:await_last() @@ -15,12 +19,12 @@ fn coro_val(suffix: String) String { return "a" + suffix } -fn coro_throw(suffix: String) String !testerr { - throw testerr, "throw_msg" +fn coro_throw(suffix: String) String !TsetCoroError { + throw .err { message: "throw_msg" } } fn coro_cothrow(suffix: String) String { - cothrow testerr, "cothrow_msg" + cothrow testerr } test "Coro: basics" { @@ -40,8 +44,7 @@ test "Coro: await" { test "Coro: throw" { let task = co coro_throw("x") let res = await task !? <{ - assert(error_is(E, testerr)) - assert(EMSG == "throw_msg") + assert(error_is(E.code, testerr)) return "z" } assert(res == "z") diff --git a/tests/error-handlers.valk b/tests/error-handlers.valk index af38e244..46323bce 100644 --- a/tests/error-handlers.valk +++ b/tests/error-handlers.valk @@ -1,21 +1,25 @@ use valk:core -fn test_errh_success() uint !err { +error TestError (err, err2) payload { + message: String +} + +fn test_errh_success() uint !TestError { return 5 } -fn test_errh_error() uint !err { - throw err +fn test_errh_error() uint !TestError { + throw .err } -fn test_errh_error2() uint !err !err2 { - throw err2 +fn test_errh_error2() uint !TestError { + throw .err2 } -fn test_errh_error3() !err { - throw err, "Hello" +fn test_errh_error3() !TestError { + throw .err { message: "Hello" } } -fn test_errh_error4() !err !err2 { +fn test_errh_error4() !TestError { test_errh_error3() !> - throw err2 + throw .err2 } test "Error handling" { @@ -27,14 +31,14 @@ test "Error handling" { test_errh_error3() _ // let v3 = test_errh_error() !? <{ - assert(error_is(E, err)) + assert(error_is(E.code, err)) return 5 } assert(v3 == 5) let v4 = test_errh_error2() !? <{ - assert(!error_is(E, err)) - assert(error_is(E, err2)) - assert(E == E.err2) + assert(!error_is(E.code, err)) + assert(error_is(E.code, err2)) + assert(E.code == E.err2) return 5 } assert(v4 == 5) @@ -42,8 +46,8 @@ test "Error handling" { let has_error = false test_errh_error4() ! { has_error = true - assert(E == E.err) - assert(EMSG == "Hello") + assert(E.code == E.err) + assert(E.message == "Hello") } assert(has_error) } diff --git a/tests/success-handlers.valk b/tests/success-handlers.valk index 22eb61dc..329c720b 100644 --- a/tests/success-handlers.valk +++ b/tests/success-handlers.valk @@ -1,9 +1,11 @@ -fn success1() (String, int) !err { +error SuccessError (err) + +fn success1() (String, int) !SuccessError { return ("test", 20) } -fn success2() String !err { - throw err +fn success2() String !SuccessError { + throw .err } class SH1 { @@ -12,8 +14,8 @@ class SH1 { class SH2 { value: uint (5) - fn getv() uint !fail { - if false : throw fail + fn getv() uint !SuccessError { + if false : throw .err return this.value } } From a8d29b18ce77dd0245fe20242d94fdd7d1f5bfd6 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 09:38:29 +0100 Subject: [PATCH 09/32] update --- lib/src/http/context-parser.valk | 10 +-- lib/src/json/error.valk | 5 ++ lib/src/json/parse.valk | 79 ++++++++++++++-------- lib/src/json/value.valk | 10 +-- lib/src/markdown/to-html.valk | 6 +- lib/src/template/error.valk | 8 +++ lib/src/template/parser.valk | 108 ++++++++++++++++++------------- lib/src/template/render.valk | 7 +- lib/src/template/scope.valk | 4 +- src/build/ast.valk | 31 ++++++--- tests/try-convert.valk | 4 +- tests/vscope.valk | 12 ++-- 12 files changed, 177 insertions(+), 107 deletions(-) create mode 100644 lib/src/json/error.valk create mode 100644 lib/src/template/error.valk diff --git a/lib/src/http/context-parser.valk b/lib/src/http/context-parser.valk index 8ae4e1b1..c328f09b 100644 --- a/lib/src/http/context-parser.valk +++ b/lib/src/http/context-parser.valk @@ -169,20 +169,20 @@ trait ContextParser { } } - - fn parse_headers(data: ptr, length: uint, result: Map[String]) uint !invalid { + - fn parse_headers(data: ptr, length: uint, result: Map[String]) uint !HttpParseError { let pos : uint = 0 while true { - let index = data.$offset(pos).index_of_byte('\r', length - pos) ! throw invalid + let index = data.$offset(pos).index_of_byte('\r', length - pos) ! throw .invalid index += pos - if index + 1 >= length : throw invalid - if @ptrv(data, u8, index + 1) != '\n' : throw invalid + if index + 1 >= length : throw .invalid + if @ptrv(data, u8, index + 1) != '\n' : throw .invalid // End of headers if index == pos : break - let split = data.$offset(pos).index_of_byte(':', index - pos) ! throw invalid + let split = data.$offset(pos).index_of_byte(':', index - pos) ! throw .invalid split += pos let value_pos = split + 1 diff --git a/lib/src/json/error.valk b/lib/src/json/error.valk new file mode 100644 index 00000000..1f807fea --- /dev/null +++ b/lib/src/json/error.valk @@ -0,0 +1,5 @@ + +error ParseError (invalid) payload { + at_index: uint + message: String +} diff --git a/lib/src/json/parse.valk b/lib/src/json/parse.valk index 8710bfe7..b725dd72 100644 --- a/lib/src/json/parse.valk +++ b/lib/src/json/parse.valk @@ -12,10 +12,10 @@ enum TYPE { object } -+ fn decode(json: String) Value !invalid { ++ fn decode(json: String) Value !ParseError { let data = ByteBuffer.new(json.bytes) data.append_str(json) - return Parser.parse(data) ! throw invalid, EMSG + return Parser.parse(data) !> } class Parser { @@ -23,33 +23,35 @@ class Parser { data: ByteBuffer str_buf: ByteBuffer - - static fn parse(data: ByteBuffer) Value !invalid { + - static fn parse(data: ByteBuffer) Value !ParseError { let p = Parser{ index: 0 data: data str_buf: ByteBuffer.new(100) } - let result = p.parse_value() ! throw invalid, EMSG + let result = p.parse_value() !> p.skip_whitespace() if p.index != p.data.length { - // println("Invalid trailing chars") - throw invalid, "Trailing character at index: " + p.index + throw .invalid { + message: "Trailing character at index" + at_index: p.index + } } return result } - - fn parse_value() Value !invalid { + - fn parse_value() Value !ParseError { this.skip_whitespace() let ch = this.data.get(this.index++) if ch == '{' { - return this.parse_object() ! throw invalid, EMSG + return this.parse_object() !> } if ch == '[' { - return this.parse_array() ! throw invalid, EMSG + return this.parse_array() !> } if ch == '"' { - let str = this.read_string() ! throw invalid, EMSG + let str = this.read_string() !> return Value { type: TYPE.string string_value: str @@ -57,7 +59,7 @@ class Parser { } if (ch >= '0' && ch <= '9') || ch == '-' { this.index-- - return this.parse_number() ! throw invalid, EMSG + return this.parse_number() !> } if ch >= 'a' && ch <= 'z' { this.index-- @@ -66,10 +68,14 @@ class Parser { if word == "true" : return Value { type: TYPE.bool, bool_value: true } if word == "false" : return Value { type: TYPE.bool, bool_value: false } } - throw invalid, "Unknown value character '%{ch.to_ascii_string()}' at index: " + this.index + throw .invalid { + message: "Unknown value character" + at_index: this.index + // char: ch.to_ascii_string() + } } - - fn parse_object() Value !invalid { + - fn parse_object() Value !ParseError { let count = 0 let values = Map[Value]{} while true { @@ -79,19 +85,25 @@ class Parser { break } if count > 0 { - if ch != ',' : throw invalid, "Missing object comma at index: " + this.index + if ch != ',' : throw .invalid { + message: "Missing object comma" + at_index: this.index + } this.skip_whitespace() ch = this.data.get(this.index++) } count++ if ch == '"' { - let key = this.read_string() ! throw invalid, EMSG - this.expect(':') ! throw invalid - let value = this.parse_value() ! throw invalid, EMSG + let key = this.read_string() !> + this.expect(':') !> + let value = this.parse_value() !> values.set(key, value) continue } - throw invalid, "Invalid json object syntax at index: " + this.index + throw .invalid { + message: "Invalid json object syntax" + at_index: this.index + } } return Value { type: TYPE.object @@ -99,7 +111,7 @@ class Parser { } } - - fn parse_array() Value !invalid { + - fn parse_array() Value !ParseError { let values = type:Array[Value]{} let count = 0 while true { @@ -109,13 +121,16 @@ class Parser { break } if count > 0 { - if ch != ',' : throw invalid, "Missing array comma at index: " + this.index + if ch != ',' : throw .invalid { + message: "Missing array comma" + at_index: this.index + } this.skip_whitespace() ch = this.data.get(this.index++) } count++ this.index-- - let v = this.parse_value() ! throw invalid, EMSG + let v = this.parse_value() !> values.append(v) } return Value { @@ -124,7 +139,7 @@ class Parser { } } - - fn parse_number() Value !invalid { + - fn parse_number() Value !ParseError { let buf = this.str_buf let data = this.data buf.clear() @@ -186,10 +201,14 @@ class Parser { return buf.to_string() } - - fn expect(ch: u8) !invalid { + - fn expect(ch: u8) !ParseError { this.skip_whitespace() let c = this.data.get(this.index++) - if c != ch : throw invalid, "Expected '%{ch.to_ascii_string()}' at index: " + this.index + if c != ch : throw .invalid { + message: "Expected a different character" + at_index: this.index + // char: ch.to_ascii_string() + } } - fn skip_whitespace() { @@ -204,7 +223,7 @@ class Parser { } } - - fn read_string() String !invalid { + - fn read_string() String !ParseError { let buf = this.str_buf let data = this.data buf.clear() @@ -217,12 +236,18 @@ class Parser { else if ch == 'n' : ch = '\n' else if ch == 'r' : ch = '\r' else if ch == 't' : ch = '\t' - else if ch == 0 : throw invalid, "Unexpected EOF at index " + this.index + else if ch == 0 : throw .invalid { + message: "Unexpected EOF" + at_index: this.index + } buf.append_byte(ch) continue } if ch == '"' : break - if ch == 0 : throw invalid, "Unexpected EOF at index " + this.index + if ch == 0 : throw .invalid { + message: "Unexpected EOF" + at_index: this.index + } buf.append_byte(ch) } return buf.to_string() diff --git a/lib/src/json/value.valk b/lib/src/json/value.valk index 4c2db2b8..e1a1e342 100644 --- a/lib/src/json/value.valk +++ b/lib/src/json/value.valk @@ -21,11 +21,11 @@ class Value { } // #doc "Get object value. If missing: throw an error" - + fn find(key: String) Value !missing { + + fn find(key: String) Value !LookupError { this.type = TYPE.object let vv = this.object_values if isset(vv) : vv.get(key) -> val : return val - throw missing + throw .missing } // #doc "Set object value" @@ -73,10 +73,10 @@ class Value { } // #doc "Get array value by index" - + fn get_index(index: uint) Value !range { + + fn get_index(index: uint) Value !LookupError { let vv = this.array_values - if isset(vv) : return vv.get(index) ! throw range - throw range + if isset(vv) : return vv.get(index) !> + throw .range } // #doc "Remove array value by index" diff --git a/lib/src/markdown/to-html.valk b/lib/src/markdown/to-html.valk index 65911586..498b71ac 100644 --- a/lib/src/markdown/to-html.valk +++ b/lib/src/markdown/to-html.valk @@ -243,21 +243,21 @@ fn line_format_all(line: String) String { return line } -fn find_end_scope(line: String, start: uint, char_start: u8, char_end: u8) uint !none { +fn find_end_scope(line: String, start: uint, char_start: u8, char_end: u8) uint !LookupError { let depth = 1 let i = start while i < line.length { let ch = line.get(i) if ch == char_start : depth++ else if ch == char_end { - if depth-- == 0 : throw none + if depth-- == 0 : throw .missing if depth == 0 { return i } } i++ } - throw none + throw .missing } fn line_format(line: String, find1: String, find2: String, before: String, after: String, no_ascii: bool (false)) String { diff --git a/lib/src/template/error.valk b/lib/src/template/error.valk new file mode 100644 index 00000000..89ab7ed2 --- /dev/null +++ b/lib/src/template/error.valk @@ -0,0 +1,8 @@ + +error Error (template_not_found) payload { + template_name: String + message: String +} +error ParseError (invalid) extends(Error) payload { + at_index: uint +} diff --git a/lib/src/template/parser.valk b/lib/src/template/parser.valk index 98c24cd4..8220b649 100644 --- a/lib/src/template/parser.valk +++ b/lib/src/template/parser.valk @@ -1,6 +1,5 @@ use json -use fs class Parser { parent: ?Parser (null) @@ -75,9 +74,9 @@ class Parser { // Handle token if !escaped { if this.token_is("@include") { - this.expect("(", false) ! return EMSG - this.expect("\"", false) ! return EMSG - let sub_name = this.read_str() ! return EMSG + this.expect("(", false) ! return E.message + this.expect("\"", false) ! return E.message + let sub_name = this.read_str() ! return E.message let sub_content = contents.get(sub_name) ! return this.error("Template not found: " + sub_name) let sub = Parser{ parent: this @@ -88,13 +87,13 @@ class Parser { } result.append_str(sub.render()) - this.expect(")", true) ! return EMSG + this.expect(")", true) ! return E.message continue } if this.token_is("@extend") { - this.expect("(", false) ! return EMSG - this.expect("\"", false) ! return EMSG - let name = this.read_str() ! return EMSG + this.expect("(", false) ! return E.message + this.expect("\"", false) ! return E.message + let name = this.read_str() ! return E.message let content = contents.get(name) ! return this.error("@extend template not found: " + name) let extend_final = ByteBuffer.new() @@ -114,14 +113,14 @@ class Parser { this.result = ByteBuffer.new() result = this.result - this.expect(")", true) ! return EMSG + this.expect(")", true) ! return E.message continue } if this.token_is("@block") { - this.expect("(", false) ! return EMSG - this.expect("\"", false) ! return EMSG - let name = this.read_str() ! return EMSG - this.expect(")", true) ! return EMSG + this.expect("(", false) ! return E.message + this.expect("\"", false) ! return E.message + let name = this.read_str() ! return E.message + this.expect(")", true) ! return E.message let sub = Parser{ parent: this @@ -145,26 +144,26 @@ class Parser { continue } if this.token_is("@yield") { - this.expect("(", false) ! return EMSG - this.expect("\"", false) ! return EMSG - let name = this.read_str() ! return EMSG + this.expect("(", false) ! return E.message + this.expect("\"", false) ! return E.message + let name = this.read_str() ! return E.message let content = this.yields.get(name) ! return this.error("No block found named: " + name) result.append_str(content) - this.expect(")", true) ! return EMSG + this.expect(")", true) ! return E.message continue } if this.token_is("@each") { - this.expect("(", false) ! return EMSG - let val = this.read_value() ! return EMSG + this.expect("(", false) ! return E.message + let val = this.read_value() ! return E.message if !val.is_array() && !val.is_object() : return this.error("@each value must be an array or object") - this.expect("as", true) ! return EMSG - let vname = this.read_var_name() ! return EMSG + this.expect("as", true) ! return E.message + let vname = this.read_var_name() ! return E.message let kname : ?String = null let iname : ?String = null if this.next_is(",", true) { - kname = this.read_var_name() ! return EMSG + kname = this.read_var_name() ! return E.message if this.next_is(",", true) { - iname = this.read_var_name() ! return EMSG + iname = this.read_var_name() ! return E.message } } @@ -195,16 +194,16 @@ class Parser { ignore = true } - this.expect(")", true) ! return EMSG + this.expect(")", true) ! return E.message scope.start = this.i scope.start_line = this.line scope.start_col = this.col continue } if this.token_is("@if") { - this.expect("(", false) ! return EMSG - let val = this.read_value() ! return EMSG - this.expect(")", true) ! return EMSG + this.expect("(", false) ! return E.message + let val = this.read_value() ! return E.message + this.expect(")", true) ! return E.message let scope = this.new_scope(scope_type_if) this.scope = scope @@ -256,9 +255,9 @@ class Parser { if !isset(scope) : return this.error("Using @elif without an '@if' token before it") if scope.type != scope_type_if : return this.error("Using @elif without an '@if' token before it") - this.expect("(", false) ! return EMSG - let val = this.read_value() ! return EMSG - this.expect(")", true) ! return EMSG + this.expect("(", false) ! return E.message + let val = this.read_value() ! return E.message + this.expect(")", true) ! return E.message if !scope.if_is_handled && this.value_is_true(val) { scope.if_is_handled = true @@ -269,7 +268,7 @@ class Parser { } if this.token_is("{{") || this.token_is("{!") { let raw = this.token_is("{!") - let val = this.read_value() ! return EMSG + let val = this.read_value() ! return E.message let str = val.to_string() // Sanitize if !raw { @@ -281,8 +280,8 @@ class Parser { // result.append_str(str) // - if raw : this.expect("!}", true, true) ! return EMSG - else : this.expect("}}", true, true) ! return EMSG + if raw : this.expect("!}", true, true) ! return E.message + else : this.expect("}}", true, true) ! return E.message continue } } @@ -336,7 +335,7 @@ class Parser { return !val.is_null() } - fn read_value(prio: int (999)) json:Value !invalid { + fn read_value(prio: int (999)) json:Value !ParseError { let data = this.data let token = this.token let val : ?json:Value = null @@ -344,9 +343,9 @@ class Parser { this.tok(true, true) // @tokens if token.equals_str("@length") { - this.expect("(", false) ! throw invalid, EMSG + this.expect("(", false) !> let of = this.read_value() !> - this.expect(")", true) ! throw invalid, EMSG + this.expect(")", true) !> val = json:new_uint(of.length()) } // Numbers @@ -373,7 +372,11 @@ class Parser { scope = scope.parent } if !isset(val) { - if !data.has(name) : throw invalid, this.error("Data not found: '%name'") + if !data.has(name) : throw .invalid { + message: this.error("Data not found: '%name'") + template_name: this.name ?? "?" + at_index: this.i + } val = data.get(name) } } @@ -382,7 +385,11 @@ class Parser { if !this.next_is(".", false) : break this.tok(false, true) let name : String = token - if !val.has(name) : throw invalid, this.error("Property not found: '%name'") + if !val.has(name) : throw .invalid { + message: this.error("Property not found: '%name'") + template_name: this.name ?? "?" + at_index: this.i + } val = val.get(name) } @@ -424,12 +431,15 @@ class Parser { return val } - fn read_var_name() String !invalid { - let data = this.data + fn read_var_name() String !ParseError { let token = this.token this.tok(true, true) let name : String = token - if !name.get(0).is_alpha() : throw invalid, this.error("Invalid variable name syntax: '%name' ([a-zA-Z0-9_])") + if !name.get(0).is_alpha() : throw .invalid { + message: this.error("Invalid variable name syntax: '%name' ([a-zA-Z0-9_])") + template_name: this.name ?? "?" + at_index: this.i + } return name } @@ -445,12 +455,16 @@ class Parser { return true } - fn expect(tok: String, allow_space: bool, update: bool (true)) !notfound { + fn expect(tok: String, allow_space: bool, update: bool (true)) !ParseError { this.tok(allow_space, update) - if !this.token_is(tok) : throw notfound, this.error("Expected '%{tok}' instead of '%{this.token}'") + if !this.token_is(tok) : throw .invalid { + message: this.error("Expected '%{tok}' instead of '%{this.token}'") + template_name: this.name ?? "?" + at_index: this.i + } } - fn read_str() String !missing_end { + fn read_str() String !ParseError { let token = this.token token.clear() @@ -475,7 +489,11 @@ class Parser { } token.append_byte(ch) } - throw missing_end, this.error("Missing double-quote") + throw .invalid { + message: this.error("Missing double-quote") + template_name: this.name ?? "?" + at_index: this.i + } } fn tok(allow_space: bool, update: bool (true)) { diff --git a/lib/src/template/render.valk b/lib/src/template/render.valk index 3d18fb47..b750f189 100644 --- a/lib/src/template/render.valk +++ b/lib/src/template/render.valk @@ -13,9 +13,12 @@ global contents : Map[String] (.{}) } } -+ fn render(name: String, data: $T, options: ?RenderOptions (null)) String !FileNotFound { ++ fn render(name: String, data: $T, options: ?RenderOptions (null)) String !Error { let d = json:value(data) - let content = contents.get(name) ! throw FileNotFound, "Content not found for: '" + name + "'. Did you forget to use 'set_content'?" + let content = contents.get(name) ! throw .template_not_found { + message: "Template not found. Did you forget to use 'set_content'?" + template_name: name + } let p = Parser{ name: name content: content diff --git a/lib/src/template/scope.valk b/lib/src/template/scope.valk index d887ed5a..62d2f84f 100644 --- a/lib/src/template/scope.valk +++ b/lib/src/template/scope.valk @@ -20,10 +20,10 @@ value scope_type_if (2) if_is_handled: bool (false) data: ?Map[json:Value] (null) - fn set_list_index(index: uint) !range { + fn set_list_index(index: uint) !LookupError { this.index = index // - let item = this.list.get(index) ! throw range + let item = this.list.get(index) !> let d = this.get_data() // Value d.set(this.vname, item) diff --git a/src/build/ast.valk b/src/build/ast.valk index 7c7e506f..3f4ab487 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -707,18 +707,29 @@ fn ast_throw(p: Parser, scope: Scope) { if !etype.codes.has_value(err) : p.error("No error code named '" + err + "' in error type: " + etype.display_name) let hash = helper:ctxhash_u32(err) + // Payload let payload : ?Map[Value] = null - let msg = Str.new_value(err, p.ctx) - p.tok(true, false, false) - // if p.word_is(",") { - // p.tok(true, false) - // msg = read_value(p, scope) - // if (msg.rett.get_class() !? null) != b.valk_class("type", "String") { - // p.error("Invalid throw message value. Expected a 'String' instead of: " + msg.rett) - // } - // } + if p.next_word_is("{", true, false, true) { + payload = .{} + while !p.next_word_is("}", true, true, true) { + // Property + let name = p.read_word(true, true) + let type = etype.payload.get(name) ! p.error("There is no payload property named '%name' in error type: " + etype.display_name) + if payload.has(name) : p.error("Duplicate payload '%name'") + p.expect(":", true, false) + // Value + let sg = p.suggest_type + p.suggest_type = type + let val = read_value(p, scope) + p.suggest_type = sg + // + payload.set(name, val) + // + p.next_word_is(",", true, true, true) + } + } - ast_gen_throw(p.ctx, scope, etype, vgen_int(hash, etype.get_type(null)), payload, p.clone_chunk().path_and_line()) + ast_gen_throw(p.ctx, scope, etype, vgen_int(hash, etype.get_type(null)), payload, p.chunk.path_and_line()) } fn ast_cothrow(p: Parser, scope: Scope) { diff --git a/tests/try-convert.valk b/tests/try-convert.valk index a4307e4e..548ec438 100644 --- a/tests/try-convert.valk +++ b/tests/try-convert.valk @@ -2,8 +2,8 @@ value tc_v1 (8) value tc_v2 (2) -fn tc_fn1() uint !err { - throw err +fn tc_fn1() uint !SomeError { + throw .error } test "Try convert: Suggest number types" { diff --git a/tests/vscope.valk b/tests/vscope.valk index 3d379900..ddd5cbdd 100644 --- a/tests/vscope.valk +++ b/tests/vscope.valk @@ -1,12 +1,12 @@ -fn vscope_err() int !err { - throw err +fn vscope_err() int !SomeError { + throw .error } -fn vscope_err2() (int, int) !err { - throw err +fn vscope_err2() (int, int) !SomeError { + throw .error } -fn vscope_err3() (String, String) !err { - throw err +fn vscope_err3() (String, String) !SomeError { + throw .error } fn mvtest() (String, String) { return ("1", "2") From 23b659e0ecc56bfda264b4cbddc860a8b74c4a73 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 11:17:12 +0100 Subject: [PATCH 10/32] update --- lib/src/fs/in-memory.valk | 4 ++-- lib/src/http/router.valk | 21 +++++++++------------ lib/src/http/worker.valk | 2 +- src/build/ast-gen.valk | 1 + src/build/coro-gen.valk | 4 ++-- src/build/error-handling.valk | 2 +- src/build/error-type.valk | 2 +- src/build/ir-gen.valk | 1 + src/build/ir-value.valk | 2 +- src/build/stage-1-fc.valk | 6 +++--- src/build/stage-2-4-types.valk | 3 ++- src/build/type.valk | 11 ++++++++--- src/build/value.valk | 13 ++++++++----- tests/closures.valk | 18 +++++++++++------- tests/coros.valk | 4 ++-- tests/http.valk | 22 +++++++++++----------- tests/json.valk | 2 +- tests/match.valk | 7 +++---- tests/template.valk | 4 ++-- 19 files changed, 70 insertions(+), 59 deletions(-) diff --git a/lib/src/fs/in-memory.valk b/lib/src/fs/in-memory.valk index da8a09f6..0054195f 100644 --- a/lib/src/fs/in-memory.valk +++ b/lib/src/fs/in-memory.valk @@ -24,8 +24,8 @@ use io } + static fn create_from_file(path: String) SELF !io:IoError { let buffer = ByteBuffer.new(128) - let stream = stream(path, true, false) ! throw .open - while stream.read(32000, buffer) ! throw .read {} + let stream = stream(path, true, false) !> + while stream.read(32000, buffer) !> : {} return SELF.create_from_buffer(buffer) } diff --git a/lib/src/http/router.valk b/lib/src/http/router.valk index 0cbfcad0..b1244427 100644 --- a/lib/src/http/router.valk +++ b/lib/src/http/router.valk @@ -9,6 +9,8 @@ class RouteArg { i: uint } +error RouteError (invalid) + + class Router[T] { - block: RouteBlock[T] @@ -22,11 +24,10 @@ class RouteArg { } } - + fn add(method: String, url: String, handler: T) !invalid_route { + + fn add(method: String, url: String, handler: T) { - if url.bytes == 0 || url.get(0) != '/' { - throw invalid_route - } + if url.is_empty() : url = "/" + if url[0] != '/' : url = "/" + url let url_tmp = url.part(1, url.bytes - 1) let parts = url_tmp.split("/") @@ -46,15 +47,11 @@ class RouteArg { let is_wcard = part == "*" let key = part - if is_wcard && has_wcard { - throw invalid_route - } + // if is_wcard && has_wcard : throw .invalid if is_arg { let name = key.part(1, key.bytes - 1) - if name.bytes == 0 { - throw invalid_route - } + // if name.bytes == 0 : throw .invalid args.append(RouteArg { i: i, name: name }) key = "@" } else if is_wcard { @@ -75,7 +72,7 @@ class RouteArg { block.route = Route[T]{ handler: handler, args: args } } - + fn find(method: String, url: String) Route[T] !not_found { + + fn find(method: String, url: String) Route[T] !LookupError { let parts_ = url.split("/") let parts = Array[String].new(2) each parts_ as part, i { @@ -92,7 +89,7 @@ class RouteArg { if isset(r) { return r } - throw not_found + throw .missing } - static fn find_route(block: RouteBlock[T], index: u8, arg_c: u8, parts: Array[String]) ?Route[T] { diff --git a/lib/src/http/worker.valk b/lib/src/http/worker.valk index de64a436..64553473 100644 --- a/lib/src/http/worker.valk +++ b/lib/src/http/worker.valk @@ -12,7 +12,7 @@ fn worker(server: Server) { while true { let netcon = sock.accept() ! { - if error_is(E, too_many_connections) { + if error_is(E.code, too_many_connections) { println("[x] Too many connections") continue } diff --git a/src/build/ast-gen.valk b/src/build/ast-gen.valk index ad40e7d4..75413dab 100644 --- a/src/build/ast-gen.valk +++ b/src/build/ast-gen.valk @@ -64,6 +64,7 @@ fn ast_gen_throw(ctx: Context, scope: Scope, etype: ErrorType, code: Value, payl if isset(payload) { each payload as val, name { let g = etype.payload_globals.get(name) ! ctx.error("Bug: Missing payload store") + ctx.uses_global(g) ast_gen_assign(ctx, scope, vgen_global(g), val, false, null) } } diff --git a/src/build/coro-gen.valk b/src/build/coro-gen.valk index 2468b695..74b77b92 100644 --- a/src/build/coro-gen.valk +++ b/src/build/coro-gen.valk @@ -128,8 +128,8 @@ fn coro_generate(p: Parser, scope: Scope, on: Value) Value { code.append_str(")") if fi.can_error { code.append_str(" ! {\n") - code.append_str(" coro.error_code = E.@cast(u32)\n") - code.append_str(" coro.error_msg = EMSG\n") + code.append_str(" coro.error_code = E.code.@cast(u32)\n") + // code.append_str(" coro.error_msg = E.message\n") code.append_str(" coro.complete()\n") code.append_str(" return\n") code.append_str("}\n") diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index d6cd1477..e7441f12 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -9,7 +9,7 @@ class ErrorHandler { err_check: Value scope: Scope decl_err: ?Decl (null) - decl_msg: ?Decl (null) + // decl_msg: ?Decl (null) } global errh_tokens : ?Array[String] (null) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index 6f193839..2c3b98bd 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -82,7 +82,7 @@ class ErrorType { } if codes.length == 0 { let p = Parser.new(this.chunk_define, null) - p.error("Errors need to have atleast 1 error code defined") + p.error("Error types need to have atleast 1 error code defined") } this.parsed = true } diff --git a/src/build/ir-gen.valk b/src/build/ir-gen.valk index c0246917..e2ce499d 100644 --- a/src/build/ir-gen.valk +++ b/src/build/ir-gen.valk @@ -342,6 +342,7 @@ fn ir_cast(ir: IR, val: String, from: Type, to: Type) String { let ir_from = ir_type(from) let ir_to = ir_type(to) + if ir_from == ir_to : return val let ptr_int_type = "i64" let ptr_bit_size : uint = 64 diff --git a/src/build/ir-value.valk b/src/build/ir-value.valk index afb25115..bda8171a 100644 --- a/src/build/ir-value.valk +++ b/src/build/ir-value.valk @@ -130,7 +130,7 @@ fn ir_value_resolve(ir: IR, v: Value) String { let on = v.value1 ?! ir.error("Missing return-value on value (bug)") let iron = ir_value(ir, on) let rett = on.rett - if !rett.is_multi() : ir.error("Extract value on non-multi type") + if !rett.is_multi() : return iron return ir_extract(ir, iron, ir_type(rett), v.int) } if vt == VAL.ptrv { diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index 7d39f25e..b5a4e95f 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -629,7 +629,7 @@ fn parse_error(p: Parser, fc: Fc, act: int) { let codes = HashMap[u32, String]{} p.expect("(", true, true) - while true { + while !p.next_word_is(")", true, true, true) { let code = p.read_word(true, true) if code == "code" : p.error("You cannot use 'code' as an error code, this is a reserved name for the language.") let val = helper:ctxhash_u32(code) @@ -637,8 +637,8 @@ fn parse_error(p: Parser, fc: Fc, act: int) { if name != code : p.error("There are 2 error names that resolve to the same numeric hash: '%name' vs '%code'. You must use a different error name for 1 of them.") } codes.set(val, code) - p.expect2(",", ")", true, true) - if p.word_is(")") : break + // + p.next_word_is(",", true, true, true) } if p.next_word_is("extends", true, true, true) { diff --git a/src/build/stage-2-4-types.valk b/src/build/stage-2-4-types.valk index 562e1bdf..134916b3 100644 --- a/src/build/stage-2-4-types.valk +++ b/src/build/stage-2-4-types.valk @@ -155,9 +155,10 @@ fn parse_func_args(func: Func) { func.can_error = true let types = func.rett.unroll().copy() types.append(p.build.valk_type("type", "u32")) - types.append(p.build.valk_type("type", "String")) + // types.append(p.build.valk_type("type", "String")) func.rett_real = type_multi(p.build, types) } + t = p.tok(true, true) } while t == TOK.flag || t == TOK.at_word { diff --git a/src/build/type.valk b/src/build/type.valk index c298c64c..c8fe47d0 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -252,8 +252,10 @@ class Type { } } str += ")" - if isset(info) && info.can_error { + if isset(info) { str += "!" + let etype = info.error_type + if isset(etype) : str += etype.display_name } return result + str } @@ -390,8 +392,11 @@ class Type { // Errors let base_et = base.error_type let type_et = type.error_type - if !isset(base_et) || !isset(type_et) : return false - return base_et.is_compat(type_et) + if base_et != type_et { + if !isset(base_et) || !isset(type_et) : return false + return base_et.is_compat(type_et) + } + return true } if base.type == TYPE.func || base.type == TYPE.closure { let fi_base = this.func_info diff --git a/src/build/value.valk b/src/build/value.valk index aa1dbb5c..d3405387 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -19,7 +19,7 @@ class Value { values_map: ?Map[Value] (null) unrolls: ?Array[Value] (null) func_err_code: ?Value (null) - func_err_msg: ?Value (null) + // func_err_msg: ?Value (null) errh: ?ErrorHandler (null) string: String ("") string2: String ("") @@ -81,11 +81,12 @@ class Value { fn trim_errors() Value { if this.type != VAL.func_call : return this let code = this.func_err_code - let msg = this.func_err_msg - if !isset(code) && !isset(msg) : return this + // let msg = this.func_err_msg + // if !isset(code) && !isset(msg) : return this + // if !isset(code) : return this let values = this.unroll().copy() if isset(code) : values.remove_value(code) - if isset(msg) : values.remove_value(msg) + // if isset(msg) : values.remove_value(msg) return vgen_grouped_values(this.rett.build, values) } @@ -1454,7 +1455,9 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { return vgen_int(errv, etype.get_type(key)) } if etype.payload.has(key) { - panic("TODO: payload data") + let g = etype.payload_globals.get(key) ! p.bug("Missing error type payload global") + p.ctx.uses_global(g) + return vgen_global(g) } let message = "Error type has no error code or payload property named: '" + key + "'. Valid options are: " + etype.codes.values().join(", ") let payload = etype.payload diff --git a/tests/closures.valk b/tests/closures.valk index 7e3da4b6..0c3886af 100644 --- a/tests/closures.valk +++ b/tests/closures.valk @@ -42,8 +42,8 @@ test "Closures" { assert(sum == 15) // Errors - let ecl = fn(v: bool) int !err { - if v : throw err + let ecl = fn(v: bool) int !SomeError { + if v : throw .error return 1 } @@ -82,20 +82,24 @@ test "Closure: bind order test" { assert(v) } +error ClosureTestError (error) payload { + message: String +} + test "Closure: errors" { let has_err = false let force_closure = false let ref = &force_closure - fn() !testerr { - fn() !testerr { + fn() !ClosureTestError { + fn() !ClosureTestError { ref[0] = true - throw testerr, "A" + throw .error { message: "A" } }() ! { - throw testerr, EMSG + "B" + throw .error { message: E.message + "B" } } }() ! { has_err = true - assert(EMSG == "AB") + assert(E.message == "AB") } assert(has_err) assert(force_closure) diff --git a/tests/coros.valk b/tests/coros.valk index a607f4fb..b2dcfdf6 100644 --- a/tests/coros.valk +++ b/tests/coros.valk @@ -53,8 +53,8 @@ test "Coro: throw" { test "Coro: cothrow" { let task = co coro_cothrow("x") let res = await task !? <{ - assert(error_is(E, testerr)) - assert(EMSG == "cothrow_msg") + assert(error_is(E.code, testerr)) + // assert(E.message == "cothrow_msg") return "z" } assert(res == "z") diff --git a/tests/http.valk b/tests/http.valk index a065b704..20736a2e 100644 --- a/tests/http.valk +++ b/tests/http.valk @@ -18,10 +18,10 @@ fn http_handler_file(req: http:Request) http:Response { fn http_handler(req: http:Request) http:Response { let r = http:Router[fn(http:Request)(http:Response)].new() - r.add("POST", "/test-data", http_handler_data) _ - r.add("GET", "/test-file", http_handler_file) _ - r.add("GET", "/ping", fn(req: http:Request) http:Response { return http:Response.text("pong") }) _ - r.add("GET", "/stop", http_handler_file) _ + r.add("POST", "/test-data", http_handler_data) + r.add("GET", "/test-file", http_handler_file) + r.add("GET", "/ping", fn(req: http:Request) http:Response { return http:Response.text("pong") }) + r.add("GET", "/stop", http_handler_file) let route = r.find(req.method, req.path) ! { println("ROUTE NOT FOUND: '" + req.path + "'") @@ -43,12 +43,12 @@ test "Http: server/client" { let post_data = Map[String]{ "p3" => "v3" } let json = json:value(post_data) // println(json.encode(true)) - let res = http:request("POST", "http://127.0.0.1:9000/test-data?p1=v1", http:Options{ + let res = http:request("POST", "http://127.0.0.1:9000/test-data?p1=v1", http:Options { query_data: Map[String]{ "p2" => "v2" } body: json.encode() headers: Map[String]{ "content-type" => "application/json" } }) ! { - println("ERR: " + EMSG) + // println("ERR: " + E.message) assert(false) return } @@ -56,7 +56,7 @@ test "Http: server/client" { // Request 2 res = http:request("GET", "http://127.0.0.1:9000/test-file?p1=v1") ! { - println("ERR: " + EMSG) + // println("ERR: " + EMSG) assert(false) return } @@ -67,7 +67,7 @@ test "Http: server/client" { if fs:exists(to) : fs:delete(to) ! assert(false) // http:download("http://127.0.0.1:9000/test-file?p1=v1", to) ! { - println("ERR: " + EMSG) + // println("ERR: " + EMSG) assert(false) } // Check exists @@ -75,7 +75,7 @@ test "Http: server/client" { // Check file content let file_content = fs:read(to) !? <{ println("Failed reading file") - println(EMSG) + // println(EMSG) return "" } assert(file_content == res.body) @@ -92,7 +92,7 @@ test "Https: Client" { // #end let req = http:request("GET", "https://valk-lang.dev/api/versions") ! { - println("REQUEST ERROR (1): " + EMSG) + // println("REQUEST ERROR (1): " + EMSG) assert(false) return } @@ -110,7 +110,7 @@ test "Https: Client" { // let res = http:request("GET", "http://127.0.0.1:9001/ping") ! { - println("REQUEST ERROR (3): " + EMSG) + // println("REQUEST ERROR (3): " + EMSG) assert(false) return } diff --git a/tests/json.valk b/tests/json.valk index b203a7b6..d35f109b 100644 --- a/tests/json.valk +++ b/tests/json.valk @@ -75,7 +75,7 @@ test "Json decode" { } let value = json:decode(json) !? <{ - println(EMSG) + println(E.message) assert(false) return json:new_null() } diff --git a/tests/match.valk b/tests/match.valk index 8de02715..ac7fc762 100644 --- a/tests/match.valk +++ b/tests/match.valk @@ -32,10 +32,9 @@ test "Match" { assert(c == "v1") let arr = Array[String].new() - arr.get(0) ! match E { - E.not_found => { - a = 2 - } + arr.get(0) ! match E.code { + E.missing => a = 2 + default => a = 3 } assert(a == 2) diff --git a/tests/template.valk b/tests/template.valk index 4c1a7b77..c786d3be 100644 --- a/tests/template.valk +++ b/tests/template.valk @@ -22,7 +22,7 @@ test "Templates" { let files = #embed_dir("assets/views") template:set_content_many(files) - let result = template:render("pages/test-page.html", data, options) !? EMSG + let result = template:render("pages/test-page.html", data, options) !? E.message let expect = fs:read(vdir + "/expect/result-1.html") !? "-?-" assert(result == expect) @@ -45,7 +45,7 @@ test "Template extending" { let files = #embed_dir("assets/views") template:set_content_many(files) - let result = template:render("pages/test-extend.html", data, options) !? EMSG + let result = template:render("pages/test-extend.html", data, options) !? E.message let expect = fs:read(vdir + "/expect/result-2.html") !? "-?-" assert(result == expect) From 70780c37506b7c1be8f6ee39256704e9e5dd88f4 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 11:18:50 +0100 Subject: [PATCH 11/32] fix test --- tests/coros.valk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/coros.valk b/tests/coros.valk index b2dcfdf6..866d5519 100644 --- a/tests/coros.valk +++ b/tests/coros.valk @@ -5,7 +5,7 @@ use valk:time global coro_test_str: String ("") -error TsetCoroError (err) payload { +error TestCoroError (error) payload { message: String } @@ -19,8 +19,8 @@ fn coro_val(suffix: String) String { return "a" + suffix } -fn coro_throw(suffix: String) String !TsetCoroError { - throw .err { message: "throw_msg" } +fn coro_throw(suffix: String) String !TestCoroError { + throw .error { message: "throw_msg" } } fn coro_cothrow(suffix: String) String { @@ -44,7 +44,7 @@ test "Coro: await" { test "Coro: throw" { let task = co coro_throw("x") let res = await task !? <{ - assert(error_is(E.code, testerr)) + assert(error_is(E.code, error)) return "z" } assert(res == "z") From 052bb122fd231c388bab47a2fd23f93d1932cc31 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 13:04:35 +0100 Subject: [PATCH 12/32] fix extend + default payload values --- src/build/closures.valk | 5 +++-- src/build/error-handling.valk | 28 +++++++++++++++++++++++++--- src/build/error-type.valk | 5 +++++ src/build/type.valk | 4 ++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/build/closures.valk b/src/build/closures.valk index ed1f74ba..fc69fc27 100644 --- a/src/build/closures.valk +++ b/src/build/closures.valk @@ -117,9 +117,10 @@ fn generate_closure_outer_function(parent: Func, inner_func: Value, fi: FuncInfo // Call inner function let inner_call = vgen_func_call(b, fast.scope, inner_func, inner_values) - if fi.can_error { + let etype = fi.error_type + if isset(etype) { // Pass error - inner_call = value_pass_handler(ctx, fast.scope, inner_call) + inner_call = value_pass_handler(ctx, fast.scope, inner_call, etype) } // Return value diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index e7441f12..59ae9605 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -40,7 +40,7 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp } // Ignore error if p.word_is("!>") { - return value_pass_handler(ctx, scope, on) + return value_pass_handler(ctx, scope, on, error_type) } // Panic on error if p.word_is("!!") { @@ -201,7 +201,7 @@ fn value_panic_handler(p: Parser, scope: Scope, on: Value, msg: String) Value { return vgen_this_but_that(condv, on.trim_errors()) } -fn value_pass_handler(ctx: Context, scope: Scope, on: Value) Value { +fn value_pass_handler(ctx: Context, scope: Scope, on: Value, etype: ErrorType) Value { if !on.can_error() : ctx.error("Error handler on non-function call value (4): " + on.type) @@ -210,15 +210,37 @@ fn value_pass_handler(ctx: Context, scope: Scope, on: Value) Value { let sub = scope.sub_scope(SCOPE.default) let alt = vgen_inline_scope(sub, type_void(b)) + let to = func.error_type + if !isset(to) : ctx.error("Passing an error but your function has no error type") + if !to.is_compat(etype) : ctx.error("Incompatible error types: " + etype + " -> " + to + ". The '%to' type must contain '%etype' in the 'extends' list") let values = Array[Value]{} each func.rett.unroll() as type { values.append(vgen_empty_value(type) ?! continue) } values.append(on.func_err_code ?! ctx.error("Missing error code (bug)")) - // values.append(on.func_err_msg ?! ctx.error("Missing error message (bug)")) ast_return_value(func, sub, vgen_grouped_values(b, values)) + each to.payload_globals as g, name { + if !etype.payload.has(name) { + let type = g.get_type() + let v : ?Value = null + if type.is_int() { + v = vgen_int(0, type) + } else if type.is_float() { + v = vgen_float(0, type) + } else if type.nullable { + v = vgen_null(type) + } else if type.is_string() { + v = Str.new_value("/", ctx) + } + if !isset(v) : ctx.error("You must define a payload value for: " + type) + // + ctx.uses_global(g) + ast_gen_assign(ctx, scope, vgen_global(g), v, false, null) + } + } + // Err check let err_check = vgen_error_check(b, on) let condv = vgen_cond_value(err_check, alt) diff --git a/src/build/error-type.valk b/src/build/error-type.valk index 2c3b98bd..142787d9 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -20,6 +20,10 @@ class ErrorType { parsing: bool (false) parsed: bool (false) + fn to_str() String $to { + return this.display_name + } + fn get_type(code: ?String) Type { if !isset(code) { let type = this.cache_type @@ -54,6 +58,7 @@ class ErrorType { let et = idf.error_type ?! p.error("Missing error type") if !et.parsed : et.parse() if et.extends.contains(this) : p.error("Circular extend for error type: " + this.display_name + " <> " + et.display_name) + this.extends.append(et) each et.extends as subet { this.extends.append(subet) } diff --git a/src/build/type.valk b/src/build/type.valk index c8fe47d0..1db5f7aa 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -184,6 +184,10 @@ class Type { fn is_closure() bool { return this.type == TYPE.closure } + fn is_string() bool { + let class = this.get_class() ! return false + return class == this.build.valk_class("type", "String") + } fn str() String $to { return this.to_str(false) From a9f453d7ce2bbe243c9c00c54287de1e994d50bb Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 13:17:01 +0100 Subject: [PATCH 13/32] payload test --- src/build/error-type.valk | 2 +- tests/error-payload.valk | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/error-payload.valk diff --git a/src/build/error-type.valk b/src/build/error-type.valk index 142787d9..f89b82b5 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -122,11 +122,11 @@ fn get_payload_global(b: Build, type: Type, exclude: Array[String]) Global { while exclude.contains(key) { key = base + "_" + (i++) } + exclude.append(key) b.payload_globals.get(key) -> g : return g let u = b.generated_unit() let dname = "ErrorPayload_" + key let g = u.new_global(b.generated_fc(), act_public_all, dname, "error_payload_" + helper:ctxhash(key), dname, null, null, false) - exclude.append(key) b.payload_globals.set(key, g) return g } diff --git a/tests/error-payload.valk b/tests/error-payload.valk new file mode 100644 index 00000000..9cb256a1 --- /dev/null +++ b/tests/error-payload.valk @@ -0,0 +1,39 @@ + +error Pay1 (error) payload { + message1: String +} +error Pay2 (error) extends (Pay1) payload { + message2: String + message3: String +} + +fn test_payload1() !Pay1 { + throw .error { message1: "A" } +} +fn test_payload2() !Pay2 { + test_payload1() !> +} +fn test_payload3() !Pay2 { + test_payload1() ! throw .error { message3: "C" } +} +fn test_payload4() !Pay2 { + test_payload1() ! throw .error { message1: "X", message3: "C" } +} + +test "Error payload" { + test_payload2() ! { + assert(E.message1 == "A") + assert(E.message2 == "/") + assert(E.message3 == "/") + } + test_payload3() ! { + assert(E.message1 == "A") + assert(E.message2 == "/") + assert(E.message3 == "C") + } + test_payload4() ! { + assert(E.message1 == "X") + assert(E.message2 == "/") + assert(E.message3 == "C") + } +} \ No newline at end of file From b7a61344a68f398211b9c1ba0e884ae7551a5540 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 15:39:11 +0100 Subject: [PATCH 14/32] lsp --- src/build/value.valk | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/build/value.valk b/src/build/value.valk index d3405387..8ccec7b7 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -1359,16 +1359,13 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { } if for == IDF.type { let type = idf.type - // if p.char(0) == '.' { - if isset(type) { - let class = type.get_class() !? null - if isset(class) { - if class.is_generic_base : class = read_class_generic(p, scope, class) - return handle_class(p, class, scope) - } + if isset(type) { + let class = type.get_class() !? null + if isset(class) { + if class.is_generic_base : class = read_class_generic(p, scope, class) + return handle_class(p, class, scope) } - // } - // return Str.new_value(type, p.ctx) + } } @@ -1442,9 +1439,22 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { if for == IDF.error_value { let ev = idf.error_value if !isset(ev) : p.bug("Missing error value information for identifier") + // + let etype = ev.error_type p.expect(".", false, false) + // + if p.lsp { + let list = Array[String]{ "code" } + each etype.codes as name, code { + list.append(name) + } + each etype.payload as type, name { + list.append(name) + } + p.build.getlsp().check_completion_simple(list) + } + let key = p.read_word(false, false) - let etype = ev.error_type if key == "code" { return ev.codev From dbaf9e817fac098284f97c1bf524406749fe5bbf Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 18:45:22 +0100 Subject: [PATCH 15/32] few fixes --- lib/src/json/error.valk | 1 + lib/src/json/parse.valk | 4 ++-- src/lsp/lsp.valk | 14 ++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/src/json/error.valk b/lib/src/json/error.valk index 1f807fea..5e191286 100644 --- a/lib/src/json/error.valk +++ b/lib/src/json/error.valk @@ -2,4 +2,5 @@ error ParseError (invalid) payload { at_index: uint message: String + character: u8 } diff --git a/lib/src/json/parse.valk b/lib/src/json/parse.valk index b725dd72..294e668e 100644 --- a/lib/src/json/parse.valk +++ b/lib/src/json/parse.valk @@ -71,7 +71,7 @@ class Parser { throw .invalid { message: "Unknown value character" at_index: this.index - // char: ch.to_ascii_string() + character: ch } } @@ -207,7 +207,7 @@ class Parser { if c != ch : throw .invalid { message: "Expected a different character" at_index: this.index - // char: ch.to_ascii_string() + character: ch } } diff --git a/src/lsp/lsp.valk b/src/lsp/lsp.valk index 7eb75e55..d749bff4 100644 --- a/src/lsp/lsp.valk +++ b/src/lsp/lsp.valk @@ -13,6 +13,7 @@ class Lsp { index: uint (0) stage: uint (0) headers: Map[String] (Map[String]{}) + headers_str: String ("") data: json:Value (json:new_null()) id: int (0) method: String ("") @@ -44,6 +45,7 @@ class Lsp { fn parse() { while this.buf.length > 0 { + let str : String = this.buf if this.stage == 0 { this.parse_headers() ! { if E == E.more { @@ -58,10 +60,9 @@ class Lsp { if this.stage == 1 { this.parse_content() ! { if E == E.more { - // this.log("Needs more (1) : " + this.buf) return } - this.log("Invalid content") + // this.log("Invalid content: '" + this.buf + "' | " + this.buf.length + " | " + str) this.reset() return } @@ -78,6 +79,9 @@ class Lsp { while true { let index = buf.index_of('\r', pos) ! throw more + if index + 1 >= buf.length : throw more + if buf.get(index + 1) != '\n' : throw invalid + if index == pos { // End of headers pos += 2 @@ -86,9 +90,6 @@ class Lsp { // Read header let index2 = buf.index_of(':', pos) ! throw invalid - if index + 1 >= buf.length : throw more - if buf.get(index + 1) != '\n' : throw invalid - let key = buf.part(pos, index2 - pos).trim(" ").trim("\t") let value = buf.part(index2 + 1, index - index2 - 1).trim(" ").trim("\t") pos = index + 2 @@ -98,6 +99,7 @@ class Lsp { } this.stage = 1 + this.headers_str = this.buf.part(0, pos) this.buf.clear_until(pos) this.index = 0 } @@ -109,7 +111,7 @@ class Lsp { this.buf.clear_until(len) let data = json:decode(content) ! { - this.log("Invalid JSON-RPC input: " + content) + this.log("Invalid JSON-RPC input: '%content' | length: %len/%{content.length} | Headers: " + this.headers_str) throw invalid } From c01293d963e2231d305837e8ab54ca61830e42c7 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 18:49:51 +0100 Subject: [PATCH 16/32] fix some errors --- lib/src/core/errors.valk | 2 +- lib/src/core/sysinfo.valk | 6 +++--- lib/src/fs/file.valk | 2 +- lib/src/fs/path.valk | 4 ++-- lib/src/net/socket.valk | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/core/errors.valk b/lib/src/core/errors.valk index 8c721f86..8bea8eb8 100644 --- a/lib/src/core/errors.valk +++ b/lib/src/core/errors.valk @@ -1,6 +1,6 @@ error SomeError (error) -error ExternError (error) +error ExternError (extern) error IterError (end) error InitError (init) error LookupError (missing, exists, range, empty) diff --git a/lib/src/core/sysinfo.valk b/lib/src/core/sysinfo.valk index 8d4a8b64..f6932153 100644 --- a/lib/src/core/sysinfo.valk +++ b/lib/src/core/sysinfo.valk @@ -2,7 +2,7 @@ use ext fn cpu_core_count() uint !ExternError { - let res = cpu_thread_count() ! throw .error + let res = cpu_thread_count() ! throw .extern res = res / 2 if res == 0 : return 1 return res @@ -10,10 +10,10 @@ fn cpu_core_count() uint !ExternError { fn cpu_thread_count() uint !ExternError { #if OS == linux let res = ext:sysconf(SYSCONF._SC_NPROCESSORS_ONLN) - if res < 0 : throw .error + if res < 0 : throw .extern return res.to(uint) #else - throw unknown + throw .extern #end } diff --git a/lib/src/fs/file.valk b/lib/src/fs/file.valk index dfb5e929..c9872e5e 100644 --- a/lib/src/fs/file.valk +++ b/lib/src/fs/file.valk @@ -29,7 +29,7 @@ value default_read_size (32 * 1024) let flags : u32 = ext:FILE_FLAG_OVERLAPPED | ext:FILE_FLAG_SEQUENTIAL_SCAN // let fd = ext:CreateFileA(path.data_cstring, access, share_mode, null, despo, flags, 0) - if fd == ext:INVALID_HANDLE_VALUE : throw open + if fd == ext:INVALID_HANDLE_VALUE : throw .open ext:CreateIoCompletionPort(fd, io:iocp(), 0, 0) diff --git a/lib/src/fs/path.valk b/lib/src/fs/path.valk index caf70e4d..7091fd32 100644 --- a/lib/src/fs/path.valk +++ b/lib/src/fs/path.valk @@ -206,7 +206,7 @@ use core + fn home_dir() String !ExternError { #if OS == win - return core:getenv("USERPROFILE") ! throw .error + return core:getenv("USERPROFILE") ! throw .extern #end - return core:getenv("HOME") ! throw .error + return core:getenv("HOME") ! throw .extern } diff --git a/lib/src/net/socket.valk b/lib/src/net/socket.valk index c4408e0b..4b2ded38 100644 --- a/lib/src/net/socket.valk +++ b/lib/src/net/socket.valk @@ -40,7 +40,7 @@ use io #if OS == win let fd = ext:WSASocketA(ext:AF_INET, ext:SOCK_STREAM, ext:IPPROTO_TCP, null, 0, ext:WSA_FLAG_OVERLAPPED) - if fd == ext:INVALID_SOCKET : throw create + if fd == ext:INVALID_SOCKET : throw .init ext:CreateIoCompletionPort(fd, io:iocp(), 0, 0) #else From 64031efe561a0b5db149639e32199dd0b4e294fd Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 18:54:00 +0100 Subject: [PATCH 17/32] clean up --- lib/src/coro/api.valk | 3 +-- lib/src/coro/coro.valk | 1 - src/build/ast-gen.valk | 3 --- src/build/ast.valk | 2 +- src/build/coro-gen.valk | 3 --- src/build/error-handling.valk | 5 ----- src/build/func.valk | 1 - src/build/value-gen.valk | 4 ---- src/build/value.valk | 5 ----- 9 files changed, 2 insertions(+), 25 deletions(-) diff --git a/lib/src/coro/api.valk b/lib/src/coro/api.valk index 39a6fc34..d52272d6 100644 --- a/lib/src/coro/api.valk +++ b/lib/src/coro/api.valk @@ -19,11 +19,10 @@ fn yield() { else : Coro.loop(coro) } -fn throw(code: u32, msg: String) { +fn throw(code: u32) { let current = current_coro if isset(current) { current.error_code = code - current.error_msg = msg current.complete() return } diff --git a/lib/src/coro/coro.valk b/lib/src/coro/coro.valk index 76650498..03c46c1b 100644 --- a/lib/src/coro/coro.valk +++ b/lib/src/coro/coro.valk @@ -46,7 +46,6 @@ class Coro { g_list_index: uint (0) // Error result error_code: u32 (0) - error_msg: String ("") // async results poll_event: io:PollEvent (0) completion_res: i64 (0) diff --git a/src/build/ast-gen.valk b/src/build/ast-gen.valk index 75413dab..d28b3251 100644 --- a/src/build/ast-gen.valk +++ b/src/build/ast-gen.valk @@ -19,7 +19,6 @@ fn ast_gen_return(ctx: Context, func: Func, scope: Scope, retv: Value) { if isset(etype) { let values = retv.unroll().copy() values.append(vgen_int(0, etype.get_type(null))) - // values.append(vgen_null(b.error_msg_type())) retv = vgen_grouped_values(b, values) } @@ -55,11 +54,9 @@ fn ast_gen_throw(ctx: Context, scope: Scope, etype: ErrorType, code: Value, payl let values = Array[Value]{} each retts as rett { // TODO: use undefined - // values.append(vgen_undefined(b)) values.append(vgen_empty_value(rett) ?! continue) } values.append(code) - // values.append(msg) if isset(payload) { each payload as val, name { diff --git a/src/build/ast.valk b/src/build/ast.valk index 3f4ab487..77c2d285 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -748,7 +748,7 @@ fn ast_cothrow(p: Parser, scope: Scope) { } let tfunc = b.valk_func("coro", "throw") - let values = Array[Value]{ vgen_int(hash, b.valk_type("type", "uint")), Str.new_value(msg, p.ctx) } + let values = Array[Value]{ vgen_int(hash, b.valk_type("type", "uint")) } let fptr = vgen_func_ptr(tfunc) let call = vgen_func_call(b, scope, fptr, values) diff --git a/src/build/coro-gen.valk b/src/build/coro-gen.valk index 74b77b92..9a0cf41e 100644 --- a/src/build/coro-gen.valk +++ b/src/build/coro-gen.valk @@ -129,7 +129,6 @@ fn coro_generate(p: Parser, scope: Scope, on: Value) Value { if fi.can_error { code.append_str(" ! {\n") code.append_str(" coro.error_code = E.code.@cast(u32)\n") - // code.append_str(" coro.error_msg = E.message\n") code.append_str(" coro.complete()\n") code.append_str(" return\n") code.append_str("}\n") @@ -350,11 +349,9 @@ fn coro_await(p: Parser, scope: Scope, on: Value) Value { let res = read_value(sp, sub, 0) let prop = coro_class.get_prop("error_code") ! p.error("Missing error_code property") - // let prop_msg = coro_class.get_prop("error_msg") ! p.error("Missing error_msg property") let codev = vgen_prop(prop, on) codev.rett = etype.get_type(null) res.func_err_code = codev - // res.func_err_msg = vgen_prop(prop_msg, on) // Error handling if can_error || p.has_error_handler_ahead() { diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index 59ae9605..8ce3aa33 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -72,12 +72,7 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp } } - // let msg = on.func_err_msg ?! ctx.error("Missing error message (bug)") - // let w = vgen_wrap(code) - // w.rett = type_error(b, error_type) sub.set_idf(p.ctx, "E", Idf.for_error_value(ev)) - // sub.set_idf(p.ctx, "EMSG", Idf.for_value(msg)) - // sub.ast.append(tgen_statement(msg)) // Left return value let left = on.trim_errors() diff --git a/src/build/func.valk b/src/build/func.valk index 0335a626..868a286b 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -50,7 +50,6 @@ class FuncInfo { if !isset(etype) : return this.rett_types let res = this.rett_types.copy() res.append(etype.get_type(null)) - // res.append(b.error_msg_type()) return res } fn rett(b: Build) Type { diff --git a/src/build/value-gen.valk b/src/build/value-gen.valk index 300df727..3f9adf3f 100644 --- a/src/build/value-gen.valk +++ b/src/build/value-gen.valk @@ -164,14 +164,10 @@ fn vgen_func_call(b: Build, decl_scope: Scope, on: Value, values: Array[Value]) let real_retts = retts.copy() let index = retts.length let code_type = etype.get_type(null) - // let msg_type = b.error_msg_type() let codev = vgen_rett_value(fcall, index, code_type) fcall.func_err_code = codev - // fcall.func_err_msg = vgen_rett_value(fcall, index + 1, msg_type) real_retts.append(code_type) - // real_retts.append(msg_type) fcall.rett = type_multi(b, real_retts) - // fcall.type1 = type_multi(b, real_retts) } return fcall diff --git a/src/build/value.valk b/src/build/value.valk index 8ccec7b7..404f5971 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -19,7 +19,6 @@ class Value { values_map: ?Map[Value] (null) unrolls: ?Array[Value] (null) func_err_code: ?Value (null) - // func_err_msg: ?Value (null) errh: ?ErrorHandler (null) string: String ("") string2: String ("") @@ -81,12 +80,8 @@ class Value { fn trim_errors() Value { if this.type != VAL.func_call : return this let code = this.func_err_code - // let msg = this.func_err_msg - // if !isset(code) && !isset(msg) : return this - // if !isset(code) : return this let values = this.unroll().copy() if isset(code) : values.remove_value(code) - // if isset(msg) : values.remove_value(msg) return vgen_grouped_values(this.rett.build, values) } From 3fbd8e91f875713f52e9a9cc95c5aba7720eb240 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 18:54:46 +0100 Subject: [PATCH 18/32] remove un-used code --- src/build/ast.valk | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/build/ast.valk b/src/build/ast.valk index 77c2d285..7a0100ff 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -738,15 +738,6 @@ fn ast_cothrow(p: Parser, scope: Scope) { let err = p.read_word(true, false) let hash = helper:ctxhash_u32(err) - let msg = err - p.tok(true, false, false) - if p.word_is(",") { - p.tok(true, false) - p.expect_string(true, false) - msg = p.word() - msg = msg.part(1, msg.bytes - 2).unescape() - } - let tfunc = b.valk_func("coro", "throw") let values = Array[Value]{ vgen_int(hash, b.valk_type("type", "uint")) } let fptr = vgen_func_ptr(tfunc) From 9481cc888a74fe0714b49da731d43c72074500dd Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 19:03:59 +0100 Subject: [PATCH 19/32] clean up --- src/build/func.valk | 1 - src/build/value.valk | 35 ----------------------------------- src/lsp/lsp.valk | 5 +---- tests/coros.valk | 1 - tests/http.valk | 7 ------- 5 files changed, 1 insertion(+), 48 deletions(-) diff --git a/src/build/func.valk b/src/build/func.valk index 868a286b..b992a872 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -236,7 +236,6 @@ class Func { t = p.tok(true, true, false) if p.sign_is("!") { t = p.tok(true, true) - // let idf = p.read_idf(this.fc.scope, false, false) p.skip_id(false) t = p.tok(true, true, false) } diff --git a/src/build/value.valk b/src/build/value.valk index 404f5971..a411dae0 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -352,22 +352,15 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals } else if p.word_is("error_is") { p.expect("(", false, false) - // let idf = p.read_idf(scope, true, true) let left = read_value(p, scope) - // if !isset(left) : p.error("First value in 'error_is' is not an error value") if left.rett.type != TYPE.error_code : p.error("First value in 'error_is' is not an error type") - // Left - // let left = vgen_decl(decl) - p.expect(",", true, true) let name = p.read_word(true, true) let nr = helper:ctxhash_u32(name) let right = vgen_int(nr, b.valk_type("type", "u32")) - // let l, r = match_op_values(b, left, right) - // l.rett.compat_check(r.rett, p) v = vgen_compare(left, right, OP.eq, type_valk(b, "bool")) t = p.tok(true, true, false); @@ -1569,16 +1562,6 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false if is_lsp { let lsp = p.build.getlsp() if lsp.is_completion { - // // Error - // if on.rett.type == TYPE.error { - // let etype = on.rett.error_type - // if isset(etype) { - // let comps = Array[String]{ "code" } - // comps.append_many(etype.codes.values()) - // comps.append_many(etype.payload.keys()) - // lsp.check_completion_simple(comps) - // } - // } // $length if size > 0 : lsp.check_completion_simple(Array[String]{ "$length" }) // Enum @@ -1613,24 +1596,6 @@ fn value_prop_access(p: Parser, scope: Scope, on: Value, assignable: bool (false return vgen_cast(on, type) } - // if on.rett.type == TYPE.error { - // let etype = on.rett.error_type - // if !isset(etype) : p.bug("Missing error compiler information") - - // if name == "code" { - // return vgen_cast(on, b.valk_type("type", "u32")) - // } - - // if etype.codes.has_value(name) { - // let errv = helper:ctxhash_u32(name) - // return vgen_int(errv, b.valk_type("type", "u32")) - // } - // if etype.payload.has(name) { - // panic("TODO: payload data") - // } - // p.error("Error type has no error code or payload property named: '" + name + "'. Valid options are: " + etype.codes.values().join(", ")) - // } - let e = on.rett.enum if isset(e) { // TODO diff --git a/src/lsp/lsp.valk b/src/lsp/lsp.valk index d749bff4..b5e93e19 100644 --- a/src/lsp/lsp.valk +++ b/src/lsp/lsp.valk @@ -59,10 +59,7 @@ class Lsp { } if this.stage == 1 { this.parse_content() ! { - if E == E.more { - return - } - // this.log("Invalid content: '" + this.buf + "' | " + this.buf.length + " | " + str) + if E == E.more : return this.reset() return } diff --git a/tests/coros.valk b/tests/coros.valk index 866d5519..ee356fef 100644 --- a/tests/coros.valk +++ b/tests/coros.valk @@ -54,7 +54,6 @@ test "Coro: cothrow" { let task = co coro_cothrow("x") let res = await task !? <{ assert(error_is(E.code, testerr)) - // assert(E.message == "cothrow_msg") return "z" } assert(res == "z") diff --git a/tests/http.valk b/tests/http.valk index 20736a2e..99de9e6e 100644 --- a/tests/http.valk +++ b/tests/http.valk @@ -48,7 +48,6 @@ test "Http: server/client" { body: json.encode() headers: Map[String]{ "content-type" => "application/json" } }) ! { - // println("ERR: " + E.message) assert(false) return } @@ -56,7 +55,6 @@ test "Http: server/client" { // Request 2 res = http:request("GET", "http://127.0.0.1:9000/test-file?p1=v1") ! { - // println("ERR: " + EMSG) assert(false) return } @@ -67,7 +65,6 @@ test "Http: server/client" { if fs:exists(to) : fs:delete(to) ! assert(false) // http:download("http://127.0.0.1:9000/test-file?p1=v1", to) ! { - // println("ERR: " + EMSG) assert(false) } // Check exists @@ -75,7 +72,6 @@ test "Http: server/client" { // Check file content let file_content = fs:read(to) !? <{ println("Failed reading file") - // println(EMSG) return "" } assert(file_content == res.body) @@ -92,7 +88,6 @@ test "Https: Client" { // #end let req = http:request("GET", "https://valk-lang.dev/api/versions") ! { - // println("REQUEST ERROR (1): " + EMSG) assert(false) return } @@ -103,14 +98,12 @@ test "Https: Client" { let failed = false http:request("GET", "https://127.0.0.1:9001/ping") ! { - // println("REQUEST ERROR (2): " + EMSG) failed = true } assert(failed) // let res = http:request("GET", "http://127.0.0.1:9001/ping") ! { - // println("REQUEST ERROR (3): " + EMSG) assert(false) return } From a4835903f5e17cf15652e8960f0b06f8ab209998 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Sun, 15 Mar 2026 23:56:57 +0100 Subject: [PATCH 20/32] improve payload passing --- src/build/ast.valk | 14 ++++++++++++++ src/build/enum.valk | 2 +- src/build/error-handling.valk | 10 +++++++--- src/build/error-value.valk | 6 +++++- src/build/scope.valk | 7 +++++++ src/build/value.valk | 16 +++++++++------- tests/error-payload.valk | 10 +++++++++- 7 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/build/ast.valk b/src/build/ast.valk index 7a0100ff..5fcd1937 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -729,6 +729,20 @@ fn ast_throw(p: Parser, scope: Scope) { } } + let pev = scope.find_parent_error() !? null + if isset(pev) { + let ppay = pev.payload + if isset(ppay) { + payload = payload ?? .{} + each ppay as evp, name { + if !payload.has(name) { + evp.decl.is_used = true + payload.set(name, evp.value) + } + } + } + } + ast_gen_throw(p.ctx, scope, etype, vgen_int(hash, etype.get_type(null)), payload, p.chunk.path_and_line()) } diff --git a/src/build/enum.valk b/src/build/enum.valk index 8b0c234f..8bf53961 100644 --- a/src/build/enum.valk +++ b/src/build/enum.valk @@ -75,6 +75,7 @@ enum SCOPE { func class vscope + error } // Parser @@ -120,7 +121,6 @@ enum TYPE { float undefined array - // error error_code func multi diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index 8ce3aa33..ce006b32 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -50,7 +50,7 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp let expect_value = p.word_is("!?") // Handle error - let sub = scope.sub_scope(SCOPE.default) + let sub = scope.sub_scope(SCOPE.error) // Variables let codev = on.func_err_code ?! ctx.error("Missing error code (bug)") @@ -60,7 +60,8 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp } sub.ast.append(tgen_statement(codev)) if error_type.payload.length > 0 { - let payload = Map[Value]{} + let payload = Map[ErrorValuePayload]{} + ev.payload = payload each error_type.payload_globals as g, name { let gtype = g.type ?! ctx.error("Missing payload global type in error handler") let decl = Decl.new(gtype, false, null) @@ -68,7 +69,10 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp let vdecl = vgen_decl(decl) let v = vgen_global(g) ast_gen_assign(ctx, sub, vdecl, v, true, p.chunk) - payload.set(name, v) + payload.set(name, ErrorValuePayload { + value: v + decl: decl + }) } } diff --git a/src/build/error-value.valk b/src/build/error-value.valk index 540e047b..7c3073fd 100644 --- a/src/build/error-value.valk +++ b/src/build/error-value.valk @@ -2,5 +2,9 @@ class ErrorValue { error_type: ErrorType codev: Value - payload: ?Map[Value] + payload: ?Map[ErrorValuePayload] +} +class ErrorValuePayload { + value: Value + decl: Decl } \ No newline at end of file diff --git a/src/build/scope.valk b/src/build/scope.valk index e432bfee..7ef835b6 100644 --- a/src/build/scope.valk +++ b/src/build/scope.valk @@ -196,4 +196,11 @@ class Scope { this.not_null_remove(decl) } } + + fn find_parent_error() ErrorValue !none { + let pscope = this.find_type(SCOPE.error) ! throw none + let idf = pscope.identifiers.get("E") ! this.build.error("Bug: Missing error value identifier in error scope") + let ev = idf.error_value ?! this.build.error("Bug: Missing error value in error scope") + return ev + } } diff --git a/src/build/value.valk b/src/build/value.valk index a411dae0..e0965174 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -1452,15 +1452,17 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { let errv = helper:ctxhash_u32(key) return vgen_int(errv, etype.get_type(key)) } - if etype.payload.has(key) { - let g = etype.payload_globals.get(key) ! p.bug("Missing error type payload global") - p.ctx.uses_global(g) - return vgen_global(g) + let evps = ev.payload + if isset(evps) { + evps.get(key) -> evp { + evp.decl.is_used = true + return evp.value + } } let message = "Error type has no error code or payload property named: '" + key + "'. Valid options are: " + etype.codes.values().join(", ") - let payload = etype.payload - if payload.length > 0 { - message += " or (payload) " + payload.keys().join(", ") + let etpayload = etype.payload + if etpayload.length > 0 { + message += " or (payload) " + etpayload.keys().join(", ") } p.error(message) } diff --git a/tests/error-payload.valk b/tests/error-payload.valk index 9cb256a1..b154e6d9 100644 --- a/tests/error-payload.valk +++ b/tests/error-payload.valk @@ -10,11 +10,19 @@ error Pay2 (error) extends (Pay1) payload { fn test_payload1() !Pay1 { throw .error { message1: "A" } } +fn test_payload1b() !Pay1 { + throw .error { message1: "B" } +} fn test_payload2() !Pay2 { test_payload1() !> } fn test_payload3() !Pay2 { - test_payload1() ! throw .error { message3: "C" } + test_payload1() ! { + test_payload1b() ! { + assert(E.message1 == "B") + } + throw .error { message3: "C" } + } } fn test_payload4() !Pay2 { test_payload1() ! throw .error { message1: "X", message3: "C" } From 406b275a9403d017841d49ebcde3f1bf800e77b1 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 00:07:37 +0100 Subject: [PATCH 21/32] remove SomeError --- lib/src/core/errors.valk | 2 +- src/build/coro-gen.valk | 2 +- src/build/scope.valk | 2 +- tests/closures.valk | 2 +- tests/try-convert.valk | 2 +- tests/vscope.valk | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/core/errors.valk b/lib/src/core/errors.valk index 8bea8eb8..3f83d1fd 100644 --- a/lib/src/core/errors.valk +++ b/lib/src/core/errors.valk @@ -1,5 +1,5 @@ -error SomeError (error) +error AnError (error) error ExternError (extern) error IterError (end) error InitError (init) diff --git a/src/build/coro-gen.valk b/src/build/coro-gen.valk index 9a0cf41e..9e8142bd 100644 --- a/src/build/coro-gen.valk +++ b/src/build/coro-gen.valk @@ -254,7 +254,7 @@ fn coro_await(p: Parser, scope: Scope, on: Value) Value { if on.rett.type != TYPE.promise : p.error("Cannot use await on this type: " + on.rett) let retts = on.rett.sub_types ?? Array[Type]{} - let etype = on.rett.error_type ?? b.valk_error("core", "SomeError") + let etype = on.rett.error_type ?? b.valk_error("core", "AnError") let can_error = on.rett.can_error let sub = scope.sub_scope(SCOPE.default) diff --git a/src/build/scope.valk b/src/build/scope.valk index 7ef835b6..502c85cb 100644 --- a/src/build/scope.valk +++ b/src/build/scope.valk @@ -98,7 +98,7 @@ class Scope { return b.valk_idf("type", name) } // core - if name == "exit" || name == "panic" || name == "SomeError" || name == "ExternError" || name == "IterError" || name == "InitError" || name == "LookupError" || name == "SyntaxError" { + if name == "exit" || name == "panic" || name == "AnError" || name == "ExternError" || name == "IterError" || name == "InitError" || name == "LookupError" || name == "SyntaxError" { return b.valk_idf("core", name) } diff --git a/tests/closures.valk b/tests/closures.valk index 0c3886af..be4e5e6e 100644 --- a/tests/closures.valk +++ b/tests/closures.valk @@ -42,7 +42,7 @@ test "Closures" { assert(sum == 15) // Errors - let ecl = fn(v: bool) int !SomeError { + let ecl = fn(v: bool) int !AnError { if v : throw .error return 1 } diff --git a/tests/try-convert.valk b/tests/try-convert.valk index 548ec438..07fcc844 100644 --- a/tests/try-convert.valk +++ b/tests/try-convert.valk @@ -2,7 +2,7 @@ value tc_v1 (8) value tc_v2 (2) -fn tc_fn1() uint !SomeError { +fn tc_fn1() uint !AnError { throw .error } diff --git a/tests/vscope.valk b/tests/vscope.valk index ddd5cbdd..85a0f2c5 100644 --- a/tests/vscope.valk +++ b/tests/vscope.valk @@ -1,11 +1,11 @@ -fn vscope_err() int !SomeError { +fn vscope_err() int !AnError { throw .error } -fn vscope_err2() (int, int) !SomeError { +fn vscope_err2() (int, int) !AnError { throw .error } -fn vscope_err3() (String, String) !SomeError { +fn vscope_err3() (String, String) !AnError { throw .error } fn mvtest() (String, String) { From 753f2ebd3003fea64221d5cf0cb1196a26532a4e Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 00:46:53 +0100 Subject: [PATCH 22/32] docs --- docs/api.md | 209 +++++++++++++++++++++++------------------- docs/docs.md | 37 ++++++-- src/doc/markdown.valk | 8 +- 3 files changed, 147 insertions(+), 107 deletions(-) diff --git a/docs/api.md b/docs/api.md index 4534301e..44167ba2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -20,7 +20,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + fn exec(cmd: String, print_output: bool (false)) (i32, String) + fn exit(code: i32) void -+ fn getenv(var: String) String !not_found ++ fn getenv(var: String) String !core:LookupError + fn panic(msg: String) void + fn race_lock() void + fn race_unlock() void @@ -34,7 +34,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + class Mutex { + fn await_unlock() void + fn lock() void - + static fn new() Mutex !create + + static fn new() Mutex !core:InitError + fn unlock() void } ``` @@ -69,9 +69,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn bcrypt_hash(password: String, cost: uint (12)) String + fn bcrypt_verify(password: String, hash: String) bool + fn blowfish_encrypt_block(context: BlowfishContext, input: ptr[u8], output: ptr[u8]) void -+ fn blowfish_expand_key(context: BlowfishContext, salt: ?ByteBuffer, key: ByteBuffer) void !invalid_salt !invalid_key ++ fn blowfish_expand_key(context: BlowfishContext, salt: ?ByteBuffer, key: ByteBuffer) void !crypto:CryptoError + fn blowfish_init_state(context: BlowfishContext) void + fn blowfish_xor_block(data: &[u8], salt: ByteBuffer, saltIndex: &[uint]) void ++ fn sha1_encode(str: String) String + fn sha256_encode(str: String) String ``` @@ -80,8 +81,8 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class Blake2b { + fn finalize(out: ptr[u8]) void - + static fn hash_str(input: String, key: ?String (null), lowercase: bool (true)) String !invalid_key - + static fn new(hash_size: uint, key: ?String (null)) Blake2b !invalid_hash_size !invalid_key + + static fn hash_str(input: String, key: ?String (null), lowercase: bool (true)) String !crypto:CryptoError + + static fn new(hash_size: uint, key: ?String (null)) Blake2b !crypto:CryptoError + fn update(data: ptr[u8], length: uint) void } ``` @@ -91,10 +92,23 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | } ``` +```js ++ class Sha1 { + + fn add_hash_data(data: &[u8 x 20]) void + + fn add_raw_data_unsafe(data: ptr[u8], len: uint) void + + fn add_string_data(str: String) void + + fn final() [u8 x 20] + + fn reset() void +} +``` + ```js + class Sha256 { - + fn final(hash: ptr[u8 x 32]) void - + fn update(data: ptr[u8], len: uint) void + + fn add_hash_data(data: &[u8 x 32]) void + + fn add_raw_data_unsafe(data: ptr[u8], len: uint) void + + fn add_string_data(str: String) void + + fn final() [u8 x 32] + + fn reset() void } ``` @@ -118,9 +132,9 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn add(dir: String, fn: String) String + fn basename(path: String) String + fn chdir(path: String) void -+ fn copy(from_path: String, to_path: String, recursive: bool (false)) void !fail ++ fn copy(from_path: String, to_path: String, recursive: bool (false)) void !io:IoError + fn cwd() String -+ fn delete(path: String) void !delete ++ fn delete(path: String) void !io:IoError + fn delete_recursive(path: String) void + fn dir_of(path: String) String + fn exe_dir() String @@ -128,26 +142,26 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn exists(path: String) bool + fn ext(path: String, with_dot: bool (false)) String + fn files_in(dir: String, recursive: bool (false), files: bool (true), dirs: bool (true), prefix: ?String (null), result: Array[String] (...)) Array[String] -+ fn home_dir() String !not_found ++ fn home_dir() String !core:ExternError + fn is_dir(path: String) bool + fn is_file(path: String) bool + fn mime(ext_without_dot: String) String -+ fn mkdir(path: String, permissions: u32 (493)) void !fail -+ fn modified_time(path: String) uint !file_not_found -+ fn move(from_path: String, to_path: String) void !fail -+ fn open(path: String, writable: bool, append_on_write: bool) i32 !open -+ fn open_extend(path: String, writable: bool, append_on_write: bool, create_file_if_doesnt_exist: bool (false), create_file_permissions: u32 (420)) i32 !open !access ++ fn mkdir(path: String, permissions: u32 (493)) void !io:IoError ++ fn modified_time(path: String) uint !io:IoError ++ fn move(from_path: String, to_path: String) void !io:IoError ++ fn open(path: String, writable: bool, append_on_write: bool) i32 !io:IoError ++ fn open_extend(path: String, writable: bool, append_on_write: bool, create_file_if_doesnt_exist: bool (false), create_file_permissions: u32 (420)) i32 !io:IoError + fn path(path: String) Path -+ fn read(path: String) String !open !read !close ++ fn read(path: String) String !io:IoError + fn realpath(path: String) String + fn resolve(path: String) String -+ fn rmdir(path: String) void !fail ++ fn rmdir(path: String) void !io:IoError + fn size(path: String) uint -+ fn stream(path: String, read: bool, write: bool, append: bool (false), auto_create: bool (false)) FileStream !err_open -+ fn symlink(link: String, target: String, is_directory: bool) void !permissions !exists !other ++ fn stream(path: String, read: bool, write: bool, append: bool (false), auto_create: bool (false)) FileStream !io:IoError ++ fn symlink(link: String, target: String, is_directory: bool) void !io:IoError + fn sync() void -+ fn write(path: String, content: String, append: bool (false)) void !open !write -+ fn write_from_ptr(path: String, data: ptr, size: uint, append: bool (false)) void !open !write ++ fn write(path: String, content: String, append: bool (false)) void !io:IoError ++ fn write_from_ptr(path: String, data: ptr, size: uint, append: bool (false)) void !io:IoError ``` ## Classes for 'fs' @@ -159,10 +173,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ reading: bool + fn close() void - + fn read(bytes: uint (10240), buffer: ByteBuffer) bool !read_err - + fn write(str: String) void !write_err - + fn write_buffer(buffer: ByteBuffer) void !write_err - + fn write_from_ptr(from: ptr, len: uint) void !write_err + + fn read(bytes: uint (10240), buffer: ByteBuffer) bool !io:IoError + + fn write(str: String) void !io:IoError + + fn write_buffer(buffer: ByteBuffer) void !io:IoError + + fn write_from_ptr(from: ptr, len: uint) void !io:IoError } ``` @@ -172,7 +186,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ size: uint + static fn create_from_buffer(buffer: ByteBuffer) InMemoryFile - + static fn create_from_file(path: String) InMemoryFile !load + + static fn create_from_file(path: String) InMemoryFile !io:IoError + static fn create_from_ptr(data: ptr, size: uint) InMemoryFile + fn save(path: String) void } @@ -240,10 +254,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'http' ```js -+ fn create_request(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !ssl !connect -+ fn download(url: String, to_path: String, method: String (""), options: ?Options (null)) void !invalid_path !invalid_url !ssl !connect !disconnect !invalid_response -+ fn parse_http(input: ByteBuffer, context: Context, is_response: bool) void !invalid !http413 !incomplete !missing_host_header !file_error -+ fn request(method: String, url: String, options: ?Options (null)) ClientResponse !invalid_url !invalid_output_path !ssl !connect !disconnect !invalid_response ++ fn create_request(method: String, url: String, options: ?Options (null)) ClientRequest !http:HttpError ++ fn download(url: String, to_path: String, method: String (""), options: ?Options (null)) void !http:HttpError ++ fn parse_http(input: ByteBuffer, context: Context, is_response: bool) void !http:HttpParseError ++ fn request(method: String, url: String, options: ?Options (null)) ClientResponse !http:HttpError ``` ## Classes for 'http' @@ -260,9 +274,9 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ response_received: bool ~ sent_percent: uint - + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !connection_failed !ssl !invalid_output_path - + fn progress() bool !disconnect !invalid_response - + fn response() ClientResponse !in_progress !invalid_response + + static fn create(method: String, url: String, options: ?Options (null)) ClientRequest !http:HttpError + + fn progress() bool !http:HttpError + + fn response() ClientResponse !http:HttpError } ``` @@ -372,21 +386,21 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class Router[T] { - + fn add(method: String, url: String, handler: T) void !invalid_route - + fn find(method: String, url: String) Route[T] !not_found + + fn add(method: String, url: String, handler: T) void + + fn find(method: String, url: String) Route[T] !core:LookupError + static fn new() Router[T] } ``` ```js + class Server { - + fast_handler: ?fn(Context, ResponseWriter)() + + fast_handler: ?fn(Context, ResponseWriter)()! ~ host: String ~ port: u16 + show_info: bool - + fn add_static_dir(path: String) void !notfound - + static fn new(host: String, port: u16, handler: fn(Request)(Response)) Server !socket_init_error !socket_bind_error + + fn add_static_dir(path: String) void !core:LookupError + + static fn new(host: String, port: u16, handler: fn(Request)(Response)!) Server !http:HttpError + fn start(worker_count: i32 (-1)) void } ``` @@ -402,15 +416,15 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn print(msg: String) void + fn print_from_ptr(adr: ptr, len: uint) void + fn println(msg: String) void -+ fn read(fd: i32, buf: ByteBuffer, amount: uint, offset: uint) uint !fail -+ fn read_to_ptr(fd: i32, buf: ptr, amount: uint, offset: uint) uint !fail -+ fn read_to_ptr_sync(fd: i32, buf: ptr, amount: uint, offset: uint) uint !fail ++ fn read(fd: i32, buf: ByteBuffer, amount: uint, offset: uint) uint !io:IoError ++ fn read_to_ptr(fd: i32, buf: ptr, amount: uint, offset: uint) uint !io:IoError ++ fn read_to_ptr_sync(fd: i32, buf: ptr, amount: uint, offset: uint) uint !io:IoError + fn set_mode(fd: i32, mode: io:MODE(int)) void + fn set_non_block(fd: i32, value: bool) void -+ fn write(fd: i32, buf: ByteBuffer, amount: uint) uint !fail -+ fn write_from_ptr(fd: i32, buf: ptr, amount: uint) uint !fail -+ fn write_from_ptr_sync(fd: i32, buf: ptr, amount: uint) uint !fail -+ fn write_string(fd: i32, str: String) uint !fail ++ fn write(fd: i32, buf: ByteBuffer, amount: uint) uint !io:IoError ++ fn write_from_ptr(fd: i32, buf: ptr, amount: uint) uint !io:IoError ++ fn write_from_ptr_sync(fd: i32, buf: ptr, amount: uint) uint !io:IoError ++ fn write_string(fd: i32, str: String) uint !io:IoError ``` # json @@ -418,7 +432,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'json' ```js -+ fn decode(json: String) Value !invalid ++ fn decode(json: String) Value !json:ParseError + fn encode(data: $T, pretty: bool (false), output: ?StringComposer (null), depth: uint (0)) StringComposer + fn encode_to_string(data: $T, pretty: bool (false)) String + fn new_array(values: ?Array[Value] (null)) Value @@ -448,12 +462,12 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn alloc(size: uint) ptr + fn alloc_ob(size: uint) ptr + fn ascii_bytes_to_lower(adr: ptr, len: uint) void -+ fn bytes_to_uint(adr: ptr, len: uint) uint !not_a_number ++ fn bytes_to_uint(adr: ptr, len: uint) uint !core:SyntaxError + fn calloc(size: uint) ptr + fn clear(adr: ptr, length: uint) void + fn copy(from: ptr, to: ptr, length: uint) void + fn equal(a: ptr, b: ptr, length: uint) bool -+ fn find_char(adr: ptr, ch: u8, length: uint) uint !not_found ++ fn find_char(adr: ptr, ch: u8, length: uint) uint !core:LookupError + fn free(adr: ptr) void + fn resize(adr: ptr, size: uint, new_size: uint) ptr ``` @@ -463,11 +477,11 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'net' ```js -+ fn recv(fd: i32, buf: ByteBuffer, amount: uint) uint !fail -+ fn recv_to_ptr(fd: i32, buf: ptr, amount: uint) uint !fail -+ fn send(fd: i32, buf: ByteBuffer, amount: uint) uint !fail -+ fn send_from_ptr(fd: i32, buf: ptr, amount: uint) uint !fail -+ fn send_string(fd: i32, str: String) uint !fail ++ fn recv(fd: i32, buf: ByteBuffer, amount: uint) uint !net:NetError ++ fn recv_to_ptr(fd: i32, buf: ptr, amount: uint) uint !net:NetError ++ fn send(fd: i32, buf: ByteBuffer, amount: uint) uint !net:NetError ++ fn send_from_ptr(fd: i32, buf: ptr, amount: uint) uint !net:NetError ++ fn send_string(fd: i32, str: String) uint !net:NetError + fn set_ca_cert_path(path: ?String) void ``` @@ -478,7 +492,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ data: libc_gen_addrinfo + fn addr_len() u32 - + static fn new(host: String, port: u16) AddrInfo !fail + + static fn new(host: String, port: u16) AddrInfo !net:NetError + fn sock_addr() libc_gen_sockaddr } ``` @@ -488,14 +502,16 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ fd: i32 ~ ssl: ?SSL ~ ssl_enabled: bool + ~ ssl_error: String + ~ ssl_error_code: i32 + fn close() void + static fn new(fd: i32) Connection - + fn recv(buffer: ByteBuffer, bytes: uint) uint !connection !closed - + fn send(data: String) void !connection - + fn send_buffer(data: ByteBuffer, skip_bytes: uint, send_all: bool) uint !connection - + fn send_bytes(data: ptr, bytes: uint, send_all: bool) uint !connection !closed - + fn ssl_connect(host: String, ca_cert_path: ?String (null)) void !ssl_error + + fn recv(buffer: ByteBuffer, bytes: uint) uint !net:NetError + + fn send(data: String) void !net:NetError + + fn send_buffer(data: ByteBuffer, skip_bytes: uint, send_all: bool) uint !net:NetError + + fn send_bytes(data: ptr, bytes: uint, send_all: bool) uint !net:NetError + + fn ssl_connect(host: String, ca_cert_path: ?String (null)) void !net:NetError } ``` @@ -515,10 +531,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ host: String ~ port: u16 - + static fn client(type: net:SOCKET_TYPE(int), host: String, port: u16) Connection !invalid_host !create !connect !closed + + static fn client(type: net:SOCKET_TYPE(int), host: String, port: u16) Connection !net:NetError + fn close() void + static fn close_fd(fd: i32) void - + static fn server(type: net:SOCKET_TYPE(int), host: String, port: u16) SocketServer !invalid_host !create !bind !listen !closed + + static fn server(type: net:SOCKET_TYPE(int), host: String, port: u16) SocketServer !net:NetError } ``` @@ -526,7 +542,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + class SocketServer { ~ socket: Socket - + fn accept() Connection !too_many_connections !error !closed + + fn accept() Connection !net:NetError + fn close() void } ``` @@ -536,7 +552,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'template' ```js -+ fn render(name: String, data: $T, options: ?RenderOptions (null)) String !FileNotFound ++ fn render(name: String, data: $T, options: ?RenderOptions (null)) String !template:Error + fn render_content(content: String, data: $T, options: ?RenderOptions (null)) String + fn set_content(name: String, content: String) void + fn set_content_many(content: Map[String]) void @@ -546,7 +562,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class RenderOptions { - + sanitize: ?fn(String)(String) + + sanitize: ?fn(String)(String)! } ``` @@ -555,10 +571,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'thread' ```js -+ fn start(func: fn()()) Thread !start ++ fn start(func: fn()()!) Thread !core:InitError + fn suspend_ms(ms: uint) void + fn suspend_ns(ns: uint) void -+ fn task(handler: fn()()) Task !error ++ fn task(handler: fn()()!) Task !core:InitError ``` ## Classes for 'thread' @@ -577,16 +593,16 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ finished: bool ~ started: bool - + static fn start(func: fn()()) Thread !start + + static fn start(func: fn()()!) Thread !core:InitError + fn wait() void } ``` ```js + class ThreadSuspendGate { - + static fn new() ThreadSuspendGate !error + + static fn new() ThreadSuspendGate + fn signal() void - + fn wait(should_wait: fn()(bool), timeout_ms: uint (0)) bool + + fn wait(should_wait: fn()(bool)!, timeout_ms: uint (0)) bool } ``` @@ -620,30 +636,30 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn copy() Array[T] + fn equal(array: Array[T]) bool + fn equal_ignore_order(array: Array[T]) bool - + fn filter(func: ?fn(T)(bool) (null)) Array[T] + + fn filter(func: ?fn(T)(bool)! (null)) Array[T] + fn fit_index(index: uint) void - + fn get(index: uint) T !not_found + + fn get(index: uint) T !core:LookupError + fn increase_size(new_size: uint) GcPtr - + fn index_of(item: T) uint !not_found + + fn index_of(item: T) uint !core:LookupError + fn intersect(with: Array[T]) Array[T] + fn iter() Slice[T] + fn lock() void + fn merge(items: Array[T]) Array[T] + static fn new(start_size: uint (2)) Array[T] + fn part(start: uint, amount: uint) Array[T] - + fn pop_first() T !empty - + fn pop_last() T !empty + + fn pop_first() T !core:LookupError + + fn pop_last() T !core:LookupError + fn prepend(item: T, unique: bool (false)) Array[T] + fn prepend_many(items: Array[T]) Array[T] + fn range(start: uint, end: uint, inclusive: bool (true)) Array[T] + fn remove(index: uint) Array[T] + fn remove_value(value: T) Array[T] + fn reverse() Array[T] - + fn set(index: uint, value: T) void !out_of_range + + fn set(index: uint, value: T) void !core:LookupError + fn set_all(value: T) void + fn set_expand(index: uint, value: T, filler_value: T) void + fn slice(start: uint, amount: uint) Slice[T] - + fn sort(func: ?fn(T, T)(bool) (null)) Array[T] + + fn sort(func: ?fn(T, T)(bool)! (null)) Array[T] + fn swap(index_a: uint, index_b: uint) void + fn to_json_value() Value + fn unique() Array[T] @@ -659,6 +675,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn append(buffer: ByteBuffer, start_index: uint (0)) void + fn append_byte(byte: u8) void + + fn append_f64(value: f64, decimals: uint (2), trim_zeros: bool (false)) ByteBuffer + fn append_from_ptr(data: ptr, length: uint) void + fn append_int(value: int) void + fn append_str(str: String) void @@ -669,20 +686,20 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn clone() ByteBuffer + fn equals_str(str: String) bool + fn get(index: uint) u8 - + fn index_of(byte: u8, start_index: uint (0)) uint !not_found - + fn index_where_byte_is_not(byte: u8, start_index: uint (0)) uint !not_found - + fn ltrim(filter: fnptr(u8)(bool)) void + + fn index_of(byte: u8, start_index: uint (0)) uint !core:LookupError + + fn index_where_byte_is_not(byte: u8, start_index: uint (0)) uint !core:LookupError + + fn ltrim(filter: fnptr(u8)(bool)!) void + fn minimum_free_space(length: uint) void + fn minimum_size(minimum_size: uint) void + static fn new(start_size: uint (128)) ByteBuffer + fn part(start_index: uint, length: uint) String + fn reduce_size(size: uint) void - + fn rtrim(filter: fnptr(u8)(bool)) void + + fn rtrim(filter: fnptr(u8)(bool)!) void + fn set(index: uint, v: u8) void + fn starts_with(str: String, offset: uint) bool + fn str_ref(offset: uint, length: uint) ByteBufferStrRef + fn to_string() String - + fn trim(filter: fnptr(u8)(bool)) void + + fn trim(filter: fnptr(u8)(bool)!) void } ``` @@ -690,7 +707,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + class FlatMap[K, T] { + fn clear() FlatMap[K, T] + fn copy() FlatMap[K, T] - + fn get(key: K) T !not_found + + fn get(key: K) T !core:LookupError + fn has(key: K) bool + fn has_value(value: T) bool + fn keys() Array[K] @@ -700,7 +717,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn remove(key: K) FlatMap[K, T] + fn set(key: K, value: T) FlatMap[K, T] + fn set_many(map: FlatMap[K, T]) FlatMap[K, T] - + fn set_unique(key: K, value: T) void !not_unique + + fn set_unique(key: K, value: T) void !core:LookupError + fn sort_keys() FlatMap[K, T] + fn values() Array[T] } @@ -710,7 +727,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + class HashMap[K, T] { + fn clear() HashMap[K, T] + fn copy() HashMap[K, T] - + fn get(key: K) T !not_found + + fn get(key: K) T !core:LookupError + fn has(key: K) bool + fn has_value(value: T) bool + fn keys() Array[K] @@ -738,7 +755,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ count: uint + fn add(item: T) void - + fn get() T !empty + + fn get() T !core:LookupError + static fn new(start_size: uint (2)) Pool[T] } ``` @@ -755,10 +772,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn escape() String + static fn from_json_value(val: Value) String + fn get(index: uint) u8 - + fn hex_to_int() int !invalid - + fn hex_to_uint() uint !invalid - + fn index_of(part: String, start_index: uint (0)) uint !not_found - + fn index_of_byte(byte: u8, start_index: uint (0)) uint !not_found + + fn hex_to_int() int !core:SyntaxError + + fn hex_to_uint() uint !core:SyntaxError + + fn index_of(part: String, start_index: uint (0)) uint !core:LookupError + + fn index_of_byte(byte: u8, start_index: uint (0)) uint !core:LookupError + fn is_alpha(allow_extra_bytes: String ("")) bool + fn is_alpha_numeric(allow_extra_bytes: String ("")) bool + fn is_empty() bool @@ -769,8 +786,8 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn ltrim(part: String, limit: uint (0)) String + static fn make_empty(length: uint) String + static fn make_from_ptr(data: ptr, length: uint) String - + fn octal_to_int() int !invalid - + fn octal_to_uint() uint !invalid + + fn octal_to_int() int !core:SyntaxError + + fn octal_to_uint() uint !core:SyntaxError + fn pad_left(char: u8, length: uint) String + fn pad_right(char: u8, length: uint) String + fn part(start_index: uint, length: uint) String @@ -779,10 +796,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn rtrim(part: String, limit: uint (0)) String + fn split(on: String) Array[String] + fn starts_with(part: String) bool - + fn to_float() f64 !invalid - + fn to_int() int !invalid + + fn to_float() f64 !core:SyntaxError + + fn to_int() int !core:SyntaxError + fn to_json_value() Value - + fn to_uint() uint !invalid + + fn to_uint() uint !core:SyntaxError + fn trim(part: String, limit: uint (0)) String + fn unescape() String + fn upper() String @@ -813,7 +830,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class cstring { + fn get(index: uint) u8 - + fn index_of(find: u8) uint !notfound + + fn index_of(find: u8) uint !core:LookupError + fn length() uint + fn to_string() String } @@ -937,7 +954,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class ptr { - + fn index_of_byte(byte: u8, memory_size: uint) uint !not_found + + fn index_of_byte(byte: u8, memory_size: uint) uint !core:LookupError + fn print_bytes(length: uint, end_with_newline: bool (true)) void + fn to_hex() String } diff --git a/docs/docs.md b/docs/docs.md index de1b6208..a61adf30 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -18,6 +18,7 @@ * [Objects](#objects) * [Typehints](#typehints) * [Functions](#functions) + * [Errors](#errors) * [Error Handling](#error-handling) * [Closures](#closures) @@ -35,7 +36,7 @@ * [If/Else](#if-else) * [While](#while) * [Each](#each) - * [Throw](#error-handling) + * [Throw](#errors)

@@ -337,17 +338,41 @@ fn main() { } ``` -### Error handling +### Errors + +Functions can return errors using `throw`. But first you need to define an error type or you can use one of the built-in ones. + +To define an error type you must provide atleast 1 error code or atleast extend 1 existing error type. Optionally you can define payload fields in case you want to pass error related data. + +```rust +error {Error type name} ({codes}) [extends ({error types})] [payload { {field-name}: {type} }] +// Example +error MyError (invalid_input, missing_key) extends (LookupError) payload { message: String, key: Key } +``` -Functions can return errors using `throw`. Errors must be defined in your function declaration first. +Usage: ```rust -fn my_func(must_fail: bool) String !fail { - if must_fail : throw fail - return "hi" +fn find_value(key: Key) Value !MyError { + //... + throw .missing_key { message: "Key not found", key: Key } + //... } ``` +Built-in error types: + +```rust +AnError (error) // Generic error +ExternError (extern) // For when an extern function returns an error +IterError (end) // You can end an 'each' loop with any error or you can use this one +InitError (init) // Common error +LookupError (missing, exists, range, empty) // For when a function needs to find or store something +SyntaxError (syntax) // Common error +``` + +### Error handling + When calling this function the error must always be handled. There are many ways to do this: ```rust diff --git a/src/doc/markdown.valk b/src/doc/markdown.valk index bab3cfb6..f16edbb4 100644 --- a/src/doc/markdown.valk +++ b/src/doc/markdown.valk @@ -140,11 +140,9 @@ fn md_func(ns_name: String, name: String, func: json:Value) String { str += ") " str += func.get("return-type").string() - let errors = func.get("errors").array() - if errors.length > 0 { - each errors as err { - str += " !" + err.string() - } + let etype = func.get("error_type") + if etype.is_string() { + str += " !" + etype.string() } str += "\n" From 642161d0b416f18761a20de016b4fb088d1f3e97 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 00:50:37 +0100 Subject: [PATCH 23/32] fix type to string --- docs/api.md | 24 ++++++++++++------------ src/build/type.valk | 3 +-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/api.md b/docs/api.md index 44167ba2..1914e898 100644 --- a/docs/api.md +++ b/docs/api.md @@ -394,13 +394,13 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class Server { - + fast_handler: ?fn(Context, ResponseWriter)()! + + fast_handler: ?fn(Context, ResponseWriter)() ~ host: String ~ port: u16 + show_info: bool + fn add_static_dir(path: String) void !core:LookupError - + static fn new(host: String, port: u16, handler: fn(Request)(Response)!) Server !http:HttpError + + static fn new(host: String, port: u16, handler: fn(Request)(Response)) Server !http:HttpError + fn start(worker_count: i32 (-1)) void } ``` @@ -562,7 +562,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ```js + class RenderOptions { - + sanitize: ?fn(String)(String)! + + sanitize: ?fn(String)(String) } ``` @@ -571,10 +571,10 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ## Functions for 'thread' ```js -+ fn start(func: fn()()!) Thread !core:InitError ++ fn start(func: fn()()) Thread !core:InitError + fn suspend_ms(ms: uint) void + fn suspend_ns(ns: uint) void -+ fn task(handler: fn()()!) Task !core:InitError ++ fn task(handler: fn()()) Task !core:InitError ``` ## Classes for 'thread' @@ -593,7 +593,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | ~ finished: bool ~ started: bool - + static fn start(func: fn()()!) Thread !core:InitError + + static fn start(func: fn()()) Thread !core:InitError + fn wait() void } ``` @@ -602,7 +602,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + class ThreadSuspendGate { + static fn new() ThreadSuspendGate + fn signal() void - + fn wait(should_wait: fn()(bool)!, timeout_ms: uint (0)) bool + + fn wait(should_wait: fn()(bool), timeout_ms: uint (0)) bool } ``` @@ -636,7 +636,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn copy() Array[T] + fn equal(array: Array[T]) bool + fn equal_ignore_order(array: Array[T]) bool - + fn filter(func: ?fn(T)(bool)! (null)) Array[T] + + fn filter(func: ?fn(T)(bool) (null)) Array[T] + fn fit_index(index: uint) void + fn get(index: uint) T !core:LookupError + fn increase_size(new_size: uint) GcPtr @@ -659,7 +659,7 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn set_all(value: T) void + fn set_expand(index: uint, value: T, filler_value: T) void + fn slice(start: uint, amount: uint) Slice[T] - + fn sort(func: ?fn(T, T)(bool)! (null)) Array[T] + + fn sort(func: ?fn(T, T)(bool) (null)) Array[T] + fn swap(index_a: uint, index_b: uint) void + fn to_json_value() Value + fn unique() Array[T] @@ -688,18 +688,18 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) | + fn get(index: uint) u8 + fn index_of(byte: u8, start_index: uint (0)) uint !core:LookupError + fn index_where_byte_is_not(byte: u8, start_index: uint (0)) uint !core:LookupError - + fn ltrim(filter: fnptr(u8)(bool)!) void + + fn ltrim(filter: fnptr(u8)(bool)) void + fn minimum_free_space(length: uint) void + fn minimum_size(minimum_size: uint) void + static fn new(start_size: uint (128)) ByteBuffer + fn part(start_index: uint, length: uint) String + fn reduce_size(size: uint) void - + fn rtrim(filter: fnptr(u8)(bool)!) void + + fn rtrim(filter: fnptr(u8)(bool)) void + fn set(index: uint, v: u8) void + fn starts_with(str: String, offset: uint) bool + fn str_ref(offset: uint, length: uint) ByteBufferStrRef + fn to_string() String - + fn trim(filter: fnptr(u8)(bool)!) void + + fn trim(filter: fnptr(u8)(bool)) void } ``` diff --git a/src/build/type.valk b/src/build/type.valk index 1db5f7aa..ee0d2b25 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -257,9 +257,8 @@ class Type { } str += ")" if isset(info) { - str += "!" let etype = info.error_type - if isset(etype) : str += etype.display_name + if isset(etype) : str += "!" + etype.display_name } return result + str } From 83058f15b83b6d0982ea0db0f917ccf4329dea6f Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 00:58:30 +0100 Subject: [PATCH 24/32] docs --- docs/docs.md | 48 ++++++++---------------------------------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index a61adf30..61c60d71 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -392,53 +392,21 @@ my_func() _ let v = my_func() !! ``` -Custom error message & checking which error was thrown: - -Note: Inside the error handler you can access the error message via `EMSG` and the error code via `E` +You can access all error information with the `E` identifier. `E.code` contains the error code that was thrown. ```rust -fn my_func() String !fail !nope { - throw fail, "We failed" -} - fn main() { my_func() ! { - println(EMSG) // Prints: We failed - // Checking the error code using `match` - match E { + // Checking the error code using `if` + if E.code == E.fail : println("Error code `fail` was thrown") + // Using 'match' + match E.code { E.fail => println("Error code `fail` was thrown") E.nope => println("Error code `nope` was thrown") - default => println("Another error was thrown") - } - // Checking the error code using `if` - if E == E.fail : println("Error code `fail` was thrown") - } -} -``` - -Error traces: When you using `!>` to pass errors to the parent, you can ask for a trace of these passes. This trace resets every time `throw` is called. - -```rust -use valk:core - -fn f1() !fail { - f2() !> -} -fn f2() !fail { - throw fail -} -fn main() { - f1() ! { - let trace = core:get_error_trace() - each trace as str { - println(str) + default => println("Another error was thrown") // Only required if not all codes were checked (compiler will tell) } - // OR - core:print_error_trace() - // ------------- ERROR TRACE ------------- - // .../src/example.valk:4 - // .../src/example.valk:7 - // -------------- END TRACE -------------- + // Payload data + println(E.message) } } ``` From dc4eca19b8030681764fec34df501aed18e76e5c Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 01:25:14 +0100 Subject: [PATCH 25/32] error code values --- src/build/ast.valk | 17 +++++++++++------ src/build/error-type.valk | 15 +++++++++++++++ src/build/type.valk | 6 ++++++ src/build/value.valk | 16 ++++++++++++++-- tests/error-handlers.valk | 8 ++++++-- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/build/ast.valk b/src/build/ast.valk index 5fcd1937..430874a4 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -702,10 +702,15 @@ fn ast_throw(p: Parser, scope: Scope) { let etype = func.error_type if !isset(etype) : p.error("This function has no error type") - p.expect(".", true, false) - let err = p.read_word(false, false) - if !etype.codes.has_value(err) : p.error("No error code named '" + err + "' in error type: " + etype.display_name) - let hash = helper:ctxhash_u32(err) + // Read error value + let suggest = p.suggest_type; + p.suggest_type = etype.get_type(null) + let errv = read_value(p, scope) + p.suggest_type = suggest + // Validate + let vetype = errv.rett.error_type + if !isset(vetype) : p.error("This not an error. Value type: " + errv.rett + " | Expected: " + etype + " (or a compatible error type)") + if !etype.is_compat(vetype) : p.error("Error types are not compatible: %etype <> %vetype") // Payload let payload : ?Map[Value] = null @@ -714,7 +719,7 @@ fn ast_throw(p: Parser, scope: Scope) { while !p.next_word_is("}", true, true, true) { // Property let name = p.read_word(true, true) - let type = etype.payload.get(name) ! p.error("There is no payload property named '%name' in error type: " + etype.display_name) + let type = vetype.payload.get(name) ! p.error("There is no payload property named '%name' in error type: " + vetype.display_name) if payload.has(name) : p.error("Duplicate payload '%name'") p.expect(":", true, false) // Value @@ -743,7 +748,7 @@ fn ast_throw(p: Parser, scope: Scope) { } } - ast_gen_throw(p.ctx, scope, etype, vgen_int(hash, etype.get_type(null)), payload, p.chunk.path_and_line()) + ast_gen_throw(p.ctx, scope, etype, errv, payload, p.chunk.path_and_line()) } fn ast_cothrow(p: Parser, scope: Scope) { diff --git a/src/build/error-type.valk b/src/build/error-type.valk index f89b82b5..fe1057e4 100644 --- a/src/build/error-type.valk +++ b/src/build/error-type.valk @@ -112,6 +112,21 @@ class ErrorType { } return true } + + fn parse_value(p: Parser, scope: Scope) Value { + if p.lsp { + let list = Array[String]{} + each this.codes as name, code { + list.append(name) + } + p.build.getlsp().check_completion_simple(list) + } + + let name = p.read_word(false, false) + if !this.codes.has_value(name) : p.error("Error type '%this' does not have an error code named: '%name'") + let errv = helper:ctxhash_u32(name) + return vgen_int(errv, this.get_type(name)) + } } fn get_payload_global(b: Build, type: Type, exclude: Array[String]) Global { diff --git a/src/build/type.valk b/src/build/type.valk index ee0d2b25..d8bfd592 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -825,6 +825,12 @@ fn handle_type_idf(p: Parser, scope: Scope, idf: Idf) Type { return enu.get_type() } + if for == IDF.error_type { + let etype = idf.error_type + if !isset(etype) : p.bug("Missing error type info for identifier") + return etype.get_type(null) + } + p.error("Not a type") } diff --git a/src/build/value.valk b/src/build/value.valk index e0965174..ded0f495 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -926,8 +926,13 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals let s = p.suggest_type if !isset(s) : p.error("Using '.' requires a typehint to be present") - let class = s.get_class() ! p.error("The '.' token cannot be used for type: " + s) - v = handle_class(p, class, scope, true) + let etype = s.error_type + if isset(etype) { + v = etype.parse_value(p, scope) + } else { + let class = s.get_class() ! p.error("The '.' token cannot be used for type: " + s) + v = handle_class(p, class, scope, true) + } } else if p.sign_is("[") { let type = read_fixed_array_type(p, scope) @@ -1424,6 +1429,13 @@ fn handle_idf(p: Parser, scope: Scope, idf: Idf, imut_mode: bool) Value { return propv } } + if for == IDF.error_type { + let etype = idf.error_type + if !isset(etype) : p.bug("Missing error type information for identifier") + p.expect(".", false, false) + // + return etype.parse_value(p, scope) + } if for == IDF.error_value { let ev = idf.error_value if !isset(ev) : p.bug("Missing error value information for identifier") diff --git a/tests/error-handlers.valk b/tests/error-handlers.valk index 46323bce..6a4886bd 100644 --- a/tests/error-handlers.valk +++ b/tests/error-handlers.valk @@ -5,14 +5,18 @@ error TestError (err, err2) payload { message: String } +fn ret_err() TestError { + return .err +} + fn test_errh_success() uint !TestError { return 5 } fn test_errh_error() uint !TestError { - throw .err + throw ret_err() } fn test_errh_error2() uint !TestError { - throw .err2 + throw TestError.err2 } fn test_errh_error3() !TestError { throw .err { message: "Hello" } From 8203bb0de36345672151912b1fe3097fa1b32181 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Mon, 16 Mar 2026 02:08:53 +0100 Subject: [PATCH 26/32] clean up --- src/build/func.valk | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/build/func.valk b/src/build/func.valk index b992a872..5ab6c5c1 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -13,7 +13,6 @@ type ERR_TYPE (u32) class FuncInfo { args: Array[Type] (Array[Type].new()) rett_types: Array[Type] (Array[Type].new()) - // errors: Map[ERR_TYPE] (Map[ERR_TYPE].new()) error_type: ?ErrorType infinite_args: bool (false) can_error: bool (false) @@ -26,7 +25,6 @@ class FuncInfo { return FuncInfo { args: this.args.copy() rett_types: this.rett_types.copy() - // errors: this.errors.copy() error_type: this.error_type infinite_args: this.infinite_args can_error: isset(this.error_type) @@ -127,7 +125,6 @@ class Func { args: Array[Arg] (Array[Arg]{}) rett: Type rett_real: Type - // errors: Map[u32] (Map[u32]{}) error_type: ?ErrorType arg_scope: Scope @@ -188,7 +185,6 @@ class Func { return FuncInfo { args: this.arg_types() rett_types: this.rett.unroll() - // errors: this.errors error_type: this.error_type infinite_args: this.infinite_args can_error: this.can_error From b6f085b84d90b6e9046faf5f887488ac2d00672f Mon Sep 17 00:00:00 2001 From: ctxcode Date: Tue, 17 Mar 2026 02:14:16 +0100 Subject: [PATCH 27/32] update exit functions --- lib/src/core/clone.valk | 33 ++++++++++++++------------- lib/src/core/{core.valk => exit.valk} | 4 ++-- lib/src/http/worker.valk | 3 ++- lib/src/json/decode-gen.valk | 4 ++-- lib/src/json/encode.valk | 3 ++- lib/src/json/value-gen.valk | 28 ++++++----------------- lib/src/type/ByteBuffer.valk | 2 +- src/build/ast.valk | 23 ++++++++++++------- src/build/func.valk | 6 ++++- src/build/generics.valk | 4 +++- src/build/stage-1-fc.valk | 10 ++------ src/build/stage-2-2-props.valk | 13 +++++------ src/build/stage-2-4-types.valk | 6 ++++- src/build/value.valk | 2 +- tests/funcs.valk | 3 ++- 15 files changed, 72 insertions(+), 72 deletions(-) rename lib/src/core/{core.valk => exit.valk} (77%) diff --git a/lib/src/core/clone.valk b/lib/src/core/clone.valk index cbeccf0a..f949eb96 100644 --- a/lib/src/core/clone.valk +++ b/lib/src/core/clone.valk @@ -6,22 +6,23 @@ fn clone_value[T](value: T) T { if value == null : return null #end #if !is_structural_type(T) - let result = value + return value #else - #if type_has_method(T, clone) - return value.clone() + #if type_has_method(T, clone) + return value.clone() + #else + // Structure types + let result = T { + // #if type_has_vtable(T) + // _VTABLE: @vtable(T) + // #end + #loop object value as PROP + // #if !property_name_is(PROP, _VTABLE) + PROP: clone_value[PROP](PROP.value) + // #end + #end + } + return result + #end #end - // Structure types - let result = T { - // #if type_has_vtable(T) - // _VTABLE: @vtable(T) - // #end - #loop object value as PROP - // #if !property_name_is(PROP, _VTABLE) - PROP: clone_value[PROP](PROP.value) - // #end - #end - } - #end - return result } diff --git a/lib/src/core/core.valk b/lib/src/core/exit.valk similarity index 77% rename from lib/src/core/core.valk rename to lib/src/core/exit.valk index 2e76916a..64d47f78 100644 --- a/lib/src/core/core.valk +++ b/lib/src/core/exit.valk @@ -5,7 +5,7 @@ use ext global cli_args : Array[String] (@undefined) -+ fn exit(code: i32) $exit { ++ fn exit(code: i32) @willexit { ext:exit(code.@cast(i32)) } @@ -14,6 +14,6 @@ global cli_args : Array[String] (@undefined) exit(1) } -+ fn raise(code: i32) $exit { ++ fn raise(code: i32) @willexit { ext:raise(code.@cast(i32)) } diff --git a/lib/src/http/worker.valk b/lib/src/http/worker.valk index 64553473..dc6ce374 100644 --- a/lib/src/http/worker.valk +++ b/lib/src/http/worker.valk @@ -18,9 +18,10 @@ fn worker(server: Server) { } #if TEST panic("HTTP Server failed to accept a connection") - #end + #else println("[x] Failed to accept") continue + #end } let fd = netcon.fd diff --git a/lib/src/json/decode-gen.valk b/lib/src/json/decode-gen.valk index a92a825c..ea01ca9a 100644 --- a/lib/src/json/decode-gen.valk +++ b/lib/src/json/decode-gen.valk @@ -22,8 +22,7 @@ use type #elif !is_structural_type(T) #print_type(T) #error "Cannot convert json to this value type" - #end - + #else // Structural let result = T { #loop properties T as prop @@ -31,4 +30,5 @@ use type #end // End loop } return result + #end } diff --git a/lib/src/json/encode.valk b/lib/src/json/encode.valk index ca417cb4..5f86cc2b 100644 --- a/lib/src/json/encode.valk +++ b/lib/src/json/encode.valk @@ -40,7 +40,7 @@ use type return str.append_str(data ? "true" : "false") #elif !is_structural_type(T) return str.append_str("null") - #end + #else #if is_structural_type(T) && is_pointer_type(T) if check_recursion.contains(data) : return str.append_str("(recursion)") @@ -70,6 +70,7 @@ use type #end return str + #end } fn indent_newline(str: type:StringComposer, depth: uint) { diff --git a/lib/src/json/value-gen.valk b/lib/src/json/value-gen.valk index 74b0b71f..8a6852bb 100644 --- a/lib/src/json/value-gen.valk +++ b/lib/src/json/value-gen.valk @@ -9,15 +9,8 @@ global check_recursion : Array[ptr] (.{}) if !isset(data) : return new_null() #end - #if is_structural_type(T) - #if is_pointer_type(T) - if check_recursion.contains(data) : return new_string("(recursion)") - check_recursion.append(data) - #end - #end - #if type_has_method(T, to_json_value) - let v = data.to_json_value() + return data.to_json_value() #elif is_integer_type(T) return new_int(data.to(type:int)) #elif is_float_type(T) @@ -26,28 +19,21 @@ global check_recursion : Array[ptr] (.{}) return new_bool(data) #elif !is_structural_type(T) return new_null() - // #print_type(T) // #error "Cannot convert value to json" - // #if is_pointer_type(T) - // return new_string(data.@cast(ptr)) - // #else - // return data - // #end #else // Structural let i = Map[Value]{} + if check_recursion.contains(data) : return new_string("(recursion)") + check_recursion.append(data) + #loop object data as prop i.set(prop.name, value(prop.value)) #end // End loop let v = new_object(i) - #end - #if is_structural_type(T) - #if is_pointer_type(T) - check_recursion.pop_last() _ - #end - #end + check_recursion.pop_last() _ - return v + return v + #end } diff --git a/lib/src/type/ByteBuffer.valk b/lib/src/type/ByteBuffer.valk index e633d0bc..779f9db4 100644 --- a/lib/src/type/ByteBuffer.valk +++ b/lib/src/type/ByteBuffer.valk @@ -201,7 +201,7 @@ use gc break } if i > 0 : this.clear_until(i) - return this + return } // + fn minimum_free_space(length: uint) { diff --git a/src/build/ast.valk b/src/build/ast.valk index 430874a4..14ad984a 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -15,9 +15,21 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { fast.free_buffers(func_buffer_count) if scope.did_return { - if !single : p.skip_body("}") + if !single { + while true { + let t = p.tok(true, true) + if p.word_is("#") && p.on_newline { + parse_compile_macro(p, scope) + continue + } + if p.word_is("}") { + break + } + p.error("Unexpected code after return: '%{ p.word() }'") + } + } if scope.did_exit { - // Insert return statement after calling exit-function + // Insert unreachable after calling exit-function let func = p.func() scope.ast.append(Token { type: AST.unreachable }) } @@ -39,7 +51,7 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { } if p.word_is("#") && p.on_newline { parse_compile_macro(p, scope) - continue; + continue } if p.word_is("{") { let sub = scope.sub_scope(SCOPE.default) @@ -92,11 +104,6 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { } } if t == TOK.at_word { - if p.word_is("@return_empty") { - scope.did_return = true - scope.did_exit = true - continue - } if p.word_is("@allocas") { let func = p.func() if !func.is_entrance : p.error("@allocas can only be used inside a function thats flagged with $entrance") diff --git a/src/build/func.valk b/src/build/func.valk index 5ab6c5c1..0c50fa70 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -156,7 +156,11 @@ class Func { disable_gc_stack: bool (false) // Bools - is_exit: bool (false) + must_exit: bool (false) + exited: bool (false) + will_exit: bool (false) + must_throw: bool (false) + // is_static: bool (false) is_extern: bool (false) infinite_args: bool (false) diff --git a/src/build/generics.valk b/src/build/generics.valk index 4360e1e2..bc3f6fe0 100644 --- a/src/build/generics.valk +++ b/src/build/generics.valk @@ -132,7 +132,9 @@ fn get_func_generic(func: Func, types: Array[Type], generic_arg_names: ?Array[St let gfunc = u.new_func(func.fc, func.parse_fc, func.act, func.export_name + "__" + hash, null, func.chunk_args) gfunc.def_chunk = func.def_chunk gfunc.display_name = func.display_name + display - gfunc.is_exit = func.is_exit + gfunc.must_exit = func.must_exit + gfunc.will_exit = func.will_exit + gfunc.must_throw = func.must_throw gfunc.is_static = func.is_static gfunc.chunk_args = func.chunk_args gfunc.chunk_body = func.chunk_body diff --git a/src/build/stage-1-fc.valk b/src/build/stage-1-fc.valk index b5a4e95f..ad420c0b 100644 --- a/src/build/stage-1-fc.valk +++ b/src/build/stage-1-fc.valk @@ -68,10 +68,6 @@ fn stage_fc(fc: Fc) { } // With act - if p.word_is("fn") { - parse_func(p, fc, act) - continue - } if p.word_is("extern") { p.tok(true, false) if p.word_is("fn") { @@ -83,10 +79,8 @@ fn stage_fc(fc: Fc) { } else : p.error("Invalid extern token '%{p.word()}'. Expected: 'fn', 'global' or 'shared'") continue } - if p.word_is("exit") { - p.expect("fn", true, false) - let func = parse_func(p, fc, act) - func.is_exit = true + if p.word_is("fn") { + parse_func(p, fc, act) continue } if p.word_is("class") { diff --git a/src/build/stage-2-2-props.valk b/src/build/stage-2-2-props.valk index f2d395d9..b2734376 100644 --- a/src/build/stage-2-2-props.valk +++ b/src/build/stage-2-2-props.valk @@ -227,29 +227,28 @@ fn class_parse_props_chunk(p: Parser, scope: Scope, class: Class, is_extend: boo let is_static = false let is_mut = false - if w1 == "static" { + if w1 == "static" && (name == "mut" || name == "fn" || name == "get") { is_static = true w1 = name name = p.word() def_chunk = p.clone_chunk_before_token() t = p.tok(true, false) } - if w1 == "mut" && name == "fn" { + if w1 == "mut" && (name == "fn") { is_mut = true w1 = name name = p.word() def_chunk = p.clone_chunk_before_token() t = p.tok(true, false) - } - - let sign = p.word() - - if w1 == "get" { + } else if w1 == "get" { + let sign = p.word() if sign != ":" : p.error("Expected ':' after the 'get' name") class_parse_func(p, scope, class, group, act, is_static, is_mut, name, sign, def_chunk) continue } + if w1 == "fn" { + let sign = p.word() if sign != "[" && sign != "(" : p.error("Expected '[' or '(' after the function name") class_parse_func(p, scope, class, group, act, is_static, is_mut, name, sign, def_chunk) continue diff --git a/src/build/stage-2-4-types.valk b/src/build/stage-2-4-types.valk index 134916b3..430583f9 100644 --- a/src/build/stage-2-4-types.valk +++ b/src/build/stage-2-4-types.valk @@ -166,7 +166,11 @@ fn parse_func_args(func: Func) { if p.word_is("$entrance") { func.is_entrance = true } else if p.word_is("$exit") { - func.is_exit = true + func.must_exit = true + } else if p.word_is("@willexit") { + func.will_exit = true + } else if p.word_is("$throw") { + func.must_throw = true } else if p.word_is("$hot") { func.is_hot = true } else if p.word_is("$inline") { diff --git a/src/build/value.valk b/src/build/value.valk index ded0f495..6340c659 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -1538,7 +1538,7 @@ fn value_func_call(p: Parser, scope: Scope, on: Value, read_co: bool) Value { // Exit func check if on.type == VAL.func_ptr { let func = on.func.@cast(Func) - if func.is_exit { + if func.must_exit || func.will_exit { scope.did_return = true scope.did_exit = true } diff --git a/tests/funcs.valk b/tests/funcs.valk index 5b22bc6c..6f99febe 100644 --- a/tests/funcs.valk +++ b/tests/funcs.valk @@ -41,8 +41,9 @@ test "Func arg default value _" { fn func_garg(v1: $V1, v2: $V2) uint { #if is_nullable_type(V2) return v1.length - #end + #else return v1.length + v2.length + #end } test "Func generic arguments" { From db47caf9d4af46f873275bb52a14b2963db7b8ae Mon Sep 17 00:00:00 2001 From: ctxcode Date: Tue, 17 Mar 2026 02:19:05 +0100 Subject: [PATCH 28/32] fixes --- lib/src/fs/path.valk | 15 +++++++++------ lib/src/io/io.valk | 2 ++ src/build/ast.valk | 5 ++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/src/fs/path.valk b/lib/src/fs/path.valk index 7091fd32..c34823ac 100644 --- a/lib/src/fs/path.valk +++ b/lib/src/fs/path.valk @@ -97,7 +97,9 @@ use core #if OS == win let len = ext:GetModuleFileNameA(null, buf, PATH_MAX) let str = String.make_from_ptr(buf, len) - + str = resolve(str) + EXE_PATH = str + return str #elif OS == macos let size : u32 = PATH_MAX; @@ -112,17 +114,16 @@ use core let link = "/proc/self/exe".data_cstring let len = ext:readlink(link, buf, PATH_MAX) let str = String.make_from_ptr(buf, len.to(u64)) - #end - str = resolve(str) EXE_PATH = str return str + #end } + fn realpath(path: String) String { let buf : ptr[u8 x 4096] = @stack([u8 x 4096]) #if OS == win - return ext:_fullpath(buf, path.data, PATH_MAX); + return ext:_fullpath(buf, path.data, PATH_MAX) #elif OS == macos path = path.replace(PATH_DIV_REPLACE, PATH_DIV) if path.starts_with(".") { @@ -131,13 +132,14 @@ use core if exists(path) { return ext:realpath(path.data, buf) } + return path #else path = path.replace(PATH_DIV_REPLACE, PATH_DIV) if exists(path) { return ext:realpath(path.data, buf) } - #end return path + #end } + fn ext(path: String, with_dot: bool (false)) String { @@ -207,6 +209,7 @@ use core + fn home_dir() String !ExternError { #if OS == win return core:getenv("USERPROFILE") ! throw .extern - #end + #else return core:getenv("HOME") ! throw .extern + #end } diff --git a/lib/src/io/io.valk b/lib/src/io/io.valk index 964cb105..c47e41d9 100644 --- a/lib/src/io/io.valk +++ b/lib/src/io/io.valk @@ -93,6 +93,7 @@ use mem #error "IO read: Unsupported OS" #end + #if OS == linux || OS == bsd coro:yield() #if OS == bsd let rcvd = ext:aio_return(cb) @@ -102,6 +103,7 @@ use mem #end if rcvd < 0 : throw .read return rcvd.@cast(uint) + #end } // Write diff --git a/src/build/ast.valk b/src/build/ast.valk index 14ad984a..a2e16668 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -22,9 +22,8 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { parse_compile_macro(p, scope) continue } - if p.word_is("}") { - break - } + if p.word_is(";") : continue + if p.word_is("}") : break p.error("Unexpected code after return: '%{ p.word() }'") } } From 353a8c1bf6959066b669a357311807a8a65a4fcb Mon Sep 17 00:00:00 2001 From: ctxcode Date: Tue, 17 Mar 2026 15:43:42 +0100 Subject: [PATCH 29/32] improvements --- ROADMAP.md | 1 + src/build/ast-gen.valk | 1 + src/build/ast.valk | 20 +++++++++----- src/build/enum.valk | 2 -- src/build/error-handling.valk | 46 +++++++++++++------------------- src/build/func.valk | 1 + src/build/match.valk | 49 ++++++++++++++++++++--------------- src/build/offset.valk | 4 ++- src/build/scope.valk | 1 + src/build/type.valk | 18 ++----------- src/build/value-gen.valk | 25 ------------------ src/build/value.valk | 22 +--------------- 12 files changed, 69 insertions(+), 121 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9e07de67..10b3b9be 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -9,6 +9,7 @@ - Rework namespaces - Nullable int - fnptr -> fn without allocation +- match without a value # Upcoming version diff --git a/src/build/ast-gen.valk b/src/build/ast-gen.valk index d28b3251..dc24f274 100644 --- a/src/build/ast-gen.valk +++ b/src/build/ast-gen.valk @@ -43,6 +43,7 @@ fn ast_return_value(func: Func, scope: Scope, value: ?Value) { }) scope.did_return = true + scope.did_exit = true } fn ast_gen_throw(ctx: Context, scope: Scope, etype: ErrorType, code: Value, payload: ?Map[Value], location: String) { diff --git a/src/build/ast.valk b/src/build/ast.valk index a2e16668..e2219dae 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -15,6 +15,11 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { fast.free_buffers(func_buffer_count) if scope.did_return { + if scope.did_exit { + // Insert unreachable after calling exit-function + let func = p.func() + scope.ast.append(Token { type: AST.unreachable }) + } if !single { while true { let t = p.tok(true, true) @@ -27,11 +32,6 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { p.error("Unexpected code after return: '%{ p.word() }'") } } - if scope.did_exit { - // Insert unreachable after calling exit-function - let func = p.func() - scope.ast.append(Token { type: AST.unreachable }) - } break } @@ -175,6 +175,9 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { }) } + if scope.must_exit && !scope.did_exit { + p.error("Missing exit statement") + } if scope.must_return && !scope.did_return { p.error("Missing 'return' statement") } @@ -627,8 +630,11 @@ fn ast_each_stage( let break_scope = sub.sub_scope(SCOPE.default) break_scope.ast.append(Token { type: AST.break }) break_scope.did_return = true - let breakv = vgen_inline_scope(break_scope, type_none(b)) - fcall = vgen_error_handler(p.ctx, sub, fcall, fcall.trim_errors(), breakv, false) + let breakv = vgen_inline_scope(break_scope, type_void(b)) + // fcall = vgen_error_handler(p.ctx, sub, fcall, fcall.trim_errors(), breakv, false) + let err_check = vgen_error_check(b, fcall) + let condv = vgen_cond_value(err_check, breakv) + fcall = vgen_this_but_that(condv, fcall.trim_errors()) if !p.loop_first_parse : sub.ast.append(tgen_statement(fcall)) let retvs = fcall.unroll() diff --git a/src/build/enum.valk b/src/build/enum.valk index 8bf53961..cd93157f 100644 --- a/src/build/enum.valk +++ b/src/build/enum.valk @@ -119,14 +119,12 @@ enum TYPE { voidptr int float - undefined array error_code func multi bool closure - none promise any // For docs ref diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index ce006b32..c3160f40 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -82,44 +82,34 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, error_type: ErrorTyp let left = on.trim_errors() // Alternative value - p.set_suggest(left.rett) - let alt = read_inline_scope_value(p, sub) - alt = vgen_unroll_inline_scope(alt) - p.pop_suggest() - if expect_value { + // {statement} !? ...value... + p.set_suggest(left.rett) + let alt = read_value(p, sub) + p.pop_suggest() + // Type check alt = alt.try_convert(p.ctx, scope, left.rett) let matched_type = left.rett.match_nullable(alt.rett) matched_type.compat_check(alt.rett, p.ctx) + // ...statement... has_error ? RETV : ALT + let err_check = vgen_error_check(b, on) + return vgen_this_or_that(err_check, alt, left, alt.rett) } - return vgen_error_handler(p.ctx, scope, on, left, alt, expect_value) -} - -fn vgen_error_handler(ctx: Context, scope: Scope, on: Value, left: Value, alt: Value, merge_values: bool) Value { - - // ?! (1,2) => multi-value[this_or_that[err-check, reset+alt[x], on[x] ]] : alt.rett - // ! (1,2) => this_but[cond-value[err-check, reset+alt], on] : void - // ! return (1,2) => multi-value[this_but[cond-value[err-check, reset+alt[x]], on[x] ]] : on.rett + // {statement} ! ...code... + let multi = p.next_word_is("{", true, true, true) + read_ast(p, sub, !multi) + let alt = vgen_inline_scope(sub, type_void(b)) - let b = on.rett.build let err_check = vgen_error_check(b, on) + let condv = vgen_cond_value(err_check, alt) - if !merge_values { - // {statement} ! ...code... - let condv = vgen_cond_value(err_check, alt) - - // Code that exits - if alt.rett.is_none() { - return vgen_this_but_that(condv, left) - } - // Code that continues - condv.rett = type_void(b) - return condv + // Code that exits + if sub.did_return { + return vgen_this_but_that(condv, left) } - - // Multi values - return vgen_this_or_that(err_check, alt, left, alt.rett) + // Code that continues + return condv } fn value_success_handler(p: Parser, scope: Scope, on: Value) Value { diff --git a/src/build/func.valk b/src/build/func.valk index 0c50fa70..ccb41b50 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -298,6 +298,7 @@ class Func { // if this.rett.count_types() > 0 : scope.must_return = true + if this.must_exit : scope.must_exit = true // Argument variable identifiers each this.args as arg { diff --git a/src/build/match.valk b/src/build/match.valk index 50f10db0..98e8b51f 100644 --- a/src/build/match.valk +++ b/src/build/match.valk @@ -44,7 +44,7 @@ fn parse_match_value(p: Parser, scope: Scope) Value { let else_scope = main_ast.sub_scope(SCOPE.if) let default_reached = false let case_count = 0 - let return_count = 0 + let exit_count = 0 // Parse match items while true { @@ -90,34 +90,41 @@ fn parse_match_value(p: Parser, scope: Scope) Value { p.expect("=>", true, true) - let sub = if_scope.sub_scope(SCOPE.default) - let itemv = read_inline_scope_value(p, sub) - let did_return = itemv.rett.is_none() - if did_return : return_count++ + // let sub = if_scope.sub_scope(SCOPE.default) - if returns_a_value && !did_return { - itemv = vgen_unroll_inline_scope(itemv) - itemv = itemv.try_convert(p.ctx, if_scope, rett) - rett.compat_check(itemv.rett, p.ctx) + if returns_a_value { + p.set_suggest(rett) + let itemv = read_value(p, if_scope) + p.pop_suggest() - let values = itemv.unroll() - each decls as decl, i { - let val = values.get(i) ! p.error("Missing match value (bug)") + if if_scope.did_return { if_scope.ast.append(Token { - type: AST.declare - decl: decl - value1: val + type: AST.statement + value1: itemv }) + } else { + itemv = itemv.try_convert(p.ctx, if_scope, rett) + rett.compat_check(itemv.rett, p.ctx) + + let values = itemv.unroll() + each decls as decl, i { + let val = values.get(i) ! p.error("Missing match value (bug)") + if_scope.ast.append(Token { + type: AST.declare + decl: decl + value1: val + }) + } } } else { - if_scope.ast.append(Token { - type: AST.statement - value1: itemv - }) + let multi = p.next_word_is("{", true, true, true) + read_ast(p, if_scope, !multi) } + if if_scope.did_exit : exit_count++ + if isset(cond) { - if did_return : apply_issets(scope, cond.not_issets) + if if_scope.did_return : apply_issets(scope, cond.not_issets) apply_issets(else_scope, cond.not_issets) } @@ -149,7 +156,7 @@ fn parse_match_value(p: Parser, scope: Scope) Value { } } - if return_count == case_count { + if exit_count == case_count { // let func = scope.get_func() ! p.error("Missing function for match scope (bug)") scope.did_return = true scope.did_exit = true diff --git a/src/build/offset.valk b/src/build/offset.valk index 1a890aca..32313a1d 100644 --- a/src/build/offset.valk +++ b/src/build/offset.valk @@ -49,7 +49,9 @@ fn parse_offset_value(p: Parser, scope: Scope, on: Value) Value { let left = get_call.trim_errors() let altv = vgen_empty_value(left.rett) ?! return left altv.rett = altv.rett.get_nullable() - get_call = vgen_error_handler(p.ctx, scope, get_call, left, altv, true) + // get_call = vgen_error_handler(p.ctx, scope, get_call, left, altv, true) + let err_check = vgen_error_check(p.build, get_call) + get_call = vgen_this_or_that(err_check, altv, left, altv.rett) } } // diff --git a/src/build/scope.valk b/src/build/scope.valk index 502c85cb..96cddfc7 100644 --- a/src/build/scope.valk +++ b/src/build/scope.valk @@ -22,6 +22,7 @@ class Scope { loop_idecl: ?Decl (null) did_return: bool (false) must_return: bool (false) + must_exit: bool (false) did_exit: bool (false) static fn new(type: int, build: Build, parent: ?Scope (null)) Scope { diff --git a/src/build/type.valk b/src/build/type.valk index d8bfd592..6ebb89e5 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -106,12 +106,6 @@ class Type { fn is_void() bool { return this.type == TYPE.void } - fn is_undefined() bool { - return this.type == TYPE.undefined - } - fn is_none() bool { - return this.type == TYPE.none - } fn is_multi() bool { return this.type == TYPE.multi } @@ -202,9 +196,7 @@ class Type { return result } if t == TYPE.any : return this.custom_name - if t == TYPE.none : return "none" if t == TYPE.void : return "void" - if t == TYPE.undefined : return "@undefined" // if t == TYPE.error : return "" if t == TYPE.error_code : return "" @@ -607,7 +599,7 @@ class Type { if isset(types) : return types } let res = Array[Type].new() - if !this.is_void() && !this.is_none() && !this.is_undefined() { + if !this.is_void() { res.append(this) } return res @@ -617,7 +609,7 @@ class Type { let types = this.sub_types if isset(types) : return types.length } - if !this.is_void() && !this.is_none() && !this.is_undefined() : return 1 + if !this.is_void() : return 1 return 0 } @@ -856,18 +848,12 @@ fn read_pointer_info(p: Parser, scope: Scope, type: Type) { fn type_void(b: Build) Type { return Type.new(b, TYPE.void) } -fn type_undefined(b: Build) Type { - return Type.new(b, TYPE.undefined) -} fn type_null(b: Build) Type { return Type.new(b, TYPE.null) } fn type_any(b: Build) Type { return Type.new(b, TYPE.any) } -fn type_none(b: Build) Type { - return Type.new(b, TYPE.none) -} fn type_func(func: Func) Type { let t = Type.new(func.build, TYPE.func) let fi = func.info() diff --git a/src/build/value-gen.valk b/src/build/value-gen.valk index 3f9adf3f..949fe5cd 100644 --- a/src/build/value-gen.valk +++ b/src/build/value-gen.valk @@ -660,31 +660,6 @@ fn vgen_inline_scope(scope: Scope, rett: Type) Value { } } -fn vgen_unroll_inline_scope(iscope: Value) Value { - let scope = iscope.scope1 - if !isset(scope) : iscope.rett.build.error("Missing scope value for inline scope token (bug)") - - let last = scope.ast.get(scope.ast.length - 1) !? null - if isset(last) { - if last.type == AST.statement { - let v = last.value1 - if isset(v) { - let values = v.unroll() - if values.length == 1 { - return vgen_this_but_that(iscope, v) - } else { - let results = Array[Value].new() - each values as val, i { - results.append(vgen_this_but_that(iscope, val)) - } - return vgen_grouped_values(iscope.rett.build, results) - } - } - } - } - return iscope -} - fn vgen_phi(values: Array[Value], rett: Type) Value { return Value { type: VAL.phi diff --git a/src/build/value.valk b/src/build/value.valk index 6340c659..c2a8f92e 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -578,7 +578,7 @@ fn read_value(p: Parser, scope: Scope, prio: int (99999), assignable: bool (fals } p.expect(")", true, true) v = vgen_longjmp(b, buf); - scope.did_return = true; + scope.did_return = true let current_func = scope.get_func() ! p.error("Missing function in scope for longjmp (bug)") current_func.info_calls_unknown_code = true @@ -2025,23 +2025,3 @@ fn handle_enum(p: Parser, e: Enum, scope: Scope) Value { let intv = e.values_int.get(name) ! p.error("Missing integer value for enum item (bug)") return vgen_int(intv, int_type) } - - -fn read_inline_scope_value(p: Parser, scope: Scope) Value { - let t = p.tok(true, false, false) - if t == TOK.none : p.error("Missing error value / code") - let single_line = true - if p.word_is("{") { - single_line = false - p.tok(true, true) - } - read_ast(p, scope, single_line) - - let b = p.build - let rett = type_void(b) - if scope.did_return { - rett = type_none(b) - } - - return vgen_inline_scope(scope, rett) -} From 147354f47cc3b12c3717031690aec8083e34b25c Mon Sep 17 00:00:00 2001 From: ctxcode Date: Tue, 17 Mar 2026 16:09:46 +0100 Subject: [PATCH 30/32] throw flag --- src/build/ast.valk | 12 +++++++++++- src/build/error-handling.valk | 2 +- src/build/func.valk | 8 +++++++- src/build/scope.valk | 1 + src/build/value.valk | 6 +++++- tests/error-function.valk | 21 +++++++++++++++++++++ 6 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 tests/error-function.valk diff --git a/src/build/ast.valk b/src/build/ast.valk index e2219dae..edf45a08 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -175,8 +175,11 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { }) } + if scope.must_throw && !scope.did_exit { + p.error("Missing 'throw' statement") + } if scope.must_exit && !scope.did_exit { - p.error("Missing exit statement") + p.error("Missing exit-statement") } if scope.must_return && !scope.did_return { p.error("Missing 'return' statement") @@ -302,7 +305,12 @@ fn ast_return(p: Parser, scope: Scope) { return } + // Function return let func = p.func() + + if func.must_exit || func.will_exit : p.error("You cannot use 'return' in a function that's marked with '$exit'") + if func.must_throw : p.error("You cannot use 'throw' in a function that's marked with '$exit'") + let rettc = func.rett.count_types() if rettc == 0 { let values = Array[Value]{} @@ -711,6 +719,8 @@ fn ast_throw(p: Parser, scope: Scope) { let func = p.func() let b = p.build + if func.must_exit : p.error("You cannot 'throw' in a function that's flagged with '$exit'") + let etype = func.error_type if !isset(etype) : p.error("This function has no error type") diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk index c3160f40..0abbc43c 100644 --- a/src/build/error-handling.valk +++ b/src/build/error-handling.valk @@ -200,7 +200,7 @@ fn value_pass_handler(ctx: Context, scope: Scope, on: Value, etype: ErrorType) V let sub = scope.sub_scope(SCOPE.default) let alt = vgen_inline_scope(sub, type_void(b)) let to = func.error_type - if !isset(to) : ctx.error("Passing an error but your function has no error type") + if !isset(to) : ctx.error("Throwing an error but your function has no error type") if !to.is_compat(etype) : ctx.error("Incompatible error types: " + etype + " -> " + to + ". The '%to' type must contain '%etype' in the 'extends' list") let values = Array[Value]{} diff --git a/src/build/func.valk b/src/build/func.valk index ccb41b50..f9d11a6e 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -16,6 +16,8 @@ class FuncInfo { error_type: ?ErrorType infinite_args: bool (false) can_error: bool (false) + must_exit: bool (false) + must_throw: bool (false) fn first_rett() Type !missing { return this.rett_types.get(0) ! throw missing @@ -28,6 +30,8 @@ class FuncInfo { error_type: this.error_type infinite_args: this.infinite_args can_error: isset(this.error_type) + must_exit: this.must_exit + must_throw: this.must_throw } } @@ -157,7 +161,6 @@ class Func { // Bools must_exit: bool (false) - exited: bool (false) will_exit: bool (false) must_throw: bool (false) // @@ -192,6 +195,8 @@ class Func { error_type: this.error_type infinite_args: this.infinite_args can_error: this.can_error + must_exit: this.must_exit + must_throw: this.must_throw } } fn arg_types() Array[Type] { @@ -299,6 +304,7 @@ class Func { // if this.rett.count_types() > 0 : scope.must_return = true if this.must_exit : scope.must_exit = true + if this.must_throw : scope.must_throw = true // Argument variable identifiers each this.args as arg { diff --git a/src/build/scope.valk b/src/build/scope.valk index 96cddfc7..8b93c0cd 100644 --- a/src/build/scope.valk +++ b/src/build/scope.valk @@ -23,6 +23,7 @@ class Scope { did_return: bool (false) must_return: bool (false) must_exit: bool (false) + must_throw: bool (false) did_exit: bool (false) static fn new(type: int, build: Build, parent: ?Scope (null)) Scope { diff --git a/src/build/value.valk b/src/build/value.valk index c2a8f92e..cd7c2a2e 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -1548,7 +1548,11 @@ fn value_func_call(p: Parser, scope: Scope, on: Value, read_co: bool) Value { if func_info.can_error && !read_co { let etype = func_info.error_type ?! p.error("Missing error type in function info for error handler") - res = value_error_handling(p, scope, res, etype) + if func_info.must_throw { + res = value_pass_handler(p.ctx, scope, res, etype) + } else { + res = value_error_handling(p, scope, res, etype) + } } return res diff --git a/tests/error-function.valk b/tests/error-function.valk new file mode 100644 index 00000000..4b0a5764 --- /dev/null +++ b/tests/error-function.valk @@ -0,0 +1,21 @@ + +fn test_err_func_throw(val: &uint) !AnError $throw { + val[0]++ + throw AnError.error +} +fn test_err_func(val: &uint) !AnError { + if val[0] == 0 : test_err_func_throw(val) +} + +test "Error function" { + let v : uint = 0 + let has_err = false + test_err_func(&v) ! has_err = true + assert(has_err) + assert(v == 1) + + has_err = false + test_err_func(&v) ! has_err = true + assert(!has_err) + assert(v == 1) +} \ No newline at end of file From 46c60bf3a1475fff831bdc1f6e82212dcc3834f4 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Tue, 17 Mar 2026 17:42:57 +0100 Subject: [PATCH 31/32] docs --- docs/docs.md | 52 +++++++++++++++++++++++++++++++++++++++++++- src/build/func.valk | 2 +- src/build/value.valk | 15 +++++-------- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/docs/docs.md b/docs/docs.md index 61c60d71..387536ed 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -19,7 +19,9 @@ * [Typehints](#typehints) * [Functions](#functions) * [Errors](#errors) - * [Error Handling](#error-handling) + * [Error handling](#error-handling) + * [Throw functions](#throw-functions) + * [Exit functions](#exit-functions) * [Closures](#closures) @@ -411,6 +413,54 @@ fn main() { } ``` +### Throw functions + +Throw-functions are used to generate throw statements in order to reduce repetitive code. A throw function is just a normal function that's flagged with `$throw` + +Example + +```rust +fn parse_error(parser: MyParser, message: String) !ParseError $throw { + // Log message + parser.build.log("Parse error: " + message + " | at: " + parser.location.to_string()) + // Return error + throw ParseError { + message: message + line: parser.location.line + col: parser.location.col + file: parser.filepath ?? "" + content: parser.content + at_index: parser.location.index + } +} + +fn parse() !ParseError { + // ... + if something: parse_error(p, "This should not happen") + // ... +} +``` + +### Exit functions + +Calling a function that's flagged with `$exit` tells the compiler that the function will exit the program. E.g. the `panic` function. This mechanic is used for certain null-checking or error-handling features. + +```rust +fn myexit() $exit { + println("I QUIT") + exit(0) +} +fn main() { + let str : ?String = null + // ... some code ... + if !isset(str) { + println("This should not happen") + myexit() + } + // Now the compiler knows that `str` cannot be `null` at this point +} +``` + ### Closures Closures are anonymous functions that can have variables bound to them from outside their scope. diff --git a/src/build/func.valk b/src/build/func.valk index f9d11a6e..9e1e811f 100644 --- a/src/build/func.valk +++ b/src/build/func.valk @@ -195,7 +195,7 @@ class Func { error_type: this.error_type infinite_args: this.infinite_args can_error: this.can_error - must_exit: this.must_exit + must_exit: this.must_exit || this.will_exit must_throw: this.must_throw } } diff --git a/src/build/value.valk b/src/build/value.valk index cd7c2a2e..8814831e 100644 --- a/src/build/value.valk +++ b/src/build/value.valk @@ -1535,15 +1535,6 @@ fn value_func_call(p: Parser, scope: Scope, on: Value, read_co: bool) Value { p.error("Not enough values to call this function. Function type: " + on_rett) } - // Exit func check - if on.type == VAL.func_ptr { - let func = on.func.@cast(Func) - if func.must_exit || func.will_exit { - scope.did_return = true - scope.did_exit = true - } - } - let res = vgen_func_call(p.build, scope, on, values) if func_info.can_error && !read_co { @@ -1555,6 +1546,12 @@ fn value_func_call(p: Parser, scope: Scope, on: Value, read_co: bool) Value { } } + // Exit/throw func check + if func_info.must_exit || func_info.must_throw { + scope.did_return = true + scope.did_exit = true + } + return res } From 51ccd400b058246e90d650156b092936d6be5792 Mon Sep 17 00:00:00 2001 From: ctxcode Date: Wed, 18 Mar 2026 00:37:45 +0100 Subject: [PATCH 32/32] update function types --- src/build/ast.valk | 2 +- src/build/closures.valk | 2 ++ src/build/type.valk | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/build/ast.valk b/src/build/ast.valk index edf45a08..53b818b3 100644 --- a/src/build/ast.valk +++ b/src/build/ast.valk @@ -29,7 +29,7 @@ fn read_ast(p: Parser, scope: Scope, single: bool) { } if p.word_is(";") : continue if p.word_is("}") : break - p.error("Unexpected code after return: '%{ p.word() }'") + p.error("Unexpected code after %{ scope.did_exit ? "exit" : "return" }: '%{ p.word() }'") } } break diff --git a/src/build/closures.valk b/src/build/closures.valk index fc69fc27..1bc91492 100644 --- a/src/build/closures.valk +++ b/src/build/closures.valk @@ -81,6 +81,8 @@ fn generate_closure_outer_function(parent: Func, inner_func: Value, fi: FuncInfo // Errors func.can_error = fi.can_error func.error_type = fi.error_type + func.must_exit = fi.must_exit + func.must_throw = fi.must_throw // let ctx = Context { diff --git a/src/build/type.valk b/src/build/type.valk index 6ebb89e5..30f2de68 100644 --- a/src/build/type.valk +++ b/src/build/type.valk @@ -251,6 +251,8 @@ class Type { if isset(info) { let etype = info.error_type if isset(etype) : str += "!" + etype.display_name + if info.must_exit : str += " $exit" + if info.must_throw : str += " $throw" } return result + str } @@ -412,6 +414,9 @@ class Type { if !rb.compat(rt) : return false } if fi_type.can_error && !fi_base.can_error : return false + // $exit/$throw + if fi_base.must_exit && !fi_type.must_exit : return false + if fi_base.must_throw && !fi_type.must_throw : return false } // if base.type == TYPE.int || base.type == TYPE.float { @@ -699,12 +704,25 @@ fn read_type(p: Parser, scope: Scope, allow_newline: bool (true), allow_multi: b let error_type : ?ErrorType = null read_errors(p, scope, &error_type) + let is_exit = false + let is_throw = false + t = p.tok(true, false, false) + while t == TOK.flag { + t = p.tok(true, false) + if p.word_is("$exit") : is_exit = true + else if p.word_is("$throw") : is_throw = true + else : p.error("Unexpected flag: %{ p.word() }") + t = p.tok(true, false, false) + } + // Result let info = FuncInfo { args: types rett_types: rett_types error_type: error_type can_error: isset(error_type) + must_exit: is_exit + must_throw: is_throw } let type = Type.new(p.build, is_closure ? TYPE.closure : TYPE.func) type.func_info = info