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 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) {
|
pub fn set_pc(&mut self, val: u32) {
|
||||||
self.0.set_pc(Word::from(val))
|
self.0.set_pc(Word::from(val))
|
||||||
}
|
}
|
||||||
@@ -122,6 +130,14 @@ impl WasmCPU {
|
|||||||
self.0.run_to_halt()
|
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>) {
|
pub fn load(&mut self, rom: Vec<u8>) {
|
||||||
for (i, b) in rom.iter().enumerate() {
|
for (i, b) in rom.iter().enumerate() {
|
||||||
self.poke(0x400 + i as u32, *b)
|
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'
|
import {WasmCPU, assemble_snippet, source_map, NovaForth} from '../pkg/vweb.js'
|
||||||
|
|
||||||
export default function({ children }) {
|
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) {
|
if (React.Children.count(children) !== 1) {
|
||||||
throw 'Expects a single text node as a child'
|
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 [cpu, setCpu] = useState(new WasmCPU()) // The actual CPU emulator itself
|
||||||
const [activeLine, setActiveLine] = useState(null) // The (0-based) index of the
|
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(() => {
|
const updateActiveLine = useCallback(() => {
|
||||||
sourceMap && setActiveLine(sourceMap.get(cpu.pc()))
|
sourceMap && setActiveLine(sourceMap.get(cpu.pc()))
|
||||||
|
if (cpu.halted()) { setMessage('Halted') }
|
||||||
}, [cpu, sourceMap])
|
}, [cpu, sourceMap])
|
||||||
|
|
||||||
|
// Reset the CPU
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
cpu.reset()
|
cpu.reset()
|
||||||
cpu.start()
|
cpu.start() // Reset leaves Vulcan in a stopped state but since we're going to step through anyway...
|
||||||
updateActiveLine()
|
updateActiveLine()
|
||||||
}, [cpu, 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
|
// A callback to build the binary from source
|
||||||
const rebuild = useCallback((code) => {
|
const rebuild = useCallback((code) => {
|
||||||
try {
|
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)
|
const sm = source_map(code)
|
||||||
setBinary(bin)
|
setBinary(bin)
|
||||||
setSourceMap(sm.Ok || {})
|
setSourceMap(sm.Ok || {})
|
||||||
@@ -40,11 +57,26 @@ export default function({ children }) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Step forward one
|
||||||
const step = useCallback(() => {
|
const step = useCallback(() => {
|
||||||
cpu.tick()
|
cpu.tick()
|
||||||
|
setMessage('')
|
||||||
updateActiveLine()
|
updateActiveLine()
|
||||||
}, [cpu, 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
|
// 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')
|
||||||
@@ -78,10 +110,11 @@ export default function({ children }) {
|
|||||||
<div className='message'>{message}</div>
|
<div className='message'>{message}</div>
|
||||||
<div className='buttons'>
|
<div className='buttons'>
|
||||||
<a className='step' onClick={step}>[Step]</a>
|
<a className='step' onClick={step}>[Step]</a>
|
||||||
<a className='step'>[Run]</a>
|
<a className='step' onClick={runToHalt}>[Run]</a>
|
||||||
<a className='reset' onClick={reset}>[Reset]</a>
|
<a className='reset' onClick={resetBtn}>[Reset]</a>
|
||||||
<a className='edit' onClick={() => { setEditing(true); setMessage('') }}>[Edit]</a>
|
<a className='edit' onClick={() => { setEditing(true); setMessage('') }}>[Edit]</a>
|
||||||
</div>
|
</div>
|
||||||
|
<StackDisplay data={cpu.get_stack()} call={cpu.get_call()}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,5 +132,10 @@ function SourceDisplay({ src, activeLine }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StackDisplay({ data, call }) {
|
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