diff --git a/experiments/modern/package.json b/experiments/modern/package.json
index f4a4288f..1a5d15a1 100644
--- a/experiments/modern/package.json
+++ b/experiments/modern/package.json
@@ -6,6 +6,7 @@
"jszip": "^3.2.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
+ "react-dropzone": "^10.1.5",
"react-scripts": "3.0.1",
"xml-js": "^1.6.11"
},
@@ -14,9 +15,9 @@
"build": "react-scripts build",
"test": "react-scripts test --no-watchman",
"eject": "react-scripts eject",
- "decompile": "cd src/maki-interpreter/reference/maki_decompiler_1.1 && perl mdc.pl ../../fixtures/standardframe.maki",
- "interpret": "node ./src/maki-interpreter/cli.js ./fixtures/standardframe.maki",
- "debug": "node --inspect-brk ./src/maki-interpreter/cli.js ./fixtures/standardframe.maki"
+ "decompile": "cd src/maki-interpreter/reference/maki_decompiler_1.1 && perl mdc.pl \"../../reference/maki_compiler/v1.1.1.b3 (Winamp 3.0 full)/basicTests.maki\"",
+ "interpret": "node ./src/maki-interpreter/cli.js \"./reference/maki_compiler/v1.1.13 (Winamp 5.02)/hello_world.maki\"",
+ "debug": "node --inspect-brk ./src/maki-interpreter/cli.js ./reference/maki_compiler/v1.1.13 (Winamp 5.02)/hello_world.maki"
},
"eslintConfig": {
"extends": "react-app"
diff --git a/experiments/modern/src/debugger/Command.js b/experiments/modern/src/debugger/Command.js
new file mode 100644
index 00000000..df422340
--- /dev/null
+++ b/experiments/modern/src/debugger/Command.js
@@ -0,0 +1,23 @@
+import React from "react";
+import Variable from "./Variable";
+export default function Command({ command, variables }) {
+ const arg = command.arguments[0];
+ let foo = null;
+ switch (command.opcode) {
+ case 1:
+ foo = (
+
+ Variable(
+ )
+
+ );
+ break;
+ default:
+ foo = null;
+ }
+ return (
+ <>
+ ({command.opcode}) {command.command.name.toUpperCase()} {foo}
+ >
+ );
+}
diff --git a/experiments/modern/src/debugger/Value.js b/experiments/modern/src/debugger/Value.js
new file mode 100644
index 00000000..78e46ddc
--- /dev/null
+++ b/experiments/modern/src/debugger/Value.js
@@ -0,0 +1,15 @@
+import React from "react";
+import VariableClass from "../maki-interpreter/variable";
+import Variable from "./Variable";
+
+export default function Value({ value }) {
+ if (value instanceof VariableClass) {
+ return (
+ <>
+ Variable(
+ )
+ >
+ );
+ }
+ return value ? value.toString() : null;
+}
diff --git a/experiments/modern/src/debugger/Variable.js b/experiments/modern/src/debugger/Variable.js
new file mode 100644
index 00000000..8029335a
--- /dev/null
+++ b/experiments/modern/src/debugger/Variable.js
@@ -0,0 +1,32 @@
+import runtime from "../maki-interpreter/runtime";
+
+function quote(str) {
+ return `"${str}"`;
+}
+
+export default function Variable({ variable }) {
+ let type = "UNKOWN";
+ switch (variable.typeName) {
+ case "OBJECT":
+ const obj = runtime[variable.type];
+ if (obj == null) {
+ type = "Unknown object";
+ } else {
+ type = obj.getClassName();
+ }
+ break;
+ case "STRING": {
+ const value = variable.getValue();
+ return `${variable.typeName}(${value == null ? "NULL" : quote(value)})`;
+ }
+ case "INT":
+ case "FLOAT":
+ case "DOUBLE":
+ case "BOOLEAN":
+ const value = variable.getValue();
+ return `${variable.typeName}(${value == null ? "NULL" : value})`;
+ default:
+ throw new Error(`Unknown variable type ${variable.typeName}`);
+ }
+ return type;
+}
diff --git a/experiments/modern/src/debugger/index.js b/experiments/modern/src/debugger/index.js
new file mode 100644
index 00000000..a89bd7f5
--- /dev/null
+++ b/experiments/modern/src/debugger/index.js
@@ -0,0 +1,177 @@
+import React from "react";
+import run from "../maki-interpreter/interpreter";
+import System from "../maki-interpreter/runtime/System";
+import runtime from "../maki-interpreter/runtime";
+import Variable from "./Variable";
+import Value from "./Value";
+import Command from "./Command";
+import { useDropzone } from "react-dropzone";
+
+function readFileAsArrayBuffer(file) {
+ return new Promise((resolve, reject) => {
+ const fileReader = new FileReader();
+ fileReader.onload = () => {
+ resolve(fileReader.result);
+ };
+ fileReader.onerror = reject;
+ fileReader.readAsArrayBuffer(file);
+ });
+}
+
+function Wrapper() {
+ const [maki, setMaki] = React.useState(null);
+ const onDrop = React.useCallback(async acceptedFiles => {
+ if (acceptedFiles.length !== 1) {
+ alert("Only drop one file at a time");
+ return;
+ }
+ const file = acceptedFiles[0];
+ const arrayBuffer = await readFileAsArrayBuffer(file);
+ setMaki(arrayBuffer);
+ // Do something with the files
+ }, []);
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
+ return (
+
+ {maki == null ? (
+ <>
+
+ Drop a .maki file here to debug
+
+
+ >
+ ) : (
+
+ )}
+
+ );
+}
+
+/*
+async function fetchTestScript() {
+ const response = await fetch(basicTestUrl);
+ return response.arrayBuffer();
+}
+*/
+
+function Debugger({ maki }) {
+ // This is all a huge disaster
+ const [commandOffset, setCommandOffset] = React.useState(0);
+ const [variables, setVariables] = React.useState(null);
+ const [stack, setStack] = React.useState(null);
+ const [commands, setCommands] = React.useState(null);
+ const [next, setNext] = React.useState(() => {});
+ const system = new System();
+
+ React.useEffect(() => {
+ run({
+ runtime,
+ data: maki,
+ system,
+ logger: ({ i, stack, program }) => {
+ const done = new Promise((resolve, reject) => {
+ // setState accepts a creator function, so we have to pass a function that returns our function.
+ setNext(() => resolve);
+ });
+ setVariables([...program.variables]);
+ setStack([...stack]);
+ setCommands([...program.commands]);
+ setCommandOffset(i);
+ return done;
+ },
+ });
+ }, []);
+
+ React.useEffect(() => {
+ function handler(e) {
+ switch (e.key) {
+ case ";":
+ next();
+ break;
+ default:
+ console.log(e.key);
+ }
+ }
+ document.addEventListener("keydown", handler);
+ return () => document.removeEventListener("keydown", handler);
+ });
+
+ return (
+
+
+
+
Commands
+
+
+ {commands != null &&
+ commands.map((command, i) => (
+
+ | {i} |
+
+
+ |
+
+ ))}
+
+
+
+
+
Stack
+
+
+ {stack != null &&
+ stack.map((value, i) => (
+
+ | {i} |
+
+
+ |
+
+ ))}
+
+
+
+
+
Variables
+
+
+ {variables != null &&
+ variables.map((variable, i) => (
+
+ | {i} |
+
+
+ |
+
+ ))}
+
+
+
+
+ );
+}
+
+export default Wrapper;
diff --git a/experiments/modern/src/index.js b/experiments/modern/src/index.js
index 87d1be55..77f0ccce 100644
--- a/experiments/modern/src/index.js
+++ b/experiments/modern/src/index.js
@@ -1,10 +1,14 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import * as serviceWorker from './serviceWorker';
+import React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+import App from "./App";
+import * as serviceWorker from "./serviceWorker";
+import Debugger from "./debugger";
-ReactDOM.render(, document.getElementById('root'));
+ReactDOM.render(
+ window.location.pathname === "/debugger" ? : ,
+ document.getElementById("root")
+);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
diff --git a/experiments/modern/src/maki-interpreter/interpreter.js b/experiments/modern/src/maki-interpreter/interpreter.js
index bb23b433..9e2e42d2 100644
--- a/experiments/modern/src/maki-interpreter/interpreter.js
+++ b/experiments/modern/src/maki-interpreter/interpreter.js
@@ -26,7 +26,7 @@ function main({ runtime, data, system, log, logger }) {
const { commandOffset, variableOffset, methodOffset } = binding;
const variable = program.variables[variableOffset];
const method = program.methods[methodOffset];
- const logger = log ? printCommand : null;
+ // const logger = log ? printCommand : logger;
// TODO: Handle disposing of this.
// TODO: Handle passing in variables.
variable.hook(method.name, () => {
diff --git a/experiments/modern/src/maki-interpreter/interpreter.test.js b/experiments/modern/src/maki-interpreter/interpreter.test.js
index f26cd5b7..69171698 100644
--- a/experiments/modern/src/maki-interpreter/interpreter.test.js
+++ b/experiments/modern/src/maki-interpreter/interpreter.test.js
@@ -76,7 +76,25 @@ describe("can use basic operators", () => {
console.error(e);
}
expect(mockMessageBox.mock.calls).toEqual(
- ["2 + 2 = 4", "2.2 + 2.2 = 4.4"].map(successOutputFromMessage)
+ [
+ "2 + 2 = 4",
+ "2.2 + 2.2 = 4.4",
+ "3 - 2 = 1",
+ "3 - -2 = 5",
+ "3.5 - 2 = 1.5",
+ "2 * 3 = 6",
+ "2 * 1.5 = 3",
+ "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)",
+ ].map(successOutputFromMessage)
);
});
});
diff --git a/experiments/modern/src/maki-interpreter/virtualMachine.js b/experiments/modern/src/maki-interpreter/virtualMachine.js
index 481b7fa9..d68abcbd 100644
--- a/experiments/modern/src/maki-interpreter/virtualMachine.js
+++ b/experiments/modern/src/maki-interpreter/virtualMachine.js
@@ -1,12 +1,13 @@
-function interpret(start, program, { logger = null }) {
+const Variable = require("./variable");
+
+async function interpret(start, program, { logger = null }) {
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 - 1;
+ let i = start;
while (i < commands.length) {
- i++;
const command = commands[i];
switch (command.opcode) {
@@ -25,7 +26,11 @@ function interpret(start, program, { logger = null }) {
case 8: {
const a = stack.pop();
const b = stack.pop();
- stack.push(a.getValue() === b.getValue());
+ // I'm suspicious about this. Should we really be storing both values
+ // and variables on the stack.
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(aValue === bValue);
break;
}
// jumpIf
@@ -88,20 +93,94 @@ function interpret(start, program, { logger = null }) {
stack.push(a.getValue() + b.getValue());
break;
}
- // >>
+ // - (subtract)
+ case 65: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue - aValue);
+ break;
+ }
+ // * (multiply)
+ case 66: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue * aValue);
+ break;
+ }
+ // / (divide)
+ case 67: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue / aValue);
+ break;
+ }
+ // % (mod)
+ case 68: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue % aValue);
+ break;
+ }
+ // & (binary and)
+ case 72: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue & aValue);
+ break;
+ }
+ // | (binary or)
+ case 73: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue | aValue);
+ break;
+ }
+ // - (negative)
+ case 76: {
+ const a = stack.pop();
+ stack.push(-a.getValue());
+ break;
+ }
+ // <<
case 88: {
const a = stack.pop();
const b = stack.pop();
- stack.push(a >> b);
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(aValue << bValue);
+ break;
+ }
+ // >>
+ case 89: {
+ const a = stack.pop();
+ const b = stack.pop();
+ const aValue = a instanceof Variable ? a.getValue() : a;
+ const bValue = b instanceof Variable ? b.getValue() : b;
+ stack.push(bValue >> aValue);
break;
}
default:
throw new Error(`Unhandled opcode ${command.opcode}`);
}
+ i++;
// Print some debug info
if (logger) {
- logger({ i, command, stack, variables });
+ const done = logger({ i, command, stack, variables, program });
+ console.log(done);
+ await done;
}
}
}
diff --git a/experiments/modern/yarn.lock b/experiments/modern/yarn.lock
index 00e388b6..bd4534a0 100644
--- a/experiments/modern/yarn.lock
+++ b/experiments/modern/yarn.lock
@@ -1791,6 +1791,13 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+attr-accept@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52"
+ integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==
+ dependencies:
+ core-js "^2.5.0"
+
autoprefixer@^9.4.9:
version "9.6.0"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
@@ -2710,7 +2717,7 @@ core-js@3.0.1:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
integrity sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==
-core-js@^2.4.0:
+core-js@^2.4.0, core-js@^2.5.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
@@ -3910,6 +3917,13 @@ file-loader@3.0.1:
loader-utils "^1.0.2"
schema-utils "^1.0.0"
+file-selector@^0.1.11:
+ version "0.1.12"
+ resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"
+ integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==
+ dependencies:
+ tslib "^1.9.0"
+
filesize@3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
@@ -7683,7 +7697,7 @@ prompts@^2.0.1:
kleur "^3.0.2"
sisteransi "^1.0.0"
-prop-types@^15.6.2:
+prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -7899,6 +7913,15 @@ react-dom@^16.8.6:
prop-types "^15.6.2"
scheduler "^0.13.6"
+react-dropzone@^10.1.5:
+ version "10.1.5"
+ resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.5.tgz#5f2817ab924b270010c7034fe01f49b6240b9c91"
+ integrity sha512-Hbe7soBc/i1fid7K3BU8T1oUJFnYO2AicS22F2MxcueMYDfKBWqZMr5ED3CoTAMs//9t2W30eyFiXm1h4wLCqA==
+ dependencies:
+ attr-accept "^1.1.3"
+ file-selector "^0.1.11"
+ prop-types "^15.7.2"
+
react-error-overlay@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d"