diff --git a/experiments/modern/src/maki-interpreter/interpreter.js b/experiments/modern/src/maki-interpreter/interpreter.js index fc3535c1..56e7f450 100644 --- a/experiments/modern/src/maki-interpreter/interpreter.js +++ b/experiments/modern/src/maki-interpreter/interpreter.js @@ -1,6 +1,7 @@ const parse = require("./parser"); const { getClass, getFormattedId } = require("./objects"); const interpret = require("./virtualMachine"); +const { printCommand } = require("./prettyPrinter"); function main({ runtime, data, system, log }) { const program = parse(data); @@ -32,6 +33,11 @@ function main({ runtime, data, system, log }) { }); }); + 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); diff --git a/experiments/modern/src/maki-interpreter/interpreter.test.js b/experiments/modern/src/maki-interpreter/interpreter.test.js index 01980e77..48619a0c 100644 --- a/experiments/modern/src/maki-interpreter/interpreter.test.js +++ b/experiments/modern/src/maki-interpreter/interpreter.test.js @@ -4,10 +4,18 @@ const System = require("./runtime/System"); const runtime = require("./runtime"); const interpret = require("./interpreter"); +const VERSIONS = { + WINAMP_3_ALPHA: "v1.1.0.a9 (Winamp 3 alpha 8r)", + WINAMP_3_BETA: "v1.1.1.b3 (Winamp 3.0 build 488d)", + WINAMP_3_FULL: "v1.1.1.b3 (Winamp 3.0 full)", + WINAMP_5_02: "v1.1.13 (Winamp 5.02)", + WINAMP_5_66: "v1.2.0 (Winamp 5.66)", +}; + function runFile(relativePath) { const system = new System(); const data = fs.readFileSync(path.join(__dirname, relativePath)); - interpret({ runtime, data, system, log: false }); + interpret({ runtime, data, system, log: true }); } let mockMessageBox; @@ -22,11 +30,11 @@ beforeEach(() => { describe("can call messageBox with hello World", () => { const versions = [ - // "v1.1.0.a9 (Winamp 3 alpha 8r)", - "v1.1.1.b3 (Winamp 3.0 build 488d)", - "v1.1.1.b3 (Winamp 3.0 full)", - // "v1.1.13 (Winamp 5.02)", - // "v1.2.0 (Winamp 5.66)" + // VERSIONS.WINAMP_3_ALPHA, + VERSIONS.WINAMP_3_BETA, + VERSIONS.WINAMP_3_FULL, + // VERSIONS.WINAMP_5_22, + // VERSIONS.WINAMP_5.66 ]; versions.forEach(version => { test(`with bytecode compiled by ${version}`, () => { @@ -41,3 +49,35 @@ describe("can call messageBox with hello World", () => { }); }); }); + +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_22, + // VERSIONS.WINAMP_5.66 + ]; + + // 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, ""]; + } + + versions.forEach(version => { + test.only(`with bytecode compiled by ${version}`, () => { + try { + runFile(`./reference/maki_compiler/${version}/basicTests.maki`); + } catch (e) { + // Uncomment this next line to find the next bug to work on. + console.error(e); + } + expect(mockMessageBox.mock.calls).toEqual( + ["2 + 2 = 4"].map(successOutputFromMessage) + ); + }); + }); +}); diff --git a/experiments/modern/src/maki-interpreter/parser.js b/experiments/modern/src/maki-interpreter/parser.js index 03e5f16f..c741443f 100644 --- a/experiments/modern/src/maki-interpreter/parser.js +++ b/experiments/modern/src/maki-interpreter/parser.js @@ -206,17 +206,20 @@ function decodeCode({ makiFile, classes, variables, methods }) { const commands = []; while (makiFile.getPosition() < start + length) { const pos = makiFile.getPosition() - start; - commands.push(parseComand({ makiFile, length, pos, localFunctions })); + commands.push( + parseComand({ start, makiFile, length, pos, localFunctions }) + ); } return { commands, localFunctions }; } // TODO: Refactor this to consume bytes directly off the end of MakiFile -function parseComand({ makiFile, length, pos, localFunctions }) { +function parseComand({ start, makiFile, length, pos, localFunctions }) { const opcode = makiFile.readUInt8(); const command = { offset: pos, + start, pos, opcode, // TODO: This should just be a single nullable value I think @@ -241,7 +244,7 @@ function parseComand({ makiFile, length, pos, localFunctions }) { break; } case "line": { - arg = argValue + 5; + arg = argValue + 5 + pos; break; } case "objFunc": { @@ -345,6 +348,7 @@ function parse(buffer) { localFunctions, version, extraVersion, + offsetToCommand, }; } diff --git a/experiments/modern/src/maki-interpreter/prettyPrinter.js b/experiments/modern/src/maki-interpreter/prettyPrinter.js index d73c8950..f60e0641 100644 --- a/experiments/modern/src/maki-interpreter/prettyPrinter.js +++ b/experiments/modern/src/maki-interpreter/prettyPrinter.js @@ -24,6 +24,15 @@ function printValue(value) { case "INT": type = "INT"; break; + case "FLOAT": + type = "FLOAT"; + break; + case "DOUBLE": + type = "DOUBLE"; + break; + case "BOOLEAN": + type = "BOOLEAN"; + break; default: throw new Error(`Unknown variable type ${variable.typeName}`); } @@ -32,7 +41,7 @@ function printValue(value) { function printCommand({ i, command, stack, variables }) { console.log( - i, + `${i} (${command.start} + ${command.offset})`, command.command.name.toUpperCase(), command.opcode, command.arguments.map(offset => { diff --git a/experiments/modern/src/maki-interpreter/virtualMachine.js b/experiments/modern/src/maki-interpreter/virtualMachine.js index d2f7fe2d..a802e803 100644 --- a/experiments/modern/src/maki-interpreter/virtualMachine.js +++ b/experiments/modern/src/maki-interpreter/virtualMachine.js @@ -1,12 +1,14 @@ const { printCommand } = require("./prettyPrinter"); function interpret(start, program, { log = false }) { - const { commands, methods, variables, classes } = program; + const { commands, methods, variables, classes, offsetToCommand } = program; + // Run all the commands that are safe to run. Increment this number to find // the next bug. const stack = []; - let i = start; + let i = start - 1; while (i < commands.length) { + i++; const command = commands[i]; switch (command.opcode) { @@ -21,6 +23,32 @@ function interpret(start, program, { log = false }) { stack.pop(); break; } + // == + case 8: { + const a = stack.pop(); + const b = stack.pop(); + stack.push(a.getValue() === b.getValue()); + break; + } + // jumpIf + case 16: { + const value = stack.pop(); + // This seems backwards. Seems like we're doing a "jump if not" + if (value) { + break; + } + const offset = command.arguments[0]; + const nextCommandIndex = offsetToCommand[offset]; + i = nextCommandIndex; + break; + } + // jump + case 18: { + const offset = command.arguments[0]; + const nextCommandIndex = offsetToCommand[offset]; + i = nextCommandIndex; + break; + } // call case 24: { const methodOffset = command.arguments[0]; @@ -55,6 +83,20 @@ function interpret(start, program, { log = false }) { stack.push(a); break; } + // + (add) + case 64: { + const a = stack.pop(); + const b = stack.pop(); + stack.push(a.getValue() + b.getValue()); + break; + } + // >> + case 88: { + const a = stack.pop(); + const b = stack.pop(); + stack.push(a >> b); + break; + } default: throw new Error(`Unhandled opcode ${command.opcode}`); } @@ -63,7 +105,6 @@ function interpret(start, program, { log = false }) { if (log) { printCommand({ i, command, stack, variables }); } - i++; } }