diff options
author | Austin Adams <git@austinjadams.com> | 2020-05-31 15:42:02 -0700 |
---|---|---|
committer | Austin Adams <git@austinjadams.com> | 2020-05-31 15:42:02 -0700 |
commit | 0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a (patch) | |
tree | 1fa81ff57ba22c86bc50863b67787806d27078bb | |
parent | 96dfefba92f6ed46131593bacd49a99186b0a3fd (diff) | |
download | novice-0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a.tar.gz novice-0e8ee6496d2efa3b1dc9b1171b9e0cfd663dc74a.tar.xz |
Create worker for assembler, add debug buttons
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", |