Finished snippet emulator (for now)

This commit is contained in:
2023-01-28 00:31:14 -06:00
parent 3d7a19d1bd
commit c825e6d727
2 changed files with 59 additions and 5 deletions
+16
View File
@@ -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)
+43 -5
View File
@@ -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:&nbsp;</span><span>{data.join(', ')}</span></div>}
{call && <div><span>Call:&nbsp;</span><span>{call.join(', ')}</span></div>}
</div>
)
}