diff --git a/vweb/src/lib.rs b/vweb/src/lib.rs index 744e60d..f51e441 100644 --- a/vweb/src/lib.rs +++ b/vweb/src/lib.rs @@ -112,6 +112,8 @@ impl WasmCPU { self.0.dp().into() } + pub fn halted(&self) -> bool { self.0.halted() } + pub fn set_pc(&mut self, val: u32) { self.0.set_pc(Word::from(val)) } @@ -127,4 +129,16 @@ impl WasmCPU { self.set_pc(0x400) } + + pub fn reset(&mut self) { + self.0.reset() + } + + pub fn start(&mut self) { + self.0.start() + } + + pub fn tick(&mut self) { + self.0.tick() + } } diff --git a/vweb/src/snippet_emulator.css b/vweb/src/snippet_emulator.css index 96ba395..8cd43cc 100644 --- a/vweb/src/snippet_emulator.css +++ b/vweb/src/snippet_emulator.css @@ -35,7 +35,10 @@ body { .snippetEmulator .buttons a { margin-right: 0.5em; cursor: pointer; + user-select: none; } .snippetEmulator .step, .snippetEmulator .run { color: lightgreen } .snippetEmulator .reset { color: lightcoral } .snippetEmulator .build, .snippetEmulator .edit { color: dodgerblue } +.snippetEmulator .highlight { background-color: darkgoldenrod } +.snippetEmulator .src { overflow: scroll } \ No newline at end of file diff --git a/vweb/src/snippet_emulator.jsx b/vweb/src/snippet_emulator.jsx index b413333..ffacec3 100644 --- a/vweb/src/snippet_emulator.jsx +++ b/vweb/src/snippet_emulator.jsx @@ -1,6 +1,6 @@ import './snippet_emulator.css' import React, {useState, useCallback, useEffect} from 'react' -import { WasmCPU, assemble_snippet, source_map } from '../pkg/vweb.js' +import {WasmCPU, assemble_snippet, source_map, NovaForth} from '../pkg/vweb.js' export default function({ children }) { if (React.Children.count(children) !== 1) { @@ -12,7 +12,19 @@ export default function({ children }) { const [message, setMessage] = useState('') // A status / error message const onChangeSrc = useCallback((e) => setSrc(e.target.value), []) // Callback for the textarea const [binary, setBinary] = useState(null) // The assembled binary - const [sourceMap, setSourceMap] = useState({}) // A map from byte address to line number + const [sourceMap, setSourceMap] = useState(null) // A map from byte address to line number + const [cpu, setCpu] = useState(new WasmCPU()) // The actual CPU emulator itself + const [activeLine, setActiveLine] = useState(null) // The (0-based) index of the + + const updateActiveLine = useCallback(() => { + sourceMap && setActiveLine(sourceMap.get(cpu.pc())) + }, [cpu, sourceMap]) + + const reset = useCallback(() => { + cpu.reset() + cpu.start() + updateActiveLine() + }, [cpu, updateActiveLine]) // A callback to build the binary from source const rebuild = useCallback((code) => { @@ -20,7 +32,7 @@ export default function({ children }) { const bin = assemble_snippet(code) const sm = source_map(code) setBinary(bin) - setSourceMap(sm) + setSourceMap(sm.Ok || {}) setMessage(`Assembled ${bin.length} bytes`) return true } catch(e) { @@ -28,13 +40,26 @@ export default function({ children }) { } }, []) + const step = useCallback(() => { + cpu.tick() + updateActiveLine() + }, [cpu, updateActiveLine]) + // On load, clean the source and build the stuff useEffect(() => { const lines = React.Children.toArray(children)[0].split('\n') const cleaned = lines.map(l => l.trim()).join('\n') setSrc(cleaned) rebuild(cleaned) - }, [rebuild, children]) + }, [children]) + + // Load and reset the CPU when the assembled binary changes + useEffect(() => { + if (binary) { + cpu.load(binary) + reset() + } + }, [cpu, binary, reset]) if (editing) { return ( @@ -49,12 +74,12 @@ export default function({ children }) { } else { return (
- +
{message}
- [Step] + [Step] [Run] - [Reset] + [Reset] { setEditing(true); setMessage('') }}>[Edit]
@@ -62,10 +87,11 @@ export default function({ children }) { } } -function SourceDisplay({ src }) { +function SourceDisplay({ src, activeLine }) { const lines = src.split('\n') const lineDivs = lines.map((line, i) => { - return (
{line || <> }
) + // Loop indices are from 0, activeLine numbers are from 1 + return (
{line || <> }
) }) return (
{lineDivs}