Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
- Release 0.1.9
- Rework errors
- Rework namespaces
- Nullable int
- fnptr -> fn without allocation
- match without a value

# Upcoming version

Expand Down
193 changes: 105 additions & 88 deletions docs/api.md

Large diffs are not rendered by default.

117 changes: 80 additions & 37 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -35,7 +38,7 @@
* [If/Else](#if-else)
* [While](#while)
* [Each](#each)
* [Throw](#error-handling)
* [Throw](#errors)

<br></td><td width=200px><br>

Expand Down Expand Up @@ -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
Expand All @@ -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 ?? "<generated-code>"
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
}
```

Expand Down
33 changes: 17 additions & 16 deletions lib/src/core/clone.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions lib/src/core/env.valk
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions lib/src/core/errors.valk
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions lib/src/core/core.valk → lib/src/core/exit.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand All @@ -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))
}
6 changes: 3 additions & 3 deletions lib/src/core/mutex.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions lib/src/core/sysinfo.valk
Original file line number Diff line number Diff line change
@@ -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
}

Expand Down
3 changes: 1 addition & 2 deletions lib/src/coro/api.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 0 additions & 1 deletion lib/src/coro/coro.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 4 additions & 7 deletions lib/src/crypto/bcrypt.valk
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,21 @@ 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)
// Iterate as many times as desired
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++
}
}
12 changes: 6 additions & 6 deletions lib/src/crypto/blake2b.valk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
Loading
Loading