diff --git a/modern/src/App.js b/modern/src/App.js index f7d55cbb..2033a563 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -5,7 +5,7 @@ import * as Utils from "./utils"; import initialize from "./initialize"; import System from "./runtime/System"; import runtime from "./runtime"; -import interpret from "./maki-interpreter/interpreter"; +import { run } from "./maki-interpreter/virtualMachine"; // import simpleSkin from "../skins/simple.wal"; import cornerSkin from "../skins/CornerAmp_Redux.wal"; @@ -315,7 +315,7 @@ function App() { React.useEffect(() => { getSkin().then(async root => { // Execute scripts - await Utils.asyncTreeFlatMap(root, async node => { + await Utils.asyncTreeFlatMap(root, node => { switch (node.name) { case "groupdef": { // removes groupdefs from consideration (only run scripts when actually referenced by group) @@ -332,7 +332,7 @@ function App() { "WasabiXML", ]); const system = new System(scriptGroup); - await interpret({ + run({ runtime, data: node.js_annotations.script, system, diff --git a/modern/src/debugger/index.js b/modern/src/debugger/index.js index 07c8143b..1b465eae 100644 --- a/modern/src/debugger/index.js +++ b/modern/src/debugger/index.js @@ -1,5 +1,5 @@ import React from "react"; -import run from "../maki-interpreter/interpreter"; +import { run } from "../maki-interpreter/virtualMachine"; import System from "../runtime/System"; import runtime from "../runtime"; import Variable from "./Variable"; diff --git a/modern/src/maki-interpreter/interpreter.js b/modern/src/maki-interpreter/interpreter.js index 3a6e5a6d..2bdd2105 100644 --- a/modern/src/maki-interpreter/interpreter.js +++ b/modern/src/maki-interpreter/interpreter.js @@ -1,65 +1,308 @@ -import parse from "./parser"; -import { getClass, getFormattedId } from "./objects"; -import interpret from "./virtualMachine"; +import Variable from "./variable"; -// 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) { - val = gen.next(); - } - return val.value; -} - -function main({ runtime, data, system, log, debugHandler }) { - const program = parse(data); - - // Replace class hashes with actual JavaScript classes from the runtime - program.classes = program.classes.map(hash => { - const resolved = runtime[hash]; - if (resolved == null && log) { - const klass = getClass(hash); - console.warn( - `Class missing from runtime: ${hash}`, - klass == null - ? `(formatted ID: ${getFormattedId(hash)})` - : `expected ${klass.name}` - ); +function coerceTypes(var1, var2, val1, val2) { + if (var2.type === "INT") { + if (var1.type === "FLOAT" || var1.type === "DOUBLE") { + return Math.floor(val1); } - return resolved; - }); + } - const handler = debugHandler || runGeneratorUntilReturn; - - // Bind top level hooks. - program.bindings.forEach(binding => { - const { commandOffset, variableOffset, methodOffset } = binding; - const variable = program.variables[variableOffset]; - const method = program.methods[methodOffset]; - // const logger = log ? printCommand : logger; - // TODO: Handle disposing of this. - // TODO: Handle passing in variables. - variable.hook(method.name, (...args) => { - // 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, args.reverse())); - }); - }); - - const { commands, variables } = program; - commands.forEach((command, i) => { - // printCommand({ i, command, stack: [], variables }); - }); - - // Set the System global - // TODO: We could confirm that this variable has the "system" flag set. - program.variables[0].setValue(system); - system.js_start(); + return val1; } -export default main; +// 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()`. +export function* interpret(start, program, stack = []) { + const { commands, methods, variables, classes } = program; + + function popStackValue() { + const v = stack.pop(); + return v instanceof Variable ? v.getValue() : v; + } + + function twoArgCoercingOperator(operator) { + const a = stack.pop(); + const b = stack.pop(); + let aValue = a instanceof Variable ? a.getValue() : a; + const bValue = b instanceof Variable ? b.getValue() : b; + + aValue = coerceTypes(a, b, aValue, bValue); + stack.push(operator(bValue, aValue)); + } + + function twoArgOperator(operator) { + const aValue = popStackValue(); + const bValue = popStackValue(); + + stack.push(operator(bValue, aValue)); + } + + // Instruction Pointer + let ip = start; + while (ip < commands.length) { + const command = commands[ip]; + // 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: ip, command, stack, variables, commands }; + + switch (command.opcode) { + // push + case 1: { + const offsetIntoVariables = command.arg; + stack.push(variables[offsetIntoVariables]); + break; + } + // pop + case 2: { + stack.pop(); + break; + } + // popTo + case 3: { + const aValue = popStackValue(); + const offsetIntoVariables = command.arg; + const toVar = variables[offsetIntoVariables]; + toVar.setValue(aValue); + break; + } + // == + case 8: { + twoArgCoercingOperator((b, a) => b === a); + break; + } + // != + case 9: { + twoArgCoercingOperator((b, a) => b !== a); + break; + } + // > + case 10: { + twoArgCoercingOperator((b, a) => b > a); + break; + } + // >= + case 11: { + twoArgCoercingOperator((b, a) => b >= a); + break; + } + // < + case 12: { + twoArgCoercingOperator((b, a) => b < a); + break; + } + // <= + case 13: { + twoArgCoercingOperator((b, a) => b <= a); + break; + } + // jumpIf + case 16: { + const value = popStackValue(); + // This seems backwards. Seems like we're doing a "jump if not" + if (value) { + break; + } + ip = command.arg - 1; + break; + } + // jumpIfNot + case 17: { + const value = popStackValue(); + // This seems backwards. Same as above + if (!value) { + break; + } + ip = command.arg - 1; + break; + } + // jump + case 18: { + ip = command.arg - 1; + break; + } + // call + // strangeCall (seems to behave just like regular call) + case 24: + case 112: { + const methodOffset = command.arg; + let { name: methodName, typeOffset: classesOffset } = methods[ + methodOffset + ]; + methodName = methodName.toLowerCase(); + + const klass = classes[classesOffset]; + if (!klass) { + throw new Error("Need to add a missing class to runtime"); + } + // This is a bit awkward. Because the variables are stored on the stack + // before the object, we have to find the number of arguments without + // actually having access to the object instance. + let argCount = klass.prototype[methodName].length; + + const methodArgs = []; + while (argCount--) { + const aValue = popStackValue(); + methodArgs.push(aValue); + } + const obj = popStackValue(); + stack.push(obj[methodName](...methodArgs)); + break; + } + // 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; + } + // return + case 33: { + const aValue = popStackValue(); + return aValue; + } + // mov + case 48: { + const a = stack.pop(); + const b = stack.pop(); + let aValue = a instanceof Variable ? a.getValue() : a; + if (b.type === "INT") { + aValue = Math.floor(aValue); + } + b.setValue(aValue); + stack.push(aValue); + break; + } + // postinc + case 56: { + const a = stack.pop(); + const aValue = a.getValue(); + a.setValue(aValue + 1); + stack.push(aValue); + break; + } + // postdec + case 57: { + const a = stack.pop(); + const aValue = a.getValue(); + a.setValue(aValue - 1); + stack.push(aValue); + break; + } + // preinc + case 58: { + const a = stack.pop(); + const aValue = a.getValue() + 1; + a.setValue(aValue); + stack.push(aValue); + break; + } + // predec + case 59: { + const a = stack.pop(); + const aValue = a.getValue() - 1; + a.setValue(aValue); + stack.push(aValue); + break; + } + // + (add) + case 64: { + twoArgOperator((b, a) => b + a); + break; + } + // - (subtract) + case 65: { + twoArgOperator((b, a) => b - a); + break; + } + // * (multiply) + case 66: { + twoArgOperator((b, a) => b * a); + break; + } + // / (divide) + case 67: { + twoArgOperator((b, a) => b / a); + break; + } + // % (mod) + case 68: { + const a = stack.pop(); + const b = stack.pop(); + const aValue = a instanceof Variable ? a.getValue() : a; + let bValue = b instanceof Variable ? b.getValue() : b; + // Need to coerce LHS if not int, RHS is always int (enforced by compiler) + if (b.type === "FLOAT" || b.type === "DOUBLE") { + bValue = Math.floor(bValue); + } + stack.push(bValue % aValue); + break; + } + // & (binary and) + case 72: { + twoArgOperator((b, a) => b & a); + break; + } + // | (binary or) + case 73: { + twoArgOperator((b, a) => b | a); + break; + } + // ! (not) + case 74: { + const aValue = popStackValue(); + stack.push(aValue ? 0 : 1); + break; + } + // - (negative) + case 76: { + const aValue = popStackValue(); + stack.push(-aValue); + break; + } + // logAnd (&&) + case 80: { + twoArgOperator((b, a) => b && a); + break; + } + // logOr || + case 81: { + twoArgOperator((b, a) => b || a); + break; + } + // << + case 88: { + twoArgOperator((b, a) => b << a); + break; + } + // >> + case 89: { + twoArgOperator((b, a) => b >> a); + break; + } + // new + case 96: { + const classesOffset = command.arg; + const Klass = classes[classesOffset]; + const klassInst = new Klass(); + stack.push(klassInst); + break; + } + default: + throw new Error(`Unhandled opcode ${command.opcode}`); + } + + ip++; + } +} diff --git a/modern/src/maki-interpreter/interpreter.test.js b/modern/src/maki-interpreter/interpreter.test.js deleted file mode 100644 index a7ca36fd..00000000 --- a/modern/src/maki-interpreter/interpreter.test.js +++ /dev/null @@ -1,163 +0,0 @@ -import { readFileSync } from "fs"; -import { join } from "path"; -import System from "../runtime/System"; -import runtime from "../runtime"; -import interpret from "./interpreter"; -import { VERSIONS } from "./testConstants"; - -function runFile(version, fileName) { - const relativePath = `../../resources/maki_compiler/${version}/${fileName}`; - const system = new System(); - const data = readFileSync(join(__dirname, relativePath)); - interpret({ runtime, data, system, log: false }); -} - -// The basicTest.m file that jberg prepared follows a convention for what a -// passing test looks like. This ultility function lets us just write the -// message, since the rest is common for all success messages. -function successOutputFromMessage(message) { - return [message, "Success", 0, ""]; -} - -let mockMessageBox; -beforeEach(() => { - mockMessageBox = jest.fn(); - // The VM depends upon the arity of this function, so we can't use - // `mockMessageBox` directly. - System.prototype.messagebox = function(a, b, c, d) { - mockMessageBox(...arguments); - }; -}); - -describe("can call messageBox with hello World", () => { - const versions = [ - // VERSIONS.WINAMP_3_ALPHA, - VERSIONS.WINAMP_3_BETA, - VERSIONS.WINAMP_3_FULL, - VERSIONS.WINAMP_5_02, - VERSIONS.WINAMP_5_66, - ]; - versions.forEach(version => { - test(`with bytecode compiled by ${version}`, () => { - runFile(version, "hello_world.maki"); - expect(mockMessageBox).toHaveBeenCalledTimes(1); - expect(mockMessageBox).toHaveBeenCalledWith( - "Hello World", - "Hello Title", - 1, - null - ); - }); - }); -}); - -describe("can use basic operators", () => { - const versions = [ - // jberg could not get the script to compile on this version - // VERSIONS.WINAMP_3_ALPHA, - VERSIONS.WINAMP_3_BETA, - VERSIONS.WINAMP_3_FULL, - VERSIONS.WINAMP_5_02, - VERSIONS.WINAMP_5_66, - ]; - - versions.forEach(version => { - test(`with basic test bytecode compiled by ${version}`, () => { - runFile(version, "basicTests.maki"); - expect(mockMessageBox.mock.calls).toEqual( - [ - "2 + 2 = 4", - "2.2 + 2.2 = 4.4", - "4 + 4.4 = 4.4 + 4 (not implict casting)", - "#t + #t = 2", - "3 - 2 = 1", - "3 - -2 = 5", - "3.5 - 2 = 1.5", - "2 * 3 = 6", - "2 * 1.5 = 3", - "#t * 3 = 3", - "#f * 3 = 0", - "#t * 0.25 = 0.25", - "0.25 * #t = 0.25", - "#f * 0.25 = 0", - "6 / 3 = 2", - "3 / 2 = 1.5", - "5 % 2 = 1", - "5.5 % 2 = 1 (implict casting)", - "3 & 2 = 2", - "3 | 2 = 3", - "2 << 1 = 4", - "4 >> 1 = 2", - "2.5 << 1 = 4 (implict casting)", - "4.5 >> 1 = 2 (implict casting)", - "1 != 2", - "1 < 2", - "2 > 1", - "[int] 4 = [float] 4.4 (autocasting types)", - "! [float] 4.4 = [int] 4 (not autocasting types)", - "[float] 4.4 != [int] 4 (not autocasting types)", - "! [int] 4 != [float] 4.4 (autocasting types)", - "[int] 4 <= [float] 4.4 (autocasting types)", - "[int] 4 >= [float] 4.4 (autocasting types)", - "! [float] 4.4 <= [int] 4 (not autocasting types)", - "[float] 4.4 >= [int] 4 (not autocasting types)", - "! [int] 4 < [float] 4.4 (autocasting types)", - "! [float] 4.4 < [int] 4 (not autocasting types)", - "! [int] 4 > [float] 4.4 (autocasting types)", - "[float] 4.4 > [int] 4 (not autocasting types)", - "1++ = 1", - "1++ (after incremeent) = 2", - "2-- = 2", - "2-- (after decrement) = 1", - "++1 = 2", - "!#f", - "!0", - "!1 == #f", - "1 == #t", - "0 == #f", - "#t && #t", - "!(#t && #f)", - "!(#f && #f)", - "#t || #t", - "#t || #f", - "#f || #t", - "!(#f || #f)", - "#t || ++n (doesn't short circuit)", - "!(#f && ++ n) (doesn't short circuit)", - ].map(successOutputFromMessage) - ); - }); - }); -}); - -describe("can use simple functions", () => { - const versions = [ - // jberg could not get the script to compile on this version - // VERSIONS.WINAMP_3_ALPHA, - VERSIONS.WINAMP_3_BETA, - VERSIONS.WINAMP_3_FULL, - VERSIONS.WINAMP_5_02, - VERSIONS.WINAMP_5_66, - ]; - - versions.forEach(version => { - test(`with bytecode compiled by ${version}`, () => { - runFile(version, "simpleFunctions.maki"); - expect(mockMessageBox.mock.calls).toEqual( - [ - "simple custom function", - "simple custom function with implicit cast", - "fib(2) == 0 (should be 1)", - "fib(3) == -3 (should be 2)", - "fib(9) == -63 (should be 34)", - "fib(20) == -360 (should be 6765)", - "global num == 0", - "shadow num == 5", - "localNum == 20", - "global num == 0", - "localNum == 20", - ].map(successOutputFromMessage) - ); - }); - }); -}); diff --git a/modern/src/maki-interpreter/virtualMachine.js b/modern/src/maki-interpreter/virtualMachine.js index 8527da1f..4e6116c4 100644 --- a/modern/src/maki-interpreter/virtualMachine.js +++ b/modern/src/maki-interpreter/virtualMachine.js @@ -1,310 +1,63 @@ -import Variable from "./variable"; +import parse from "./parser"; +import { getClass, getFormattedId } from "./objects"; +import { interpret } from "./interpreter"; -function coerceTypes(var1, var2, val1, val2) { - if (var2.type === "INT") { - if (var1.type === "FLOAT" || var1.type === "DOUBLE") { - return Math.floor(val1); - } +// 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) { + val = gen.next(); } - - return val1; + return val.value; } -// 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; +export function run({ runtime, data, system, log, debugHandler }) { + const program = parse(data); - function popStackValue() { - const v = stack.pop(); - return v instanceof Variable ? v.getValue() : v; - } - - function twoArgCoercingOperator(operator) { - const a = stack.pop(); - const b = stack.pop(); - let aValue = a instanceof Variable ? a.getValue() : a; - const bValue = b instanceof Variable ? b.getValue() : b; - - aValue = coerceTypes(a, b, aValue, bValue); - stack.push(operator(bValue, aValue)); - } - - function twoArgOperator(operator) { - const aValue = popStackValue(); - const bValue = popStackValue(); - - stack.push(operator(bValue, aValue)); - } - - // Instruction Pointer - let ip = start; - while (ip < commands.length) { - const command = commands[ip]; - // 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: ip, command, stack, variables, commands }; - - switch (command.opcode) { - // push - case 1: { - const offsetIntoVariables = command.arg; - stack.push(variables[offsetIntoVariables]); - break; - } - // pop - case 2: { - stack.pop(); - break; - } - // popTo - case 3: { - const aValue = popStackValue(); - const offsetIntoVariables = command.arg; - const toVar = variables[offsetIntoVariables]; - toVar.setValue(aValue); - break; - } - // == - case 8: { - twoArgCoercingOperator((b, a) => b === a); - break; - } - // != - case 9: { - twoArgCoercingOperator((b, a) => b !== a); - break; - } - // > - case 10: { - twoArgCoercingOperator((b, a) => b > a); - break; - } - // >= - case 11: { - twoArgCoercingOperator((b, a) => b >= a); - break; - } - // < - case 12: { - twoArgCoercingOperator((b, a) => b < a); - break; - } - // <= - case 13: { - twoArgCoercingOperator((b, a) => b <= a); - break; - } - // jumpIf - case 16: { - const value = popStackValue(); - // This seems backwards. Seems like we're doing a "jump if not" - if (value) { - break; - } - ip = command.arg - 1; - break; - } - // jumpIfNot - case 17: { - const value = popStackValue(); - // This seems backwards. Same as above - if (!value) { - break; - } - ip = command.arg - 1; - break; - } - // jump - case 18: { - ip = command.arg - 1; - break; - } - // call - // strangeCall (seems to behave just like regular call) - case 24: - case 112: { - const methodOffset = command.arg; - let { name: methodName, typeOffset: classesOffset } = methods[ - methodOffset - ]; - methodName = methodName.toLowerCase(); - - const klass = classes[classesOffset]; - if (!klass) { - throw new Error("Need to add a missing class to runtime"); - } - // This is a bit awkward. Because the variables are stored on the stack - // before the object, we have to find the number of arguments without - // actually having access to the object instance. - let argCount = klass.prototype[methodName].length; - - const methodArgs = []; - while (argCount--) { - const aValue = popStackValue(); - methodArgs.push(aValue); - } - const obj = popStackValue(); - stack.push(obj[methodName](...methodArgs)); - break; - } - // 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; - } - // return - case 33: { - const aValue = popStackValue(); - return aValue; - } - // mov - case 48: { - const a = stack.pop(); - const b = stack.pop(); - let aValue = a instanceof Variable ? a.getValue() : a; - if (b.type === "INT") { - aValue = Math.floor(aValue); - } - b.setValue(aValue); - stack.push(aValue); - break; - } - // postinc - case 56: { - const a = stack.pop(); - const aValue = a.getValue(); - a.setValue(aValue + 1); - stack.push(aValue); - break; - } - // postdec - case 57: { - const a = stack.pop(); - const aValue = a.getValue(); - a.setValue(aValue - 1); - stack.push(aValue); - break; - } - // preinc - case 58: { - const a = stack.pop(); - const aValue = a.getValue() + 1; - a.setValue(aValue); - stack.push(aValue); - break; - } - // predec - case 59: { - const a = stack.pop(); - const aValue = a.getValue() - 1; - a.setValue(aValue); - stack.push(aValue); - break; - } - // + (add) - case 64: { - twoArgOperator((b, a) => b + a); - break; - } - // - (subtract) - case 65: { - twoArgOperator((b, a) => b - a); - break; - } - // * (multiply) - case 66: { - twoArgOperator((b, a) => b * a); - break; - } - // / (divide) - case 67: { - twoArgOperator((b, a) => b / a); - break; - } - // % (mod) - case 68: { - const a = stack.pop(); - const b = stack.pop(); - const aValue = a instanceof Variable ? a.getValue() : a; - let bValue = b instanceof Variable ? b.getValue() : b; - // Need to coerce LHS if not int, RHS is always int (enforced by compiler) - if (b.type === "FLOAT" || b.type === "DOUBLE") { - bValue = Math.floor(bValue); - } - stack.push(bValue % aValue); - break; - } - // & (binary and) - case 72: { - twoArgOperator((b, a) => b & a); - break; - } - // | (binary or) - case 73: { - twoArgOperator((b, a) => b | a); - break; - } - // ! (not) - case 74: { - const aValue = popStackValue(); - stack.push(aValue ? 0 : 1); - break; - } - // - (negative) - case 76: { - const aValue = popStackValue(); - stack.push(-aValue); - break; - } - // logAnd (&&) - case 80: { - twoArgOperator((b, a) => b && a); - break; - } - // logOr || - case 81: { - twoArgOperator((b, a) => b || a); - break; - } - // << - case 88: { - twoArgOperator((b, a) => b << a); - break; - } - // >> - case 89: { - twoArgOperator((b, a) => b >> a); - break; - } - // new - case 96: { - const classesOffset = command.arg; - const Klass = classes[classesOffset]; - const klassInst = new Klass(); - stack.push(klassInst); - break; - } - default: - throw new Error(`Unhandled opcode ${command.opcode}`); + // Replace class hashes with actual JavaScript classes from the runtime + program.classes = program.classes.map(hash => { + const resolved = runtime[hash]; + if (resolved == null && log) { + const klass = getClass(hash); + console.warn( + `Class missing from runtime: ${hash}`, + klass == null + ? `(formatted ID: ${getFormattedId(hash)})` + : `expected ${klass.name}` + ); } + return resolved; + }); - ip++; - } + const handler = debugHandler || runGeneratorUntilReturn; + + // Bind top level hooks. + program.bindings.forEach(binding => { + const { commandOffset, variableOffset, methodOffset } = binding; + const variable = program.variables[variableOffset]; + const method = program.methods[methodOffset]; + // const logger = log ? printCommand : logger; + // 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, [])); + }); + }); + + const { commands, variables } = program; + commands.forEach((command, i) => { + // printCommand({ i, command, stack: [], variables }); + }); + + // Set the System global + // TODO: We could confirm that this variable has the "system" flag set. + program.variables[0].setValue(system); + system.js_start(); } - -export default interpret;