Finished snippet emulator (for now)
This commit is contained in:
@@ -114,6 +114,14 @@ impl WasmCPU {
|
||||
|
||||
pub fn halted(&self) -> bool { self.0.halted() }
|
||||
|
||||
pub fn get_stack(&self) -> Vec<i32> {
|
||||
self.0.get_stack().into_iter().map(i32::from).collect()
|
||||
}
|
||||
|
||||
pub fn get_call(&self) -> Vec<i32> {
|
||||
self.0.get_call().into_iter().map(i32::from).collect()
|
||||
}
|
||||
|
||||
pub fn set_pc(&mut self, val: u32) {
|
||||
self.0.set_pc(Word::from(val))
|
||||
}
|
||||
@@ -122,6 +130,14 @@ impl WasmCPU {
|
||||
self.0.run_to_halt()
|
||||
}
|
||||
|
||||
pub fn safe_run(&mut self, max_ticks: usize) {
|
||||
let mut current = 0;
|
||||
while !self.0.halted() && current < max_ticks {
|
||||
current += 1;
|
||||
self.tick()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self, rom: Vec<u8>) {
|
||||
for (i, b) in rom.iter().enumerate() {
|
||||
self.poke(0x400 + i as u32, *b)
|
||||
|
||||
@@ -3,6 +3,13 @@ import React, {useState, useCallback, useEffect} from 'react'
|
||||
import {WasmCPU, assemble_snippet, source_map, NovaForth} from '../pkg/vweb.js'
|
||||
|
||||
export default function({ children }) {
|
||||
// Use this by giving it some source as a body:
|
||||
// <SnippetEmulator>
|
||||
// {`.org 0x400
|
||||
// push 3
|
||||
// hlt`}
|
||||
// </SnippetEmulator>
|
||||
// The string-in-braces is so that jsx doesn't eat the newlines and whitespace
|
||||
if (React.Children.count(children) !== 1) {
|
||||
throw 'Expects a single text node as a child'
|
||||
}
|
||||
@@ -16,20 +23,30 @@ export default function({ children }) {
|
||||
const [cpu, setCpu] = useState(new WasmCPU()) // The actual CPU emulator itself
|
||||
const [activeLine, setActiveLine] = useState(null) // The (0-based) index of the
|
||||
|
||||
// Update which line is highlighted. This should be called in most button callbacks.
|
||||
const updateActiveLine = useCallback(() => {
|
||||
sourceMap && setActiveLine(sourceMap.get(cpu.pc()))
|
||||
if (cpu.halted()) { setMessage('Halted') }
|
||||
}, [cpu, sourceMap])
|
||||
|
||||
// Reset the CPU
|
||||
const reset = useCallback(() => {
|
||||
cpu.reset()
|
||||
cpu.start()
|
||||
cpu.start() // Reset leaves Vulcan in a stopped state but since we're going to step through anyway...
|
||||
updateActiveLine()
|
||||
}, [cpu, updateActiveLine])
|
||||
|
||||
// Reset the CPU but also put in a message saying we did. This needs to be a separate fn because the
|
||||
// rebuild handler calls the other one but it sets its own message which we don't want to overwrite
|
||||
const resetBtn = useCallback(() => {
|
||||
reset()
|
||||
setMessage('Reset')
|
||||
}, [reset])
|
||||
|
||||
// A callback to build the binary from source
|
||||
const rebuild = useCallback((code) => {
|
||||
try {
|
||||
const bin = assemble_snippet(code)
|
||||
const bin = assemble_snippet(code) // Build the thing and its source map. This throws if there's a problem
|
||||
const sm = source_map(code)
|
||||
setBinary(bin)
|
||||
setSourceMap(sm.Ok || {})
|
||||
@@ -40,11 +57,26 @@ export default function({ children }) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Step forward one
|
||||
const step = useCallback(() => {
|
||||
cpu.tick()
|
||||
setMessage('')
|
||||
updateActiveLine()
|
||||
}, [cpu, updateActiveLine])
|
||||
|
||||
// Run all the lines at once, until the CPU halts
|
||||
const runToHalt = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
cpu.safe_run(100_000) // Safe run, 100k cycles and pause
|
||||
updateActiveLine()
|
||||
// If we're not halted then we're probably in an infinite loop. That might be fine, but stop running, in case
|
||||
// it's not. If they just hit [run] again then it'll keep on going. For snippets it's likely this indicates
|
||||
// a bug though.
|
||||
if (!cpu.halted()) { setMessage('Paused after 100k cycles')}
|
||||
}, 0)
|
||||
setMessage('Running...')
|
||||
}, [cpu, updateActiveLine])
|
||||
|
||||
// On load, clean the source and build the stuff
|
||||
useEffect(() => {
|
||||
const lines = React.Children.toArray(children)[0].split('\n')
|
||||
@@ -78,10 +110,11 @@ export default function({ children }) {
|
||||
<div className='message'>{message}</div>
|
||||
<div className='buttons'>
|
||||
<a className='step' onClick={step}>[Step]</a>
|
||||
<a className='step'>[Run]</a>
|
||||
<a className='reset' onClick={reset}>[Reset]</a>
|
||||
<a className='step' onClick={runToHalt}>[Run]</a>
|
||||
<a className='reset' onClick={resetBtn}>[Reset]</a>
|
||||
<a className='edit' onClick={() => { setEditing(true); setMessage('') }}>[Edit]</a>
|
||||
</div>
|
||||
<StackDisplay data={cpu.get_stack()} call={cpu.get_call()}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -99,5 +132,10 @@ function SourceDisplay({ src, activeLine }) {
|
||||
}
|
||||
|
||||
function StackDisplay({ data, call }) {
|
||||
|
||||
return (
|
||||
<div className='stacks'>
|
||||
{data && <div><span>Data: </span><span>{data.join(', ')}</span></div>}
|
||||
{call && <div><span>Call: </span><span>{call.join(', ')}</span></div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user