diff --git a/ROADMAP.md b/ROADMAP.md
index 0af360b2..10b3b9be 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -7,6 +7,9 @@
- Release 0.1.9
- Rework errors
- Rework namespaces
+- Nullable int
+- fnptr -> fn without allocation
+- match without a value
# Upcoming version
diff --git a/docs/api.md b/docs/api.md
index 4534301e..1914e898 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,8 +386,8 @@ 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]
}
```
@@ -385,8 +399,8 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) |
~ 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
@@ -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,14 +593,14 @@ 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
}
@@ -622,24 +638,24 @@ Namespaces: [ansi](#ansi) | [core](#core) | [coro](#coro) | [crypto](#crypto) |
+ fn equal_ignore_order(array: Array[T]) bool
+ 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]
@@ -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,8 +686,8 @@ 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 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
@@ -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..387536ed 100644
--- a/docs/docs.md
+++ b/docs/docs.md
@@ -18,7 +18,10 @@
* [Objects](#objects)
* [Typehints](#typehints)
* [Functions](#functions)
- * [Error Handling](#error-handling)
+ * [Errors](#errors)
+ * [Error handling](#error-handling)
+ * [Throw functions](#throw-functions)
+ * [Exit functions](#exit-functions)
* [Closures](#closures)
@@ -35,7 +38,7 @@
* [If/Else](#if-else)
* [While](#while)
* [Each](#each)
- * [Throw](#error-handling)
+ * [Throw](#errors)
@@ -337,17 +340,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.
-Functions can return errors using `throw`. Errors must be defined in your function declaration first.
+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
-fn my_func(must_fail: bool) String !fail {
- if must_fail : throw fail
- return "hi"
+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 }
+```
+
+Usage:
+
+```rust
+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
@@ -367,54 +394,70 @@ 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")
+ default => println("Another error was thrown") // Only required if not all codes were checked (compiler will tell)
}
- // Checking the error code using `if`
- if E == E.fail : println("Error code `fail` was thrown")
+ // Payload data
+ println(E.message)
}
}
```
-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.
+### 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
-use valk:core
+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 f1() !fail {
- f2() !>
+fn parse() !ParseError {
+ // ...
+ if something: parse_error(p, "This should not happen")
+ // ...
}
-fn f2() !fail {
- throw fail
+```
+
+### 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() {
- f1() ! {
- let trace = core:get_error_trace()
- each trace as str {
- println(str)
- }
- // OR
- core:print_error_trace()
- // ------------- ERROR TRACE -------------
- // .../src/example.valk:4
- // .../src/example.valk:7
- // -------------- END TRACE --------------
+ 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
}
```
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/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
new file mode 100644
index 00000000..3f83d1fd
--- /dev/null
+++ b/lib/src/core/errors.valk
@@ -0,0 +1,7 @@
+
+error AnError (error)
+error ExternError (extern)
+error IterError (end)
+error InitError (init)
+error LookupError (missing, exists, range, empty)
+error SyntaxError (syntax)
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/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..f6932153 100644
--- a/lib/src/core/sysinfo.valk
+++ b/lib/src/core/sysinfo.valk
@@ -1,19 +1,19 @@
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 .extern
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 .extern
return res.to(uint)
#else
- throw unknown
+ throw .extern
#end
}
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/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..c9872e5e 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
@@ -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)
@@ -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..0054195f 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) !>
+ while stream.read(32000, buffer) !> : {}
return SELF.create_from_buffer(buffer)
}
diff --git a/lib/src/fs/path.valk b/lib/src/fs/path.valk
index 28a95da4..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 {
@@ -204,9 +206,10 @@ 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 .extern
+ #else
+ return core:getenv("HOME") ! throw .extern
#end
- return core:getenv("HOME") ! throw not_found
}
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 4de2fd97..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_failed !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_failed, "Failed to create socket: " + EMSG
+ 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, 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) !>
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, "Unexpected disconnect during request"
+ 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/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/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/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/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/http/worker.valk b/lib/src/http/worker.valk
index de64a436..dc6ce374 100644
--- a/lib/src/http/worker.valk
+++ b/lib/src/http/worker.valk
@@ -12,15 +12,16 @@ 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
}
#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/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..c47e41d9 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)
}
@@ -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)
@@ -100,39 +101,40 @@ use mem
#else
let rcvd = coro.completion_res
#end
- if rcvd < 0 : throw fail
+ if rcvd < 0 : throw .read
return rcvd.@cast(uint)
+ #end
}
// 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 +147,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 +161,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/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/error.valk b/lib/src/json/error.valk
new file mode 100644
index 00000000..5e191286
--- /dev/null
+++ b/lib/src/json/error.valk
@@ -0,0 +1,6 @@
+
+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 8710bfe7..294e668e 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
+ character: ch
+ }
}
- - 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
+ character: ch
+ }
}
- 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-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/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/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 c1c16e07..a11d3799 100644
--- a/lib/src/net/connection.valk
+++ b/lib/src/net/connection.valk
@@ -1,11 +1,12 @@
-use ext
use io
+ class Connection {
~ 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 +28,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)) !NetError {
if this.ssl_enabled : return
@@ -39,17 +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_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
}
@@ -57,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
@@ -88,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)
@@ -105,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
@@ -124,15 +127,15 @@ use io
break
}
- throw connection
+ throw .write
}
return bytes_sent
}
- + fn recv(buffer: ByteBuffer, bytes: uint) uint !connection !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
@@ -155,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 connection, "SSL Error #" + err;
+ throw .ssl
}
count = rcvd.@cast(uint)
buffer.length += count
@@ -170,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..4b2ded38 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,31 +23,31 @@ 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)
- if fd == ext:INVALID_SOCKET : throw create
+ if fd == ext:INVALID_SOCKET : throw .init
ext:CreateIoCompletionPort(fd, io:iocp(), 0, 0)
#else
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/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/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..779f9db4 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
}
/////////////////////////
@@ -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/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-gen.valk b/src/build/ast-gen.valk
index d2b4b5fe..dc24f274 100644
--- a/src/build/ast-gen.valk
+++ b/src/build/ast-gen.valk
@@ -15,10 +15,10 @@ 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)))
retv = vgen_grouped_values(b, values)
}
@@ -43,9 +43,10 @@ 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, 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()
@@ -54,11 +55,17 @@ fn ast_gen_throw(ctx: Context, scope: Scope, code: Value, msg: Value, location:
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 {
+ 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)
+ }
+ }
ast_return_value(func, scope, vgen_grouped_values(b, values))
}
diff --git a/src/build/ast.valk b/src/build/ast.valk
index f64724aa..53b818b3 100644
--- a/src/build/ast.valk
+++ b/src/build/ast.valk
@@ -15,12 +15,23 @@ 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 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 })
}
+ 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(";") : continue
+ if p.word_is("}") : break
+ p.error("Unexpected code after %{ scope.did_exit ? "exit" : "return" }: '%{ p.word() }'")
+ }
+ }
break
}
@@ -39,7 +50,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 +103,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")
@@ -169,6 +175,12 @@ 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")
+ }
if scope.must_return && !scope.did_return {
p.error("Missing 'return' statement")
}
@@ -293,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]{}
@@ -621,8 +638,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()
@@ -699,20 +719,58 @@ fn ast_throw(p: Parser, scope: Scope) {
let func = p.func()
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 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 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")
+
+ // 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
+ 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 = 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
+ 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, vgen_int(hash, b.error_code_type()), msg, p.clone_chunk().path_and_line())
+ 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, errv, payload, p.chunk.path_and_line())
}
fn ast_cothrow(p: Parser, scope: Scope) {
@@ -721,17 +779,8 @@ 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")), 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/build.valk b/src/build/build.valk
index 918a7c1e..a01a91ce 100644
--- a/src/build/build.valk
+++ b/src/build/build.valk
@@ -40,6 +40,8 @@ class Build {
tests: Array[Func] (.{})
vtable_indexes: Map[uint] (.{})
enums: Array[Enum] (.{})
+ error_types: Array[ErrorType] (.{})
+ payload_globals: Map[Global] (.{})
allocators: Map[Allocator] (.{})
//
functions_tracking_globals: Array[Func] (.{})
@@ -216,11 +218,11 @@ class Build {
let class = this.valk_class(ns, name)
return class.get_type()
}
- fn error_code_type() Type {
- return this.valk_type("type", "u32")
- }
- fn error_msg_type() Type {
- return this.valk_type("type", "String")
+ 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 array_of_string_type() Type {
let a = this.valk_class("type", "Array")
diff --git a/src/build/closures.valk b/src/build/closures.valk
index d3566999..1bc91492 100644
--- a/src/build/closures.valk
+++ b/src/build/closures.valk
@@ -80,7 +80,9 @@ 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
+ func.must_exit = fi.must_exit
+ func.must_throw = fi.must_throw
//
let ctx = Context {
@@ -117,9 +119,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/coro-gen.valk b/src/build/coro-gen.valk
index c6d86d90..9e8142bd 100644
--- a/src/build/coro-gen.valk
+++ b/src/build/coro-gen.valk
@@ -128,8 +128,7 @@ 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.complete()\n")
code.append_str(" return\n")
code.append_str("}\n")
@@ -235,7 +234,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 +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 errors = on.rett.errors ?? Map[ERR_TYPE]{}
+ 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)
@@ -350,13 +349,13 @@ 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 codev = vgen_prop(prop, on)
+ codev.rett = etype.get_type(null)
+ res.func_err_code = codev
// 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/enum.valk b/src/build/enum.valk
index 8d46c573..cd93157f 100644
--- a/src/build/enum.valk
+++ b/src/build/enum.valk
@@ -75,6 +75,7 @@ enum SCOPE {
func
class
vscope
+ error
}
// Parser
@@ -118,15 +119,12 @@ enum TYPE {
voidptr
int
float
- undefined
array
- error
- error_item
+ error_code
func
multi
bool
closure
- none
promise
any // For docs
ref
@@ -151,6 +149,8 @@ enum IDF {
idf_group
class_prop
prop
+ error_type
+ error_value
}
// Class types
diff --git a/src/build/error-handling.valk b/src/build/error-handling.valk
index e3812207..0abbc43c 100644
--- a/src/build/error-handling.valk
+++ b/src/build/error-handling.valk
@@ -9,12 +9,12 @@ 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)
-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")
@@ -40,7 +40,7 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, errors: Map[ERR_TYPE
}
// 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("!!") {
@@ -50,60 +50,66 @@ fn value_error_handling(p: Parser, scope: Scope, on: Value, errors: Map[ERR_TYPE
let expect_value = p.word_is("!?")
// Handle error
- let sub = scope.sub_scope(SCOPE.default)
+ let sub = scope.sub_scope(SCOPE.error)
// 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, errors)
- 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[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)
+ 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, ErrorValuePayload {
+ value: v
+ decl: decl
+ })
+ }
+ }
+
+ sub.set_idf(p.ctx, "E", Idf.for_error_value(ev))
// Left return value
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 {
@@ -184,7 +190,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)
@@ -193,15 +199,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("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]{}
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
new file mode 100644
index 00000000..fe1057e4
--- /dev/null
+++ b/src/build/error-type.valk
@@ -0,0 +1,147 @@
+
+use helper
+
+class ErrorType {
+ build: Build
+ fc: Fc
+ act: int
+ name: String
+ display_name: String
+ chunk_define: Chunk
+ chunk_extends: ?Chunk
+ chunk_payload: ?Chunk
+ 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] (.{})
+ 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
+ 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() {
+ 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
+ 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")
+ 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)
+ }
+ 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)
+ }
+ p.expect2(",", ")", true, true)
+ if p.word_is(")") : break
+ }
+ }
+ 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)
+ this.add_payload(p.ctx, name, type)
+ }
+ }
+ if codes.length == 0 {
+ let p = Parser.new(this.chunk_define, null)
+ p.error("Error types 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 codes = this.codes
+ if codes.has_value(name) : ctx.error("The payload property name '%name' is already used as an error code.")
+ 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")
+ }
+ 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)
+ }
+
+ fn is_compat(with: ErrorType) bool {
+ if this != with {
+ if !this.extends.contains(with) : return false
+ }
+ 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 {
+ type = type.get_nullable()
+ let base = type.to_str(true)
+ let key = base
+ let i = 0
+ 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)
+ 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..7c3073fd
--- /dev/null
+++ b/src/build/error-value.valk
@@ -0,0 +1,10 @@
+
+class ErrorValue {
+ error_type: ErrorType
+ codev: Value
+ payload: ?Map[ErrorValuePayload]
+}
+class ErrorValuePayload {
+ value: Value
+ decl: Decl
+}
\ No newline at end of file
diff --git a/src/build/func.valk b/src/build/func.valk
index ab18d9c8..9e1e811f 100644
--- a/src/build/func.valk
+++ b/src/build/func.valk
@@ -13,9 +13,11 @@ 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)
+ must_exit: bool (false)
+ must_throw: bool (false)
fn first_rett() Type !missing {
return this.rett_types.get(0) ! throw missing
@@ -25,9 +27,11 @@ 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: this.errors.length > 0
+ can_error: isset(this.error_type)
+ must_exit: this.must_exit
+ must_throw: this.must_throw
}
}
@@ -44,10 +48,10 @@ 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))
return res
}
fn rett(b: Build) Type {
@@ -125,7 +129,7 @@ class Func {
args: Array[Arg] (Array[Arg]{})
rett: Type
rett_real: Type
- errors: Map[u32] (Map[u32]{})
+ error_type: ?ErrorType
arg_scope: Scope
// Generics
@@ -156,7 +160,10 @@ class Func {
disable_gc_stack: bool (false)
// Bools
- is_exit: bool (false)
+ must_exit: bool (false)
+ will_exit: bool (false)
+ must_throw: bool (false)
+ //
is_static: bool (false)
is_extern: bool (false)
infinite_args: bool (false)
@@ -185,9 +192,11 @@ 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
+ must_exit: this.must_exit || this.will_exit
+ must_throw: this.must_throw
}
}
fn arg_types() Array[Type] {
@@ -230,10 +239,9 @@ 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())
+ p.skip_id(false)
t = p.tok(true, true, false)
}
@@ -295,6 +303,8 @@ 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/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/idf.valk b/src/build/idf.valk
index 028f2500..626da9e4 100644
--- a/src/build/idf.valk
+++ b/src/build/idf.valk
@@ -15,6 +15,8 @@ class Idf {
macro_values: ?Array[Map[Idf]] (null)
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)
@@ -131,6 +133,19 @@ class 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
if !isset(item) : build_error("Missing decl value (bug)")
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-type.valk b/src/build/ir-type.valk
index 67157347..06eac271 100644
--- a/src/build/ir-type.valk
+++ b/src/build/ir-type.valk
@@ -3,7 +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 : return "i32"
+ 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/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/match.valk b/src/build/match.valk
index 55097319..98e8b51f 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
}
@@ -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 {
@@ -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)
}
}
@@ -93,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)
}
@@ -152,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 28c000e6..32313a1d 100644
--- a/src/build/offset.valk
+++ b/src/build/offset.valk
@@ -41,14 +41,17 @@ 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
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/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..8b93c0cd 100644
--- a/src/build/scope.valk
+++ b/src/build/scope.valk
@@ -22,6 +22,8 @@ class Scope {
loop_idecl: ?Decl (null)
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 {
@@ -98,7 +100,7 @@ class Scope {
return b.valk_idf("type", name)
}
// core
- if name == "exit" || name == "panic" {
+ if name == "exit" || name == "panic" || name == "AnError" || name == "ExternError" || name == "IterError" || name == "InitError" || name == "LookupError" || name == "SyntaxError" {
return b.valk_idf("core", name)
}
@@ -196,4 +198,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/stage-1-fc.valk b/src/build/stage-1-fc.valk
index 86a0533c..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") {
@@ -157,6 +151,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 +613,53 @@ 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]{}
+
+ p.expect("(", true, 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)
+ 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.next_word_is(",", true, true, 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(")")
+ }
+ 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
+ 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_type(e))
+}
diff --git a/src/build/stage-2-2-props.valk b/src/build/stage-2-2-props.valk
index 64ff9fa6..b2734376 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)
}
@@ -224,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 e28d2c82..430583f9 100644
--- a/src/build/stage-2-4-types.valk
+++ b/src/build/stage-2-4-types.valk
@@ -146,19 +146,16 @@ 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()
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)
@@ -169,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/type.valk b/src/build/type.valk
index 23168047..30f2de68 100644
--- a/src/build/type.valk
+++ b/src/build/type.valk
@@ -7,7 +7,8 @@ class Type {
sub_type: ?Type (null)
array_size: uint (0)
func_info: ?FuncInfo (null)
- errors: ?Map[ERR_TYPE] (null)
+ error_type: ?ErrorType (null)
+ error_code_name: ?String (null)
class: ?Class (null)
group: ?Group (null)
enum: ?Enum (null)
@@ -105,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
}
@@ -183,6 +178,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)
@@ -197,11 +196,9 @@ 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_item : return ""
+ // if t == TYPE.error : return ""
+ if t == TYPE.error_code : return ""
if t == TYPE.promise {
let result = "co"
@@ -214,11 +211,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
}
@@ -253,8 +248,11 @@ class Type {
}
}
str += ")"
- if isset(info) && info.can_error {
- str += "!"
+ 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
}
@@ -345,7 +343,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
@@ -368,15 +366,12 @@ 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
- 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
+ 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
+ return base_et.is_compat(type_et)
}
// co[u32]!err
if base.type == TYPE.promise {
@@ -392,13 +387,11 @@ 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
- }
+ let base_et = base.error_type
+ let type_et = type.error_type
+ if base_et != type_et {
+ if !isset(base_et) || !isset(type_et) : return false
+ return base_et.is_compat(type_et)
}
return true
}
@@ -421,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 {
@@ -608,7 +604,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
@@ -618,7 +614,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
}
@@ -705,15 +701,28 @@ 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)
+
+ 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
- errors: errors
- can_error: errors.length > 0
+ 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
@@ -725,9 +734,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 +774,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
}
}
@@ -833,6 +835,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")
}
@@ -858,18 +866,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()
@@ -881,11 +883,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 +944,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 {
- 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.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()
+// 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-gen.valk b/src/build/value-gen.valk
index 7cb5c230..949fe5cd 100644
--- a/src/build/value-gen.valk
+++ b/src/build/value-gen.valk
@@ -159,17 +159,15 @@ 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 codev = vgen_rett_value(fcall, index, code_type)
+ fcall.func_err_code = codev
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
@@ -662,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 4ea09f43..8814831e 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,11 +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
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)
}
@@ -356,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 = 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")
-
- // Left
- // let left = vgen_decl(decl)
+ let left = read_value(p, scope)
+ if left.rett.type != TYPE.error_code : p.error("First value in 'error_is' is not an error type")
p.expect(",", true, true)
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)
v = vgen_compare(left, right, OP.eq, type_valk(b, "bool"))
t = p.tok(true, true, false);
@@ -380,7 +369,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)
@@ -589,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
@@ -937,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)
@@ -1358,16 +1352,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)
+ }
}
@@ -1438,6 +1429,55 @@ 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")
+ //
+ 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)
+
+ 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))
+ }
+ 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 etpayload = etype.payload
+ if etpayload.length > 0 {
+ message += " or (payload) " + etpayload.keys().join(", ")
+ }
+ p.error(message)
+ }
p.error("Unhandled identifier type (type: " + for + ") (compiler bug)")
}
@@ -1495,19 +1535,21 @@ 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.is_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 {
- 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")
+ if func_info.must_throw {
+ res = value_pass_handler(p.ctx, scope, res, etype)
+ } else {
+ res = value_error_handling(p, scope, res, etype)
+ }
+ }
+
+ // Exit/throw func check
+ if func_info.must_exit || func_info.must_throw {
+ scope.did_return = true
+ scope.did_exit = true
}
return res
@@ -1535,11 +1577,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 errors = on.rett.errors
- if isset(errors) : lsp.check_completion_simple(errors.keys())
- }
// $length
if size > 0 : lsp.check_completion_simple(Array[String]{ "$length" })
// Enum
@@ -1574,24 +1611,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 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 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)
- }
-
- return vgen_int(errv, type_error_item(b, name, errv))
- }
-
let e = on.rett.enum
if isset(e) {
// TODO
@@ -2007,23 +2026,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)
-}
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 {
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"
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;
}
diff --git a/src/lsp/lsp.valk b/src/lsp/lsp.valk
index 7eb75e55..b5e93e19 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 {
@@ -57,11 +59,7 @@ 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")
+ if E == E.more : return
this.reset()
return
}
@@ -78,6 +76,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 +87,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 +96,7 @@ class Lsp {
}
this.stage = 1
+ this.headers_str = this.buf.part(0, pos)
this.buf.clear_until(pos)
this.index = 0
}
@@ -109,7 +108,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
}
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/closures.valk b/tests/closures.valk
index 7e3da4b6..be4e5e6e 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 !AnError {
+ 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 07ee0952..ee356fef 100644
--- a/tests/coros.valk
+++ b/tests/coros.valk
@@ -5,6 +5,10 @@ use valk:time
global coro_test_str: String ("")
+error TestCoroError (error) 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 !TestCoroError {
+ throw .error { 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, error))
return "z"
}
assert(res == "z")
@@ -50,8 +53,7 @@ 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))
return "z"
}
assert(res == "z")
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
diff --git a/tests/error-handlers.valk b/tests/error-handlers.valk
index af38e244..6a4886bd 100644
--- a/tests/error-handlers.valk
+++ b/tests/error-handlers.valk
@@ -1,21 +1,29 @@
use valk:core
-fn test_errh_success() uint !err {
+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 !err {
- throw err
+fn test_errh_error() uint !TestError {
+ throw ret_err()
}
-fn test_errh_error2() uint !err !err2 {
- throw err2
+fn test_errh_error2() uint !TestError {
+ throw TestError.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 +35,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 +50,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/error-payload.valk b/tests/error-payload.valk
new file mode 100644
index 00000000..b154e6d9
--- /dev/null
+++ b/tests/error-payload.valk
@@ -0,0 +1,47 @@
+
+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_payload1b() !Pay1 {
+ throw .error { message1: "B" }
+}
+fn test_payload2() !Pay2 {
+ test_payload1() !>
+}
+fn test_payload3() !Pay2 {
+ test_payload1() ! {
+ test_payload1b() ! {
+ assert(E.message1 == "B")
+ }
+ 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
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" {
diff --git a/tests/http.valk b/tests/http.valk
index a065b704..99de9e6e 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,11 @@ 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)
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
}
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/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
}
}
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)
diff --git a/tests/try-convert.valk b/tests/try-convert.valk
index a4307e4e..07fcc844 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 !AnError {
+ throw .error
}
test "Try convert: Suggest number types" {
diff --git a/tests/vscope.valk b/tests/vscope.valk
index 3d379900..85a0f2c5 100644
--- a/tests/vscope.valk
+++ b/tests/vscope.valk
@@ -1,12 +1,12 @@
-fn vscope_err() int !err {
- throw err
+fn vscope_err() int !AnError {
+ throw .error
}
-fn vscope_err2() (int, int) !err {
- throw err
+fn vscope_err2() (int, int) !AnError {
+ throw .error
}
-fn vscope_err3() (String, String) !err {
- throw err
+fn vscope_err3() (String, String) !AnError {
+ throw .error
}
fn mvtest() (String, String) {
return ("1", "2")
|