added support for try-catch exception handlers

This commit is contained in:
Pekka Laiho 2020-12-09 18:09:35 +07:00
parent 6077dc135c
commit 167711c4f0
2 changed files with 63 additions and 13 deletions

View File

@ -223,6 +223,26 @@ For another example, lets combine `if` and `not` into a macro named `unless`, th
The `quasiquote` form described above is essential for declaring macros. Internally macros are just functions with a special flag.
## Exceptions
The language has support for `try-catch` style exception handlers. The syntax is `(try A (catch B C))` where A is evaluated first and if exception is thrown, then C is evaluated with the symbol B bound to the value of the exception. Exceptions are thrown using the `throw` core function. You can give any data structure as argument to `throw` and it is passed along to `catch`. This way exceptions can contain more data than just a string that represents an error message.
Simple example of throwing and catching an exception:
```
> (try (throw {"msg":"message"}) (catch ex (str "error: " (get ex "msg"))))
"error: message"
```
Exceptions generated by PHP are catched as well. Their value will be a hash-map with keys `type`, `file`, `line` and `message`:
```
> (try (get "wrong" "type") (catch ex (get ex "type")))
"TypeError"
```
The Repl contains its own exception handler defined in PHP that will catch any exceptions thrown outside of `try-catch` form.
## Reflection
You can use the `meta` special form to retrieve the arguments, body, code or full definition of user-defined functions:
@ -276,6 +296,7 @@ or | yes | `(or false 0 1)` | `1` | Return the first value that evaluates to
quote | yes | | | See the section Quoting.
quasiquote | yes | | | See the section Quoting.
quasiquote-expand | yes | | | See the section Quoting.
try | yes | | | See the section Exceptions.
undef | yes | `(undef myFn)` | `<function>` | Remove a definition from the current environment. Return the removed value.
## Functions
@ -293,7 +314,7 @@ print | no | `(print "hello world")` | `hello world` | Print expression on the
pstr | yes | `(pstr {"a":"b"})` | `"{\"a\":\"b\"}"` | Print expression to string.
read | yes | `(read "(+ 1 2 3)")` | `(+ 1 2 3)` | Read a string as code and return the expression.
sleep | no | `(sleep 2000)` | `null` | Sleep for the given period given in milliseconds using [usleep](https://www.php.net/manual/en/function.usleep).
throw | yes | `(throw "invalid value")` | `error: "invalid value"` | Throw an exception. The given value is passed to catch.
throw | yes | `(throw "invalid value")` | `error: "invalid value"` | Throw an exception. The given value is passed to catch. See the section Exceptions.
timer | no | `(timer (fn (d) (sleep d)) 200)` | `0.20010209` | Measure the execution time of a function and return it in seconds.
### Collection functions

View File

@ -116,9 +116,7 @@ class Evaller
} elseif ($symbolName == 'def') {
if ($astLength != 3) {
throw new MadLispException("def requires exactly 2 arguments");
}
if (!($astData[1] instanceof Symbol)) {
} elseif (!($astData[1] instanceof Symbol)) {
throw new MadLispException("first argument to def is not symbol");
}
@ -170,9 +168,7 @@ class Evaller
} elseif ($symbolName == 'fn' || $symbolName == 'macro') {
if ($astLength != 3) {
throw new MadLispException("$symbolName requires exactly 2 arguments");
}
if (!($astData[1] instanceof Seq)) {
} elseif (!($astData[1] instanceof Seq)) {
throw new MadLispException("first argument to $symbolName is not seq");
}
@ -213,9 +209,7 @@ class Evaller
} elseif ($symbolName == 'let') {
if ($astLength != 3) {
throw new MadLispException("let requires exactly 2 arguments");
}
if (!($astData[1] instanceof MList)) {
} elseif (!($astData[1] instanceof MList)) {
throw new MadLispException("first argument to let is not list");
}
@ -367,12 +361,47 @@ class Evaller
}
return $this->quasiquote($astData[1]);
} elseif ($symbolName == 'try') {
if ($astLength != 3) {
throw new MadLispException("try requires exactly 2 arguments");
} elseif (!($astData[2] instanceof MList)) {
throw new MadLispException("second argument to try is not list");
}
$catch = $astData[2]->getData();
if (count($catch) == 3 && $catch[0] instanceof Symbol &&
$catch[0]->getName() == 'catch' && $catch[1] instanceof Symbol) {
try {
return $this->eval($astData[1], $env, $depth + 1);
} catch (\Throwable $ex) {
if ($ex instanceof MadLispUserException) {
$exVal = $ex->getValue();
} else {
// Return a hash-map for PHP exceptions
$exVal = new Hash([
'type' => get_class($ex),
'file' => $ex->getFile(),
'line' => $ex->getLine(),
'message' => $ex->getMessage()
]);
}
$newEnv = new Env('catch', $env);
$newEnv->set($catch[1]->getName(), $exVal);
$env = $newEnv;
$ast = $catch[2];
continue; // tco
}
} else {
throw new MadLispException("invalid form for catch");
}
} elseif ($symbolName == 'undef') {
if ($astLength != 2) {
throw new MadLispException("undef requires exactly 1 argument");
}
if (!($astData[1] instanceof Symbol)) {
} elseif (!($astData[1] instanceof Symbol)) {
throw new MadLispException("first argument to undef is not symbol");
}