diff --git a/packages/webamp-modern/src/App.js b/packages/webamp-modern/src/App.js index 1fdc0ed8..84cf8307 100644 --- a/packages/webamp-modern/src/App.js +++ b/packages/webamp-modern/src/App.js @@ -6,8 +6,6 @@ import * as Selectors from "./Selectors"; import cornerSkin from "../skins/CornerAmp_Redux.wal"; import { useDispatch, useSelector, useStore } from "react-redux"; import DropTarget from "./components/DropTarget"; -import Debugger from "./debugger"; -import Sidebar from "./Sidebar"; import { Maki } from "./MakiRenderer"; const Dashboard = React.lazy(() => import("./Dashboard")); @@ -116,9 +114,6 @@ function Modern() { ))} - - - ); } diff --git a/packages/webamp-modern/src/Sidebar.js b/packages/webamp-modern/src/Sidebar.js deleted file mode 100644 index c75aaabd..00000000 --- a/packages/webamp-modern/src/Sidebar.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -export default function Sidebar({ children }) { - const [open, setOpen] = React.useState(false); - return ( -
setOpen(true)} - > - {open && children} -
- ); -} diff --git a/packages/webamp-modern/src/debugger/Command.js b/packages/webamp-modern/src/debugger/Command.js deleted file mode 100644 index 02975027..00000000 --- a/packages/webamp-modern/src/debugger/Command.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import Variable from "./Variable"; -import { COMMANDS } from "../maki-interpreter/constants"; - -export default function Command({ command, variables }) { - const { arg } = command; - let foo = null; - switch (command.opcode) { - case 1: - foo = ( - - Variable( - ) - - ); - break; - default: - foo = null; - } - return ( - <> - ({command.opcode}) {COMMANDS[command.opcode].name.toUpperCase()} {foo} - - ); -} diff --git a/packages/webamp-modern/src/debugger/Value.js b/packages/webamp-modern/src/debugger/Value.js deleted file mode 100644 index 78e46ddc..00000000 --- a/packages/webamp-modern/src/debugger/Value.js +++ /dev/null @@ -1,15 +0,0 @@ -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/packages/webamp-modern/src/debugger/Variable.js b/packages/webamp-modern/src/debugger/Variable.js deleted file mode 100644 index 1dd0ac41..00000000 --- a/packages/webamp-modern/src/debugger/Variable.js +++ /dev/null @@ -1,43 +0,0 @@ -import runtime from "../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 { - // Bit of a hack. We should probably fix this. - type = obj.prototype.getclassname(); - } - break; - } - case "SUBCLASS": { - const obj = runtime[variable.type.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/packages/webamp-modern/src/debugger/index.js b/packages/webamp-modern/src/debugger/index.js deleted file mode 100644 index b5073fcb..00000000 --- a/packages/webamp-modern/src/debugger/index.js +++ /dev/null @@ -1,325 +0,0 @@ -import React from "react"; -import { run } from "../maki-interpreter/virtualMachine"; -import System from "../runtime/System"; -import runtime from "../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 backgroundColorFromMessageTitle(messageTitle) { - switch (messageTitle) { - case "Success": - return "lightgreen"; - case "Fial": - return "pink"; - default: - return "none"; - } -} - -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 -

- - - ) : ( - - )} -
- ); -} - -// Given a generator and a handler function, returns a function `next` which, -// when called, will `.next` values off the generator and pass them to `handler` -// until `handler` returns `false`. -function useNextGeneratorValue(gen, handler) { - return React.useMemo(() => { - if (gen == null) { - // TODO: Return null and disable the next button - return () => {}; - } - return () => { - let ret = gen.next(); - while (!ret.done) { - if (handler(ret.value) === false) { - return; - } - ret = gen.next(); - } - }; - }, [gen, handler]); -} - -const initialState = { - variables: [], - stack: [], - commands: [], - commandOffset: 0, - messages: [], - paused: true, -}; - -function reducer(state, action) { - switch (action.type) { - case "STEPPED": - const { variables, commands, commandOffset, stack } = action; - return { - ...state, - variables, - commands, - commandOffset, - stack, - }; - case "GOT_MESSAGE": - return { - ...state, - messages: [...state.messages, action.message], - }; - case "PAUSE": - return { ...state, paused: true }; - case "UNPAUSE": - return { ...state, paused: false }; - - default: - throw new Error("Unknown action type"); - } -} - -function Debugger({ maki }) { - const [gen, setGen] = React.useState(null); - const [breakPoints, setBreakpoints] = React.useState(new Set([])); - const [state, dispatch] = React.useReducer(reducer, initialState); - - const { variables, stack, commands, commandOffset, messages, paused } = state; - - const system = React.useMemo(() => { - const sys = new System(); - sys.messagebox = function (message, messageTitle, flag, notanymoreId) { - dispatch({ - type: "GOT_MESSAGE", - message: { message, messageTitle, flag, notanymoreId }, - }); - }; - return sys; - }, [dispatch]); - - React.useEffect(() => { - run({ runtime, data: maki, system, debugHandler: setGen }); - }, [maki, system]); - - const nextValue = React.useCallback( - (value) => { - dispatch({ - type: "STEPPED", - variables: value.variables, - commands: value.commands, - commandOffset: value.i, - stack: value.stack, - }); - if (paused) { - return false; - } - if (breakPoints.has(value.i)) { - dispatch({ type: "PAUSE" }); - return; - } - - return true; - }, - [dispatch, paused, breakPoints] - ); - - const next = useNextGeneratorValue(gen, nextValue); - - // When we unpause, we should resume - React.useEffect(() => { - if (!paused) next(); - }, [next, paused]); - - // When we get a new generator, immediatly take the first step. - React.useEffect(() => { - next(); - }, [gen, next]); - - 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 ( -
-
-

- Messages{" "} - - { - messages.filter(({ messageTitle }) => messageTitle === "Success") - .length - } - - /{messages.length} -

- - - {messages.map( - ({ message, messageTitle, flag, notanymoreId }, i) => { - const backgroundColor = backgroundColorFromMessageTitle( - messageTitle - ); - return ( - - - - - - - - ); - } - )} - -
{i}{message}{messageTitle}{flag}{notanymoreId}
-
-
- - {paused ? ( - - ) : ( - - )} -

Commands

- - - {commands != null && - commands.map((command, i) => ( - - - - - ))} - -
- {breakPoints.has(i) ? ( - - ) : ( - - )} - {i} - - -
-
-
-

Stack

- - - {stack != null && - stack.map((value, i) => ( - - - - - ))} - -
{i} - -
-
-
-

Variables

- - - {variables != null && - variables.map((variable, i) => ( - - - - - ))} - -
{i} - -
-
-
- ); -} - -export default Wrapper; diff --git a/packages/webamp-modern/src/index.js b/packages/webamp-modern/src/index.js index 82af9da9..09da3f17 100644 --- a/packages/webamp-modern/src/index.js +++ b/packages/webamp-modern/src/index.js @@ -3,14 +3,13 @@ import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import "./index.css"; import App from "./App"; -import Debugger from "./debugger"; import { create } from "./store"; const store = create(); ReactDOM.render( - {window.location.pathname === "/debugger" ? : } + , document.getElementById("root") ); diff --git a/packages/webamp-modern/src/maki-interpreter/interpreter.js b/packages/webamp-modern/src/maki-interpreter/interpreter.js index dc51d204..9c0bddd6 100644 --- a/packages/webamp-modern/src/maki-interpreter/interpreter.js +++ b/packages/webamp-modern/src/maki-interpreter/interpreter.js @@ -11,16 +11,7 @@ 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()`. -export function* interpret(start, program, stack = []) { +export function interpret(start, program, stack = []) { const { commands, methods, variables, classes } = program; function popStackValue() { @@ -49,11 +40,6 @@ export function* interpret(start, program, stack = []) { 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 @@ -160,12 +146,9 @@ export function* interpret(start, program, stack = []) { methodArgs.push(aValue); } const obj = popStackValue(); - const ret = obj[methodName](...methodArgs); - let value; - if (isPromise(ret)) { - value = yield ret; - } else { - value = ret; + let value = obj[methodName](...methodArgs); + if (isPromise(value)) { + throw new Error("Did not expect maki method to return promise"); } if (value === null) { // variables[1] holds global NULL value @@ -180,7 +163,7 @@ export function* interpret(start, program, stack = []) { // 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); + const value = interpret(offset, program, stack); stack.push(value); break; } diff --git a/packages/webamp-modern/src/maki-interpreter/virtualMachine.js b/packages/webamp-modern/src/maki-interpreter/virtualMachine.js index 64b89065..a64d5b4d 100644 --- a/packages/webamp-modern/src/maki-interpreter/virtualMachine.js +++ b/packages/webamp-modern/src/maki-interpreter/virtualMachine.js @@ -1,29 +1,8 @@ import parse from "./parser"; import { getClass, getFormattedId } from "./objects"; import { interpret } from "./interpreter"; -import { isPromise } from "../utils"; -// 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. -async function runGeneratorUntilReturn(gen) { - let val = gen.next(); - while (!val.done) { - val = gen.next(); - if (isPromise(val.value)) { - gen.next(await val.value); - } - } - return val.value; -} - -export function run({ - runtime, - data, - system, - log, - debugHandler = runGeneratorUntilReturn, -}) { +export function run({ runtime, data, system, log }) { const program = parse(data); // Replace class hashes with actual JavaScript classes from the runtime @@ -50,12 +29,7 @@ export function run({ // 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. - debugHandler(interpret(commandOffset, program, args.reverse())); + interpret(commandOffset, program, args.reverse()); }); });