mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-24 02:27:37 +00:00
Remove debugger. I want to redo this
This commit is contained in:
parent
aafdcd2922
commit
6185d95c47
9 changed files with 8 additions and 486 deletions
|
|
@ -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() {
|
|||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Sidebar>
|
||||
<Debugger />
|
||||
</Sidebar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
export default function Sidebar({ children }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
width: open ? "50%" : 0,
|
||||
borderLeft: open ? "2px solid grey" : "20px solid grey",
|
||||
padding: 0,
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
transition: "0.25s ease-out",
|
||||
}}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{open && children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 = (
|
||||
<span>
|
||||
Variable(
|
||||
<Variable variable={variables[arg]} />)
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
foo = null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
({command.opcode}) {COMMANDS[command.opcode].name.toUpperCase()} {foo}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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(
|
||||
<Variable variable={value} />)
|
||||
</>
|
||||
);
|
||||
}
|
||||
return value ? value.toString() : 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;
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "70% 30%",
|
||||
gridTemplateRows: "30% 70%",
|
||||
height: "100vh",
|
||||
gridTemplateAreas: `
|
||||
"messages stack"
|
||||
"commands variables"
|
||||
`,
|
||||
}}
|
||||
>
|
||||
<div style={{ gridArea: "messages", overflow: "scroll" }}>
|
||||
<h2>
|
||||
Messages{" "}
|
||||
<span style={{ backgroundColor: "lightgreen" }}>
|
||||
{
|
||||
messages.filter(({ messageTitle }) => messageTitle === "Success")
|
||||
.length
|
||||
}
|
||||
</span>
|
||||
/{messages.length}
|
||||
</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{messages.map(
|
||||
({ message, messageTitle, flag, notanymoreId }, i) => {
|
||||
const backgroundColor = backgroundColorFromMessageTitle(
|
||||
messageTitle
|
||||
);
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td>{i}</td>
|
||||
<td>{message}</td>
|
||||
<td style={{ backgroundColor }}>{messageTitle}</td>
|
||||
<td>{flag}</td>
|
||||
<td>{notanymoreId}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style={{ gridArea: "commands", overflow: "scroll" }}>
|
||||
<button onClick={next}>Next</button>
|
||||
{paused ? (
|
||||
<button onClick={() => dispatch({ type: "UNPAUSE" })}>Play</button>
|
||||
) : (
|
||||
<button onClick={() => dispatch({ type: "PAUSE" })}>Pause</button>
|
||||
)}
|
||||
<h2>Commands</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
{commands != null &&
|
||||
commands.map((command, i) => (
|
||||
<tr
|
||||
style={{
|
||||
backgroundColor: i === commandOffset ? "pink" : "white",
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
<td>
|
||||
{breakPoints.has(i) ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
const newBreakPoints = new Set(breakPoints);
|
||||
newBreakPoints.delete(i);
|
||||
setBreakpoints(newBreakPoints);
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => {
|
||||
const newBreakPoints = new Set(breakPoints);
|
||||
newBreakPoints.add(i);
|
||||
setBreakpoints(newBreakPoints);
|
||||
}}
|
||||
>
|
||||
Pause
|
||||
</button>
|
||||
)}
|
||||
{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;
|
||||
|
|
@ -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(
|
||||
<Provider store={store}>
|
||||
{window.location.pathname === "/debugger" ? <Debugger /> : <App />}
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue