Try/exceptions

Unlike PHP, in THP all errors must be explicitly declared and handled.

Declare that a function returns an exception

Possible errors have their own syntax: Error!Type. This means: This may be an Error, or a Type.

For example, a function that returned a DivisionByZero may be written like this:

fun invert(Int number) -> DivisionByZero!Int
{
    if number == 0
    {
        return DivisionByZero()
    }

    return 1 / number
}thp
    
Syntax error: Expected a block after the function declaration. at line 1:40

In the previous segment, DivisionByZero!Int denotates that the function may return either a DivisionByZero error or an Int.

There is no throw keyword, errors are just returned.

Multiple error returns

TODO: properly define syntax, how this interacts with type unions.

Multiple errors are chained with !. The last one is always the success value.

fun sample() -> Error1!Error2!Error3!Int
{ /* ... */}thp
    
Syntax error: Expected a block after the function declaration. at line 1:22

Error handling

The caller must handle all possible errors, they don’t automatically bubble up the stack.

THP provides syntax for handling errors following certain patterns, via try expressions:

Naked try

Use a naked try when you want to rethrow an error, if there is any.

fun dangerous() -> Exception!Int
{   // May throw randomly
    return if Math.random() < 0.5 { 50 }
    else { Exception("Unlucky") }
}

fun run() -> Exception!
{   // If `dangerous()` throws, the function exits with the same error.
    // Otherwise, continues
    val result = try dangerous()
    print("The result is {result}")
}

val res1 = run() // First, without error
val res2 = run() // Then, an example with errorthp
    
Syntax error: Expected a block after the function declaration. at line 1:28

In the previous example:

Try/return

Try/return will return a new value if an expression fails, otherwise will assign the success value and continue.

Try/return will run a function and assign its value if Ok is found. Otherwise, it will return a new value specified by the programmer.

fun run() -> Int
{
    val result = try dangerous() return 0

    // ...
}thp
    
Syntax error: Unexpected token `dangerous`, expected a new line at line 3:22

In the previous example:

Try/else

Try/else will assign a new value if an expression fails.

fun run(Exception!Int possible_value)
{
    val mid = try possible_value else 666

    print("The value is: {mid}")
}

run(777)                // With an actual value
run(Exception("oh uh")) // With an exceptionthp
    
Syntax error: Expected an identifier for the parameter. at line 1:8

Either way, the function will continue executing.

Try/catch

Try/catch allows the error to be manually used & handled.

fun run()
{
    val result = try dangerous()
    catch Exception e
    {
        // This is run if `dangerous()` throws.
        // `e` is the thrown error

        // Handle the error
        // ...

        // Return a new value to be assigned to `result`
        0
    }
}thp
    
Syntax error: Unexpected token `dangerous`, expected a new line at line 3:22

A try/catch may have many catch clauses:

try dangerous()
catch Exception1 e
{...}
catch Exception2 e
{...}
catch Exception3 e
{...}thp
    
Syntax error: Unexpected token `dangerous`, expected a new line at line 1:4