mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 01:29:22 +00:00
Clean up and document the generator approach
This commit is contained in:
parent
2a3be1656b
commit
a3b7527f71
2 changed files with 25 additions and 4 deletions
|
|
@ -1,8 +1,10 @@
|
|||
const parse = require("./parser");
|
||||
const { getClass, getFormattedId } = require("./objects");
|
||||
const interpret = require("./virtualMachine");
|
||||
const { printCommand } = require("./prettyPrinter");
|
||||
|
||||
// Note: if this incurs a performance overhead, we could pass a flag into the VM
|
||||
// to not yield in production. In that case, we would never even enter the
|
||||
// `while` loop.
|
||||
function runGeneratorUntilReturn(gen) {
|
||||
let val = gen.next();
|
||||
while (!val.done) {
|
||||
|
|
@ -40,6 +42,11 @@ function main({ runtime, data, system, log, debugHandler }) {
|
|||
// TODO: Handle disposing of this.
|
||||
// TODO: Handle passing in variables.
|
||||
variable.hook(method.name, () => {
|
||||
// Interpret is a generator that yields before each command is exectued.
|
||||
// `handler` is reponsible for `.next()`ing until the program execution is
|
||||
// complete (the generator is "done"). In production this is done
|
||||
// synchronously. In the debugger, if execution is paused, it's done
|
||||
// async.
|
||||
handler(interpret(commandOffset, program, []));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@ function coerceTypes(var1, var2, val1, val2) {
|
|||
return val1;
|
||||
}
|
||||
|
||||
// Note: We implement `interpret` as a generator in order to support pausing
|
||||
// execution in the debugger. This allows the caller granular control over the
|
||||
// execution timing of the virtual machine while also allowing the VM to divulge
|
||||
// some of its internal state before each command is exectued.
|
||||
//
|
||||
// In "production" we syncrnously call `.next()` on the generator until it's
|
||||
// empty, ignoring all yeilded values. In the debugger we use the yielded data
|
||||
// to populate the UI, and -- when stepping -- pause execution by waiting
|
||||
// between calls to `.next()`.
|
||||
function* interpret(start, program, stack = []) {
|
||||
const { commands, methods, variables, classes } = program;
|
||||
|
||||
|
|
@ -35,9 +44,11 @@ function* interpret(start, program, stack = []) {
|
|||
let i = start;
|
||||
while (i < commands.length) {
|
||||
const command = commands[i];
|
||||
{
|
||||
yield { i, command, stack, variables, commands };
|
||||
}
|
||||
// This probably incurrs some perf cost. If it does, we can pass in a flag
|
||||
// to enable it only when we are debugging. When we are not, (in prod) we
|
||||
// can just jump right over it and we will execute straight through to the
|
||||
// return value on the first call to `.next()`.
|
||||
yield { i, command, stack, variables, commands };
|
||||
|
||||
switch (command.opcode) {
|
||||
// push
|
||||
|
|
@ -141,6 +152,9 @@ function* interpret(start, program, stack = []) {
|
|||
// callGlobal
|
||||
case 25: {
|
||||
const offset = command.arg;
|
||||
// Note: We proxy all yielded values from the child `interpret` out to
|
||||
// the caller, while capturing the return value, (`value`) for use within the
|
||||
// VM.
|
||||
const value = yield* interpret(offset, program, stack);
|
||||
stack.push(value);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue