It’s now possible to create and run Worst interpreters from within.
Also, raising an error doesn’t stop execution.
It pauses the interpreter – even when in the middle of a Lua function –
and passes an error value back to the outer interpreter for it to deal with
as it pleases.
Resuming the interpreter will then continue as if nothing happened.
Of course, there’s nothing forcing it to use error values specifically,
pause is available to do the same with arbitrary values.
The interactive environment now takes advantage of these additions,
resulting in a simpler implementation with nicer whitespace handling
that lets you know when more code is required by
Run an interpreter with
interpreter-run. It will return false on completion,
or any other value (e.g. an error) whenever a pause is encountered.
A consequence is that you cannot pause with false, but that’s probably fine.
interpreter-empty interpreter-inherit-definitions ; (adding definitions one at a time is also an option) [ blah blah blah here is my code ... ] interpreter-body-set while [ interpreter-run false? if [ ; completed! yay ] [ ; some error or whatever ; maybe do something useful with it ->string print ; exit loop in this example anyway #f ] ] 
Errors are “resumable” here because you can catch them and then
pretend they never happened.
For example, the
stack-empty error is resumable by putting something on the
interpreter’s stack. Then just run it again, and the code won’t realise
the stack was ever empty.
(If you don’t put anything on the stack, it will pause again with the same error.)
A catalogue of built-in errors and how to deal with them is planned.
Lua coroutines is how. Pausing is basically just
and the interpreter stack frames keep track of paused coroutines.
Running the interpreter checks for coroutines to resume
before stepping into any new code. That’s all there is to it.
There may well be a performance impact in using coroutines for everything
pcall. No idea!
Meanwhile, creating interpreters is not a big deal, as they are quite simple,
and running them is identical to running the main interpreter.
To what end?
Many ends may be whatted with pausable interpreters. CIL, the work-in-progress compiler/interpreter library, uses a fresh interpreter as a controlled environment, and pausing to interact with the unique symbol counter and current level of indentation.
But what about also perhaps:
- Regular ol’ throw/catch exceptions, which simply abandons the inner interpreter on error.
- List comprehensions, lazy sequence generators, asynchronous IO, coroutines, and other constructs that need to keep state around in order to produce values or otherwise do stuff.
- DSLs with their own semantics, interacting with regular code.
- Security boundaries. Don’t want to let some code access files? No problem, run it in an interpreter without any filesystem stuff defined. Want to add some kind of execution limit? Make a new interpreter and wrap every definition in something that pauses in order to decrement a counter.
It may be that no user code will ever use subinterpreters or pausing directly, but they should enable some neat stuff.