Highlighting lines and stepping through code
This commit is contained in:
@@ -112,6 +112,8 @@ impl WasmCPU {
|
|||||||
self.0.dp().into()
|
self.0.dp().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn halted(&self) -> bool { self.0.halted() }
|
||||||
|
|
||||||
pub fn set_pc(&mut self, val: u32) {
|
pub fn set_pc(&mut self, val: u32) {
|
||||||
self.0.set_pc(Word::from(val))
|
self.0.set_pc(Word::from(val))
|
||||||
}
|
}
|
||||||
@@ -127,4 +129,16 @@ impl WasmCPU {
|
|||||||
|
|
||||||
self.set_pc(0x400)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ body {
|
|||||||
.snippetEmulator .buttons a {
|
.snippetEmulator .buttons a {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.snippetEmulator .step, .snippetEmulator .run { color: lightgreen }
|
.snippetEmulator .step, .snippetEmulator .run { color: lightgreen }
|
||||||
.snippetEmulator .reset { color: lightcoral }
|
.snippetEmulator .reset { color: lightcoral }
|
||||||
.snippetEmulator .build, .snippetEmulator .edit { color: dodgerblue }
|
.snippetEmulator .build, .snippetEmulator .edit { color: dodgerblue }
|
||||||
|
.snippetEmulator .highlight { background-color: darkgoldenrod }
|
||||||
|
.snippetEmulator .src { overflow: scroll }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import './snippet_emulator.css'
|
import './snippet_emulator.css'
|
||||||
import React, {useState, useCallback, useEffect} from 'react'
|
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 }) {
|
export default function({ children }) {
|
||||||
if (React.Children.count(children) !== 1) {
|
if (React.Children.count(children) !== 1) {
|
||||||
@@ -12,7 +12,19 @@ export default function({ children }) {
|
|||||||
const [message, setMessage] = useState('') // A status / error message
|
const [message, setMessage] = useState('') // A status / error message
|
||||||
const onChangeSrc = useCallback((e) => setSrc(e.target.value), []) // Callback for the textarea
|
const onChangeSrc = useCallback((e) => setSrc(e.target.value), []) // Callback for the textarea
|
||||||
const [binary, setBinary] = useState(null) // The assembled binary
|
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
|
// A callback to build the binary from source
|
||||||
const rebuild = useCallback((code) => {
|
const rebuild = useCallback((code) => {
|
||||||
@@ -20,7 +32,7 @@ export default function({ children }) {
|
|||||||
const bin = assemble_snippet(code)
|
const bin = assemble_snippet(code)
|
||||||
const sm = source_map(code)
|
const sm = source_map(code)
|
||||||
setBinary(bin)
|
setBinary(bin)
|
||||||
setSourceMap(sm)
|
setSourceMap(sm.Ok || {})
|
||||||
setMessage(`Assembled ${bin.length} bytes`)
|
setMessage(`Assembled ${bin.length} bytes`)
|
||||||
return true
|
return true
|
||||||
} catch(e) {
|
} 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
|
// On load, clean the source and build the stuff
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const lines = React.Children.toArray(children)[0].split('\n')
|
const lines = React.Children.toArray(children)[0].split('\n')
|
||||||
const cleaned = lines.map(l => l.trim()).join('\n')
|
const cleaned = lines.map(l => l.trim()).join('\n')
|
||||||
setSrc(cleaned)
|
setSrc(cleaned)
|
||||||
rebuild(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) {
|
if (editing) {
|
||||||
return (
|
return (
|
||||||
@@ -49,12 +74,12 @@ export default function({ children }) {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className='snippetEmulator'>
|
<div className='snippetEmulator'>
|
||||||
<SourceDisplay src={src}/>
|
<SourceDisplay src={src} activeLine={activeLine}/>
|
||||||
<div className='message'>{message}</div>
|
<div className='message'>{message}</div>
|
||||||
<div className='buttons'>
|
<div className='buttons'>
|
||||||
<a className='step'>[Step]</a>
|
<a className='step' onClick={step}>[Step]</a>
|
||||||
<a className='step'>[Run]</a>
|
<a className='step'>[Run]</a>
|
||||||
<a className='reset'>[Reset]</a>
|
<a className='reset' onClick={reset}>[Reset]</a>
|
||||||
<a className='edit' onClick={() => { setEditing(true); setMessage('') }}>[Edit]</a>
|
<a className='edit' onClick={() => { setEditing(true); setMessage('') }}>[Edit]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,10 +87,11 @@ export default function({ children }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SourceDisplay({ src }) {
|
function SourceDisplay({ src, activeLine }) {
|
||||||
const lines = src.split('\n')
|
const lines = src.split('\n')
|
||||||
const lineDivs = lines.map((line, i) => {
|
const lineDivs = lines.map((line, i) => {
|
||||||
return (<div key={`line_${i}`}>{line || <> </>}</div>)
|
// Loop indices are from 0, activeLine numbers are from 1
|
||||||
|
return (<div key={`line_${i}`} className={i + 1 === activeLine ? 'highlight' : ''}>{line || <> </>}</div>)
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<div className='src'>{lineDivs}</div>
|
<div className='src'>{lineDivs}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user