Q: Can we use fibers for error-handling?

The goal here is to avoid adding support for exception handling if we're already
going to support fibers. A potential bonus would be being able to have
restartable error-handling.

The general idea is that instead of putting code in a "try" block, you throw it
onto a new fiber. If an error occurs, that fiber is paused, and returns control
back to the spawning fiber. The parent fiber can then decipher the error and
either abandon the fiber, or try to fix the error and resume somehow.

The first question is what kinds of errors is this useful for. For things like
parsing strings where failure is common and error-handling needs to be
lightweight, I think using fibers is too heavy, both in performance and code.
A better answer there is to lean on dynamic typing and return null on parse
failure.

On the other hand, it might be nice to be able to resume here if the code that
provided the string is far away and you don't want to have to manually propagate
the error out.

Programmatic errors like invalid argument types should halt the fiber but the
programmer will not want to resume that at runtime. Using the mechanism here is
fine since it would then dump a stack trace, etc. But it won't take advantage
of resuming.

Resuming is probably useful for things like IO errors where the error can't be
easily predicted beforehand but where you may want to handle it gracefully. For
example, if a file can't be opened, the caller may want to wait a while and
try again.

--

After thinking about it, maybe resuming is a bridge too far. Erlang's model is
that a failure just kills the process. I'll note that Erlang does have try and
catch, though.

The goals for error-handling in a scripting language are:

0. Have simple semantics and implementation.

1. Make it easy for developers to track down programmatic errors so they can
   fix them. This means bugs like wrong argument types should fail immediately
   and loudly, and should provide context (a callstack) about where the error
   occurred.

2. For runtime errors like parsing an invalid string or opening a missing file,
   the program should be able to easily detect the error at handle it.

3. It *may* be useful for programmers to be able to trap all errors and try to
   keep the program alive, or at least log the error in a meaningful way. When
   you have user-defined scripts, or a lot of code, or code authored by
   non-technical people, it's nice if a failure in one part can be reported but
   not take down the entire system.

   Two close-at-hand examples:

   - The REPL. A bug in code in the REPL shouldn't kill the whole REPL session.

   - The test framework. In order to write tests in Wren that test programmatic
     runtime errors, we need to be able to detect them and output something.
     The test runner could just parse the error output when the entire process
     dies, but that means you can only have one error test per test file.

Given those, I'm thinking:

1. Programmatic errors take down the entire fiber and dump a callstack.
   Normally, they will also take down the parent fiber and so on until the
   entire program goes down.

2. Runtime errors return error codes (or null). Things like parsing a string to
   a number, etc. should just return an error that you are responsible for
   handling.

3. When handing off control to a fiber, there is a "guarded run" method that
   will run the fiber. If it fails with a programmatic error, the invoked fiber
   dies, but the parent does not. It gets the callstack and error as some sort
   of object it can poke at.
