mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 09:37:17 +00:00
Implement more operators and add debugger
This commit is contained in:
parent
c02b59219c
commit
dcbcd20a65
10 changed files with 392 additions and 20 deletions
|
|
@ -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"
|
||||
|
|
|
|||
23
experiments/modern/src/debugger/Command.js
Normal file
23
experiments/modern/src/debugger/Command.js
Normal file
|
|
@ -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 = (
|
||||
<span>
|
||||
Variable(
|
||||
<Variable variable={variables[arg]} />)
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
foo = null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
({command.opcode}) {command.command.name.toUpperCase()} {foo}
|
||||
</>
|
||||
);
|
||||
}
|
||||
15
experiments/modern/src/debugger/Value.js
Normal file
15
experiments/modern/src/debugger/Value.js
Normal file
|
|
@ -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(
|
||||
<Variable variable={value} />)
|
||||
</>
|
||||
);
|
||||
}
|
||||
return value ? value.toString() : null;
|
||||
}
|
||||
32
experiments/modern/src/debugger/Variable.js
Normal file
32
experiments/modern/src/debugger/Variable.js
Normal file
|
|
@ -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;
|
||||
}
|
||||
177
experiments/modern/src/debugger/index.js
Normal file
177
experiments/modern/src/debugger/index.js
Normal file
|
|
@ -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 (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
style={{
|
||||
height: "100vh",
|
||||
width: "100vh",
|
||||
backgroundColor: isDragActive ? "yellow" : "white",
|
||||
}}
|
||||
>
|
||||
{maki == null ? (
|
||||
<>
|
||||
<h1 style={{ textAlign: "center" }}>
|
||||
Drop a .maki file here to debug
|
||||
</h1>
|
||||
<input {...getInputProps()} type="file" />
|
||||
</>
|
||||
) : (
|
||||
<Debugger maki={maki} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
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 (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "70% 30%",
|
||||
gridTemplateRows: "30% 70%",
|
||||
height: "100vh",
|
||||
gridTemplateAreas: `
|
||||
"commands stack"
|
||||
"commands variables"
|
||||
`,
|
||||
}}
|
||||
>
|
||||
<div style={{ gridArea: "commands", overflow: "scroll" }}>
|
||||
<button onClick={next}>Next</button>
|
||||
<h2>Commands</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{commands != null &&
|
||||
commands.map((command, i) => (
|
||||
<tr
|
||||
style={{
|
||||
backgroundColor: i === commandOffset ? "pink" : "white",
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
<td>{i}</td>
|
||||
<td>
|
||||
<Command command={command} variables={variables} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style={{ gridArea: "stack", overflow: "scroll" }}>
|
||||
<h2>Stack</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{stack != null &&
|
||||
stack.map((value, i) => (
|
||||
<tr key={i}>
|
||||
<td>{i}</td>
|
||||
<td>
|
||||
<Value value={value} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style={{ gridArea: "variables", overflow: "scroll" }}>
|
||||
<h2>Variables</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{variables != null &&
|
||||
variables.map((variable, i) => (
|
||||
<tr key={i}>
|
||||
<td>{i}</td>
|
||||
<td>
|
||||
<Variable variable={variable} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Wrapper;
|
||||
|
|
@ -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(<App />, document.getElementById('root'));
|
||||
ReactDOM.render(
|
||||
window.location.pathname === "/debugger" ? <Debugger /> : <App />,
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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, () => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue