Interpreter V2 is designed to have a non-recursive run function. This should allow for an almost endless function call depth, constrained only by what can fit in memory.
It also makes stack traces easier to follow, as it will only ever be a few calls deep, as opposed to Interpreter V1, which recursively calls run over and over, resulting in huge JavaScript stack depths.
Like any good non-recursive function (example, a complete directory listing), lists are used to keep track of the current operation.
However, we must also keep track of several levels of state:
* The current opchain
* The current instruction
* Satisfying the parameters of a function call:
** For each parameter, we must satisfy it, and keep track of it so
that we can pass it back as a parameter value to be used in the
function call.
** However, each parameter may be:
*** An atom
*** A literal value
*** A function call, which probably has parameters to satisfy
like the above.
* Calling the function once parameters are satisfied:
** For FunctionDefinitionNative, we can simply call it and be done.
** For FunctionDefinition, we must create a new opchain and and set
it as the current opchain to run (saving the current state.)
** Keeping track of the return values so that either run finally
exits and returns a value, or assigns it to the correct parameter
name and calls a function once the parameters for it are set.
Keeping track of all of this state so that nothing is recursive is a difficult task.
Additionally, the interpreter needs to make available .invoke_functioncall and .get_param_value. This could be done by using Tuple return values so that run can push state and get the param value or invoke the function call and return it. This part could be done using run.