Remove debugger. I want to redo this

This commit is contained in:
Jordan Eldredge 2021-05-30 14:18:06 -07:00
parent aafdcd2922
commit 6185d95c47
9 changed files with 8 additions and 486 deletions

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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}
</>
);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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")
);

View file

@ -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;
}

View file

@ -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());
});
});