aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2020-05-31 15:42:02 -0700
committerAustin Adams <git@austinjadams.com>2020-05-31 15:42:02 -0700
commit0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a (patch)
tree1fa81ff57ba22c86bc50863b67787806d27078bb
parent96dfefba92f6ed46131593bacd49a99186b0a3fd (diff)
downloadnovice-0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a.tar.gz
novice-0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a.tar.xz
Create worker for assembler, add debug buttons
-rw-r--r--packages/novice-web/index-proto.html5
-rw-r--r--packages/novice-web/novice-web/components/AssembleForm.tsx49
-rw-r--r--packages/novice-web/novice-web/components/GuiDebugger.tsx122
-rw-r--r--packages/novice-web/novice-web/index.tsx10
-rw-r--r--packages/novice-web/novice-web/workers/assembler/assembler-worker.ts40
-rw-r--r--packages/novice-web/novice-web/workers/assembler/index.ts4
-rw-r--r--packages/novice-web/novice-web/workers/assembler/proto.ts27
-rw-r--r--packages/novice-web/novice-web/workers/assembler/worker-entry.ts4
-rw-r--r--packages/novice-web/novice-web/workers/base-worker.ts27
-rw-r--r--packages/novice-web/novice-web/workers/debugger/debugger-worker.ts (renamed from packages/novice-web/novice-web/worker/debugger-worker.ts)35
-rw-r--r--packages/novice-web/novice-web/workers/debugger/index.ts4
-rw-r--r--packages/novice-web/novice-web/workers/debugger/proto.ts (renamed from packages/novice-web/novice-web/worker/proto.ts)14
-rw-r--r--packages/novice-web/novice-web/workers/debugger/snitch-debugger.ts (renamed from packages/novice-web/novice-web/worker/snitch-debugger.ts)0
-rw-r--r--packages/novice-web/novice-web/workers/debugger/worker-entry.ts (renamed from packages/novice-web/novice-web/worker/worker.ts)0
-rw-r--r--packages/novice-web/style.css32
-rw-r--r--packages/novice-web/webpack.config.js3
16 files changed, 323 insertions, 53 deletions
diff --git a/packages/novice-web/index-proto.html b/packages/novice-web/index-proto.html
index 64dd3a2..902bcf1 100644
--- a/packages/novice-web/index-proto.html
+++ b/packages/novice-web/index-proto.html
@@ -2,10 +2,11 @@
<html>
<head>
<meta charset="UTF-8" />
- <title>Hello React!</title>
+ <title>novice</title>
+ <link rel="stylesheet" href="style.css">
</head>
<body>
- <div id="example"></div>
+ <div id="root"></div>
<!-- Dependencies -->
<script crossorigin src="REACT_URL"></script>
diff --git a/packages/novice-web/novice-web/components/AssembleForm.tsx b/packages/novice-web/novice-web/components/AssembleForm.tsx
new file mode 100644
index 0000000..0efd599
--- /dev/null
+++ b/packages/novice-web/novice-web/components/AssembleForm.tsx
@@ -0,0 +1,49 @@
+import * as React from 'react';
+
+export interface AssembleFormProps {
+ initialAssemblyCode: string;
+ handleAssembleRequest: (assemblyCode: string) => void;
+ handleStepRequest: () => void;
+ handleUnstepRequest: () => void;
+ handleContinueRequest: () => void;
+}
+
+export interface AssembleFormState {
+ assemblyCode: string;
+}
+
+export class AssembleForm extends React.Component<AssembleFormProps, AssembleFormState> {
+
+ constructor(props: AssembleFormProps) {
+ super(props);
+
+ this.state = {
+ assemblyCode: this.props.initialAssemblyCode,
+ };
+
+ this.handleAssemblyCodeChange = this.handleAssemblyCodeChange.bind(this);
+ this.handleAssembleButtonClick = this.handleAssembleButtonClick.bind(this);
+ }
+
+ public render() {
+ return (
+ <div className='assemble-form'>
+ <textarea value={this.state.assemblyCode} onChange={this.handleAssemblyCodeChange} />
+ <div className='buttons'>
+ <button onClick={this.handleAssembleButtonClick}>Assemble</button>
+ <button onClick={this.props.handleStepRequest}>Step</button>
+ <button onClick={this.props.handleUnstepRequest}>Unstep</button>
+ <button onClick={this.props.handleContinueRequest}>Continue</button>
+ </div>
+ </div>
+ );
+ }
+
+ private handleAssemblyCodeChange(e: React.FormEvent<HTMLTextAreaElement>) {
+ this.setState({assemblyCode: e.currentTarget.value});
+ }
+
+ private handleAssembleButtonClick(e: React.FormEvent<HTMLButtonElement>) {
+ this.props.handleAssembleRequest(this.state.assemblyCode);
+ }
+}
diff --git a/packages/novice-web/novice-web/components/GuiDebugger.tsx b/packages/novice-web/novice-web/components/GuiDebugger.tsx
index f0e6fb3..9894281 100644
--- a/packages/novice-web/novice-web/components/GuiDebugger.tsx
+++ b/packages/novice-web/novice-web/components/GuiDebugger.tsx
@@ -1,11 +1,15 @@
import { FullMachineState, getIsa, Isa, fmtHex, Symbols, BaseSymbols } from 'novice';
import * as React from 'react';
import { VariableSizeGrid as Grid } from 'react-window';
-import { FrontendMessage, WorkerMessage } from '../worker/proto';
+import { AssemblerFrontendMessage, AssemblerWorkerMessage } from '../workers/assembler';
+import { DebuggerFrontendMessage, DebuggerWorkerMessage } from '../workers/debugger';
+import { AssembleForm } from './AssembleForm';
export interface GuiDebuggerProps {
- workerBundleUrl: string;
+ debuggerWorkerBundleUrl: string;
+ assemblerWorkerBundleUrl: string;
isaName: string;
+ initialAssemblyCode: string;
}
export interface GuiDebuggerState {
@@ -16,7 +20,8 @@ export class GuiDebugger extends React.Component<GuiDebuggerProps,
GuiDebuggerState> {
private isa: Isa;
private symbols: Symbols;
- private worker: Worker;
+ private debuggerWorker: Worker;
+ private assemblerWorker: Worker;
constructor(props: GuiDebuggerProps) {
super(props);
@@ -27,10 +32,20 @@ export class GuiDebugger extends React.Component<GuiDebuggerProps,
state: this.isa.initMachineState(),
};
- (this.worker = new Worker(this.props.workerBundleUrl)).onerror =
- this.onError.bind(this);
- this.worker.onmessage = this.onMessage.bind(this);
- this.postMessage({ kind: 'reset', isa: this.props.isaName });
+ this.onError = this.onError.bind(this);
+ this.onDebuggerMessage = this.onDebuggerMessage.bind(this);
+ this.onAssemblerMessage = this.onAssemblerMessage.bind(this);
+ this.handleAssembleRequest = this.handleAssembleRequest.bind(this);
+ this.handleStepRequest = this.handleStepRequest.bind(this);
+ this.handleUnstepRequest = this.handleUnstepRequest.bind(this);
+ this.handleContinueRequest = this.handleContinueRequest.bind(this);
+
+ this.debuggerWorker = this.loadWorkerBundle(this.props.debuggerWorkerBundleUrl,
+ this.onDebuggerMessage);
+ this.assemblerWorker = this.loadWorkerBundle(this.props.assemblerWorkerBundleUrl,
+ this.onAssemblerMessage);
+
+ this.postDebuggerMessage({ kind: 'reset', isa: this.props.isaName });
}
public componentDidUpdate(prevProps: GuiDebuggerProps) {
@@ -38,17 +53,15 @@ export class GuiDebugger extends React.Component<GuiDebuggerProps,
if (prevProps.isaName !== this.props.isaName) {
this.isa = getIsa(this.props.isaName);
this.setState({ state: this.isa.initMachineState() });
- this.postMessage({ kind: 'reset', isa: this.props.isaName });
+ this.postDebuggerMessage({ kind: 'reset', isa: this.props.isaName });
}
}
- public onError(err: ErrorEvent) {
+ private onError(err: ErrorEvent) {
console.log(err);
}
- public onMessage(event: MessageEvent) {
- const msg: WorkerMessage = event.data;
-
+ private onDebuggerMessage(msg: DebuggerWorkerMessage) {
switch (msg.kind) {
case 'updates':
const [state, _] = this.isa.stateApplyUpdates(
@@ -66,9 +79,28 @@ export class GuiDebugger extends React.Component<GuiDebuggerProps,
}
}
+ private onAssemblerMessage(msg: AssemblerWorkerMessage) {
+ switch (msg.kind) {
+ case 'assembly-finished':
+ const sections = msg.sections;
+ this.postDebuggerMessage({ kind: 'load-sections', sections });
+ break;
+
+ case 'assembly-error':
+ const errorMessage = msg.errorMessage;
+ console.error('assembly error in frontend', errorMessage);
+ break;
+
+ default:
+ const __: never = msg;
+ }
+ }
+
public render() {
- const cols = [80, 80, 80, 200];
+ const rowHeight = 20;
+ const cols = [20, 80, 80, 80, 200];
const colVal: ((addr: number) => string)[] = [
+ addr => (this.state.state.pc == addr)? '►' : '',
addr => this.fmtAddr(addr),
addr => this.fmtWord(this.isa.stateLoad(this.state.state, addr)),
addr => this.isa.stateLoad(this.state.state, addr).toString(10),
@@ -84,18 +116,62 @@ export class GuiDebugger extends React.Component<GuiDebuggerProps,
</div>
);
- return (<Grid
- columnCount={cols.length}
- columnWidth={i => cols[i]}
- rowCount={Math.pow(2, this.isa.spec.mem.space)}
- rowHeight={i => 20}
- width={cols.reduce((acc, cur) => acc + cur) + 32}
- height={600}
- >{cell}</Grid>);
+ return (
+ <div className='gui-wrapper'>
+ <div className='memory-view'>
+ <Grid columnCount={cols.length}
+ columnWidth={i => cols[i]}
+ rowCount={Math.pow(2, this.isa.spec.mem.space)}
+ rowHeight={i => rowHeight}
+ width={cols.reduce((acc, cur) => acc + cur) + 32}
+ height={600}
+ initialScrollTop={this.state.state.pc * rowHeight}>{cell}</Grid>
+ </div>
+ <AssembleForm initialAssemblyCode={this.props.initialAssemblyCode}
+ handleAssembleRequest={this.handleAssembleRequest}
+ handleStepRequest={this.handleStepRequest}
+ handleUnstepRequest={this.handleUnstepRequest}
+ handleContinueRequest={this.handleContinueRequest} />
+ </div>
+ );
+ }
+
+ private loadWorkerBundle<WorkerMessageType>(workerBundleUrl: string,
+ onMessage: (msg: WorkerMessageType) => void): Worker {
+ const worker = new Worker(workerBundleUrl);
+ worker.onerror = this.onError;
+ worker.onmessage = (e: MessageEvent) => {
+ const msg: WorkerMessageType = e.data;
+ onMessage(msg);
+ };
+ return worker;
+ }
+
+ private postDebuggerMessage(msg: DebuggerFrontendMessage): void {
+ this.debuggerWorker.postMessage(msg);
+ }
+
+ private postAssemblerMessage(msg: AssemblerFrontendMessage): void {
+ this.assemblerWorker.postMessage(msg);
+ }
+
+ private handleAssembleRequest(assemblyCode: string): void {
+ // TODO: it's a hack to assume isa name == assembler config name
+ this.postAssemblerMessage({ kind: 'assemble',
+ configName: this.props.isaName,
+ assemblyCode });
+ }
+
+ private handleStepRequest(): void {
+ this.postDebuggerMessage({ kind: 'step' });
+ }
+
+ private handleUnstepRequest(): void {
+ this.postDebuggerMessage({ kind: 'unstep' });
}
- private postMessage(msg: FrontendMessage): void {
- this.worker.postMessage(msg);
+ private handleContinueRequest(): void {
+ this.postDebuggerMessage({ kind: 'run' });
}
private fmtAddr(addr: number): string {
diff --git a/packages/novice-web/novice-web/index.tsx b/packages/novice-web/novice-web/index.tsx
index bb3dc1a..213b51f 100644
--- a/packages/novice-web/novice-web/index.tsx
+++ b/packages/novice-web/novice-web/index.tsx
@@ -3,9 +3,13 @@ import * as ReactDOM from 'react-dom';
import { GuiDebugger } from './components/GuiDebugger';
-const WORKER_BUNDLE_URL = 'dist/worker.bundle.js';
+const DEBUGGER_WORKER_BUNDLE_URL = 'dist/debuggerWorker.bundle.js';
+const ASSEMBLER_WORKER_BUNDLE_URL = 'dist/assemblerWorker.bundle.js';
ReactDOM.render(
- <GuiDebugger isaName='lc3' workerBundleUrl={WORKER_BUNDLE_URL} />,
- document.getElementById('example'),
+ <GuiDebugger isaName='lc3'
+ initialAssemblyCode={'; write LC-3 assembly code here\n'}
+ debuggerWorkerBundleUrl={DEBUGGER_WORKER_BUNDLE_URL}
+ assemblerWorkerBundleUrl={ASSEMBLER_WORKER_BUNDLE_URL} />,
+ document.getElementById('root'),
);
diff --git a/packages/novice-web/novice-web/workers/assembler/assembler-worker.ts b/packages/novice-web/novice-web/workers/assembler/assembler-worker.ts
new file mode 100644
index 0000000..56d08aa
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/assembler/assembler-worker.ts
@@ -0,0 +1,40 @@
+import { getConfig, Assembler, SymbTable, MachineCodeSection } from 'novice';
+import { AssemblerFrontendMessage, AssemblerWorkerMessage } from './proto';
+import { BaseWorker } from '../base-worker';
+
+class AssemblerWorker extends BaseWorker<AssemblerFrontendMessage,
+ AssemblerWorkerMessage> {
+
+ public constructor(ctx: Worker) {
+ super(ctx);
+ }
+
+ protected onFrontendMessage(msg: AssemblerFrontendMessage): void {
+ switch (msg.kind) {
+ case 'assemble':
+ this.assemble(msg.configName, msg.assemblyCode);
+ break;
+
+ // TODO: add never default again
+ }
+ }
+
+ private async assemble(configName: string, assemblyCode: string): Promise<void> {
+ const cfg = getConfig(configName);
+ const assembler = new Assembler(cfg);
+
+ let symbtable: SymbTable;
+ let sections: MachineCodeSection[];
+
+ try {
+ [symbtable, sections] = await assembler.assembleString(assemblyCode);
+ } catch (err) {
+ this.sendWorkerMessage({kind: 'assembly-error', errorMessage: err.message});
+ return;
+ }
+
+ this.sendWorkerMessage({kind: 'assembly-finished', symbtable, sections});
+ }
+}
+
+export { AssemblerWorker };
diff --git a/packages/novice-web/novice-web/workers/assembler/index.ts b/packages/novice-web/novice-web/workers/assembler/index.ts
new file mode 100644
index 0000000..8cba4d2
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/assembler/index.ts
@@ -0,0 +1,4 @@
+import { AssemblerWorker } from './assembler-worker';
+import { AssemblerFrontendMessage, AssemblerWorkerMessage } from './proto';
+
+export { AssemblerWorker, AssemblerFrontendMessage, AssemblerWorkerMessage };
diff --git a/packages/novice-web/novice-web/workers/assembler/proto.ts b/packages/novice-web/novice-web/workers/assembler/proto.ts
new file mode 100644
index 0000000..7ff3620
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/assembler/proto.ts
@@ -0,0 +1,27 @@
+import { SymbTable, MachineCodeSection } from 'novice';
+
+// Messages originating from frontend
+
+interface AssembleMessage {
+ kind: 'assemble';
+ configName: string;
+ assemblyCode: string;
+}
+
+type AssemblerFrontendMessage = AssembleMessage;
+
+// Messages originating from worker
+
+interface AssemblyFinishedMessage {
+ kind: 'assembly-finished';
+ symbtable: SymbTable;
+ sections: MachineCodeSection[];
+}
+
+interface AssemblyErrorMessage {
+ kind: 'assembly-error';
+ errorMessage: string;
+}
+type AssemblerWorkerMessage = AssemblyFinishedMessage|AssemblyErrorMessage;
+
+export { AssemblerFrontendMessage, AssemblerWorkerMessage };
diff --git a/packages/novice-web/novice-web/workers/assembler/worker-entry.ts b/packages/novice-web/novice-web/workers/assembler/worker-entry.ts
new file mode 100644
index 0000000..2f7fcad
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/assembler/worker-entry.ts
@@ -0,0 +1,4 @@
+import { AssemblerWorker } from './assembler-worker';
+
+const ctx: Worker = self as any;
+new AssemblerWorker(ctx).register();
diff --git a/packages/novice-web/novice-web/workers/base-worker.ts b/packages/novice-web/novice-web/workers/base-worker.ts
new file mode 100644
index 0000000..113c7c2
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/base-worker.ts
@@ -0,0 +1,27 @@
+abstract class BaseWorker<FrontendMessageType, WorkerMessageType> {
+ private ctx: Worker;
+
+ public constructor(ctx: Worker) {
+ this.ctx = ctx;
+
+ this.onMessageHandler = this.onMessageHandler.bind(this);
+ }
+
+ // Register events
+ public register(): void {
+ this.ctx.onmessage = this.onMessageHandler;
+ }
+
+ public onMessageHandler(event: MessageEvent): void {
+ const msg: FrontendMessageType = event.data;
+ this.onFrontendMessage(msg);
+ }
+
+ protected abstract onFrontendMessage(frontendMessage: FrontendMessageType): void;
+
+ protected sendWorkerMessage(workerMessage: WorkerMessageType): void {
+ this.ctx.postMessage(workerMessage);
+ }
+}
+
+export { BaseWorker };
diff --git a/packages/novice-web/novice-web/worker/debugger-worker.ts b/packages/novice-web/novice-web/workers/debugger/debugger-worker.ts
index ddb4c61..5adfb1b 100644
--- a/packages/novice-web/novice-web/worker/debugger-worker.ts
+++ b/packages/novice-web/novice-web/workers/debugger/debugger-worker.ts
@@ -1,32 +1,27 @@
import { Debugger, getIsa, IO, MachineStateUpdate } from 'novice';
-import { FrontendMessage, WorkerMessage } from './proto';
+import { DebuggerFrontendMessage, DebuggerWorkerMessage } from './proto';
import { SnitchDebugger } from './snitch-debugger';
+import { BaseWorker } from '../base-worker';
-class DebuggerWorker {
- private ctx: Worker;
+class DebuggerWorker extends BaseWorker<DebuggerFrontendMessage,
+ DebuggerWorkerMessage> {
private dbg: Debugger|null;
private io: IO;
public constructor(ctx: Worker) {
- this.ctx = ctx;
+ super(ctx);
+
this.dbg = null;
// TODO: Add actual IO
this.io = {
getc: () => Promise.resolve(0),
putc: (c: number) => {
- this.postMessage({ kind: 'putc', c });
+ this.sendWorkerMessage({ kind: 'putc', c });
},
};
}
- // Register events
- public register(): void {
- this.ctx.onmessage = this.onMessage.bind(this);
- }
-
- public onMessage(event: MessageEvent): void {
- const msg: FrontendMessage = event.data;
-
+ protected onFrontendMessage(msg: DebuggerFrontendMessage): void {
switch (msg.kind) {
case 'reset':
this.dbg = new SnitchDebugger(getIsa(msg.isa), this.io, -1,
@@ -57,6 +52,14 @@ class DebuggerWorker {
this.dbg.step();
break;
+ case 'unstep':
+ if (!this.dbg) {
+ throw new Error('must reset before unstepping');
+ }
+
+ this.dbg.unstep();
+ break;
+
case 'run':
if (!this.dbg) {
throw new Error('must reset before running');
@@ -71,11 +74,7 @@ class DebuggerWorker {
}
private onUpdates(updates: MachineStateUpdate[]): void {
- this.postMessage({kind: 'updates', updates});
- }
-
- private postMessage(msg: WorkerMessage): void {
- this.ctx.postMessage(msg);
+ this.sendWorkerMessage({kind: 'updates', updates});
}
}
diff --git a/packages/novice-web/novice-web/workers/debugger/index.ts b/packages/novice-web/novice-web/workers/debugger/index.ts
new file mode 100644
index 0000000..3db7726
--- /dev/null
+++ b/packages/novice-web/novice-web/workers/debugger/index.ts
@@ -0,0 +1,4 @@
+import { DebuggerWorker } from './debugger-worker';
+import { DebuggerFrontendMessage, DebuggerWorkerMessage } from './proto';
+
+export { DebuggerWorker, DebuggerFrontendMessage, DebuggerWorkerMessage };
diff --git a/packages/novice-web/novice-web/worker/proto.ts b/packages/novice-web/novice-web/workers/debugger/proto.ts
index 7f34645..acbad8c 100644
--- a/packages/novice-web/novice-web/worker/proto.ts
+++ b/packages/novice-web/novice-web/workers/debugger/proto.ts
@@ -20,12 +20,16 @@ interface StepMessage {
kind: 'step';
}
+interface UnstepMessage {
+ kind: 'unstep';
+}
+
interface RunMessage {
kind: 'run';
}
-type FrontendMessage = ResetMessage|LoadSectionsMessage|InterruptMessage|
- StepMessage|RunMessage;
+type DebuggerFrontendMessage = ResetMessage|LoadSectionsMessage|InterruptMessage|
+ StepMessage|UnstepMessage|RunMessage;
// Messages originating from worker
@@ -39,8 +43,6 @@ interface PutcMessage {
c: number;
}
-type WorkerMessage = UpdatesMessage|PutcMessage;
+type DebuggerWorkerMessage = UpdatesMessage|PutcMessage;
-export { ResetMessage, LoadSectionsMessage, InterruptMessage, StepMessage,
- RunMessage, UpdatesMessage, PutcMessage, FrontendMessage,
- WorkerMessage };
+export { DebuggerFrontendMessage, DebuggerWorkerMessage };
diff --git a/packages/novice-web/novice-web/worker/snitch-debugger.ts b/packages/novice-web/novice-web/workers/debugger/snitch-debugger.ts
index 583dd10..583dd10 100644
--- a/packages/novice-web/novice-web/worker/snitch-debugger.ts
+++ b/packages/novice-web/novice-web/workers/debugger/snitch-debugger.ts
diff --git a/packages/novice-web/novice-web/worker/worker.ts b/packages/novice-web/novice-web/workers/debugger/worker-entry.ts
index 0d75bd5..0d75bd5 100644
--- a/packages/novice-web/novice-web/worker/worker.ts
+++ b/packages/novice-web/novice-web/workers/debugger/worker-entry.ts
diff --git a/packages/novice-web/style.css b/packages/novice-web/style.css
new file mode 100644
index 0000000..7cb8c5e
--- /dev/null
+++ b/packages/novice-web/style.css
@@ -0,0 +1,32 @@
+html, body {
+ background-color: white;
+}
+
+.gui-wrapper {
+ width: 960px;
+ margin: 0 auto;
+}
+
+.memory-view {
+ display: inline-block;
+ font-family: monospace;
+}
+
+.assemble-form {
+ display: inline-block;
+ margin: 0 0 0 32px;
+}
+
+.assemble-form textarea {
+ height: 550px;
+ width: 400px;
+ display: block;
+}
+
+.assemble-form .buttons {
+ display: block;
+}
+
+.assemble-form .buttons button {
+ display: inline-block;
+}
diff --git a/packages/novice-web/webpack.config.js b/packages/novice-web/webpack.config.js
index 38e61c7..6d30935 100644
--- a/packages/novice-web/webpack.config.js
+++ b/packages/novice-web/webpack.config.js
@@ -1,7 +1,8 @@
module.exports = {
entry: {
main: "./novice-web/index.tsx",
- worker: "./novice-web/worker/worker.ts",
+ debuggerWorker: "./novice-web/workers/debugger/worker-entry.ts",
+ assemblerWorker: "./novice-web/workers/assembler/worker-entry.ts",
},
output: {
filename: "[name].bundle.js",