You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.7 KiB
210 lines
5.7 KiB
<svelte:head>
|
|
<title>Buttons the Computer</title>
|
|
</svelte:head>
|
|
|
|
<script>
|
|
import {goto, stores} from '@sapper/app';
|
|
import { ButtonMachine } from 'buttons';
|
|
import Icon from '../components/Icon.svelte';
|
|
const { session } = stores();
|
|
|
|
let code = [
|
|
['PUSH', -10], // start at -10
|
|
['STOR', 'AX'],
|
|
['PUSH', 1], // increment by 1
|
|
['ADD'],
|
|
['JZ', 5], // if we're at 0 jumps to the end
|
|
['JUMP', 1] // the previous test fails so it jumps to loop again
|
|
];
|
|
|
|
let machine = new ButtonMachine(code);
|
|
|
|
let has_error = false;
|
|
|
|
$: has_error = machine.error != '';
|
|
|
|
const data_ops = new Set(['PUSH', 'JZ', 'JNZ', 'JUMP', 'STOR', 'RSTOR', 'HALT']);
|
|
|
|
const ops = ButtonMachine.operations();
|
|
const registers = ButtonMachine.register_names();
|
|
|
|
const change_op = (i, count) => {
|
|
let cur_op = code[i][0];
|
|
let cur_index = ops.indexOf(cur_op);
|
|
let new_index = (cur_index + count) < 0 ? ops.length - 1 : (cur_index + count) % ops.length;
|
|
|
|
code[i][0] = ops[new_index];
|
|
}
|
|
|
|
const op_has_data = (op) => data_ops.has(op);
|
|
|
|
const run_machine = () => {
|
|
machine.run();
|
|
machine = machine; // make svelte go
|
|
}
|
|
|
|
const step_machine = () => {
|
|
machine.step();
|
|
machine = machine;
|
|
}
|
|
|
|
const reset_machine = () => {
|
|
machine = new ButtonMachine(code);
|
|
}
|
|
|
|
const clone_line = (i) => {
|
|
let clone = code[i];
|
|
// whew! clone the array without the 2nd one so it's empty
|
|
code.splice(i+1, 0, [clone[0]]);
|
|
code = code;
|
|
}
|
|
|
|
const delete_line = (i) => {
|
|
let item = code.splice(i, 1);
|
|
code = code;
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@import "sass/_variables";
|
|
|
|
#status-panel {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.active-line {
|
|
background-color: $green !important;
|
|
}
|
|
|
|
.empty-data {
|
|
width: 100%;
|
|
background-color: lighten($primary-color, 40%);
|
|
}
|
|
|
|
.off-canvas-toggle {
|
|
position: unset;
|
|
z-index: unset;
|
|
top: unset;
|
|
left: unset;
|
|
display: unset;
|
|
transition: unset;
|
|
padding: 5px;
|
|
}
|
|
|
|
#reg-select {
|
|
background-color: $gray;
|
|
width: 100%;
|
|
}
|
|
|
|
#reg-select select {
|
|
background-color: $gray;
|
|
width: 100%;
|
|
}
|
|
|
|
#machine-state {
|
|
font-size: 20px;
|
|
overflow-x: none;
|
|
white-space: pre-wrap;
|
|
white-space: -moz-pre-wrap;
|
|
white-space: -pre-wrap;
|
|
white-space: -o-pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
#machine-state code {
|
|
color: lighten($green, 10%) !important;
|
|
}
|
|
|
|
#machine-state .error {
|
|
color: lighten($red, 10%) !important;
|
|
}
|
|
</style>
|
|
|
|
<div class="container grid-lg" id="content" data-testid="buttons-page">
|
|
<div class="columns">
|
|
|
|
<div class="column col-6 col-sm-12 col-md-12 col-xs-12">
|
|
<a alt="Open/Close help sidebar" aria-label="Open/Close help sidebar" class="off-canvas-toggle btn btn-primary btn-action" href="#sidebar-id">
|
|
?¿
|
|
</a>
|
|
|
|
<button class="btn btn-primary" on:click={ run_machine }>
|
|
<Icon code="►" tooltip="Run it" tooltip_right={ true } light={ true }/>
|
|
</button>
|
|
<button class="btn btn-primary" on:click={ step_machine }>
|
|
<Icon code="→" tooltip="Step through" tooltip_right={ true } light={ true }/>
|
|
</button>
|
|
<button class="btn btn-primary" on:click={ reset_machine }>
|
|
<Icon code="■" tooltip="Reset" light={ true }/>
|
|
</button>
|
|
|
|
<div class="divider"></div>
|
|
{#each code as [op, data], i}
|
|
<div class="input-group">
|
|
{#if has_error && machine.error_line == i}
|
|
<Icon name="alert-triangle" color="red" />
|
|
{:else}
|
|
{i}:
|
|
{/if}
|
|
|
|
<button class:active-line={ machine.ip == i }
|
|
on:click={ () => change_op(i, +1) }
|
|
on:contextmenu|preventDefault={ () => change_op(i, -1) }
|
|
class="btn btn-primary input-group-btn">{op}</button>
|
|
|
|
{#if op_has_data(op)}
|
|
{#if op === 'STOR' || op === 'RSTOR'}
|
|
<div class="form-group" id="reg-select">
|
|
<select class="form-select" bind:value={data}>
|
|
{#each registers as reg}
|
|
<option>{reg}</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
{:else if op == 'HALT'}
|
|
<input class:active-line={ machine.ip == i }
|
|
type="text" class="form-input" placeholder="register" bind:value={data}>
|
|
{:else}
|
|
<input class:active-line={ machine.ip == i }
|
|
type="number" class="form-input" placeholder="data" bind:value={data}>
|
|
{/if}
|
|
{:else}
|
|
<div class:active-line={ machine.ip == i } class="empty-data"></div>
|
|
{/if}
|
|
|
|
<button class="btn btn-primary input-group-addon"
|
|
on:click={ () => clone_line(i) }>
|
|
<Icon name="copy" tooltip='Clone it'/>
|
|
</button>
|
|
<button class="btn btn-primary input-group-addon"
|
|
on:click={ () => delete_line(i) }>
|
|
<Icon name="delete" tooltip='Delete it'/>
|
|
</button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="column col-6 col-sm-12 col-md-12 col-xs-12" id="status-panel">
|
|
<pre id="machine-state">
|
|
<code>
|
|
█LINE {machine.ip} █TICK { machine.tick}
|
|
{#if machine.halted }<span class="error">HALT! { machine.error }</span>{/if}
|
|
|
|
╞ STACK ╡
|
|
──────────────────
|
|
{#each [...machine.stack].reverse() as datum, i}
|
|
{#if i == 0}→{i}▐ {datum + '\n'}
|
|
{:else}─────────
|
|
▌{i}▐ {datum + '\n'}{/if}
|
|
{/each}
|
|
|
|
╞ REGISTERS ╡
|
|
──────────────────
|
|
{#each registers as reg }
|
|
[{ reg }]={ (machine.registers[reg] !== undefined ? machine.registers[reg] : '█') + '\n'}
|
|
{/each}
|
|
</code>
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|