341 lines
7.6 KiB
TypeScript
341 lines
7.6 KiB
TypeScript
/* eslint-disable eqeqeq */
|
|
import React, { useState } from "react";
|
|
import "./App.css";
|
|
|
|
export default function App() {
|
|
let [input, setInput] = useState("");
|
|
let [activeStateIdx, setActiveStateIdx] = useState(0);
|
|
let parsed = somes(input.split(/[^a-z0-9-]+/).map(parse_op));
|
|
let states = steps(INITIAL_STATE, parsed);
|
|
return (
|
|
<>
|
|
<textarea
|
|
onChange={e => setInput(e.target.value)}
|
|
value={input}
|
|
className="program"
|
|
title={`\
|
|
type Op =
|
|
| number
|
|
| "add"
|
|
| "sub"
|
|
| "mul"
|
|
| "div"
|
|
| "put"
|
|
| "jmp"
|
|
| "dup"
|
|
| "del"
|
|
| "cmp"
|
|
| "swp";`}
|
|
></textarea>
|
|
<div className="info">
|
|
<Ops
|
|
parsed={parsed}
|
|
ip={(states[activeStateIdx] ?? INITIAL_STATE).ip}
|
|
// // hack
|
|
// top={document.getElementById("input")?.clientHeight ?? 0}
|
|
/>
|
|
<States
|
|
states={states}
|
|
activeStateIdx={activeStateIdx}
|
|
setActiveStateIdx={n => setActiveStateIdx(n)}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function Ops({
|
|
parsed,
|
|
ip,
|
|
}: // top,
|
|
{
|
|
parsed: Op[];
|
|
ip: number;
|
|
// top: number;
|
|
}): JSX.Element {
|
|
return (
|
|
<div
|
|
className="ops"
|
|
children={parsed.map((op, i) => (
|
|
<Op op={op} highlighted={i == ip} num={i} key={i} />
|
|
))}
|
|
// style={{
|
|
// top: `${top + 5}px`,
|
|
// }}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function Op({
|
|
op,
|
|
highlighted,
|
|
num,
|
|
...props
|
|
}: {
|
|
op: Op;
|
|
num: number;
|
|
highlighted: boolean;
|
|
}): JSX.Element {
|
|
return (
|
|
<div
|
|
className={`op ${highlighted ? "active" : ""}`}
|
|
key={num}
|
|
{...props}
|
|
>
|
|
<div className="op-num">{num}: </div>
|
|
{op}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function States({
|
|
states,
|
|
activeStateIdx,
|
|
setActiveStateIdx,
|
|
}: {
|
|
states: State[];
|
|
activeStateIdx: number;
|
|
setActiveStateIdx: (_: number) => any;
|
|
}): JSX.Element {
|
|
return (
|
|
<div
|
|
className="states"
|
|
children={states.map((state, i) => (
|
|
<State
|
|
state={state}
|
|
key={i}
|
|
active={activeStateIdx == i}
|
|
onMouseEnter={() => setActiveStateIdx(i)}
|
|
/>
|
|
))}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function State({
|
|
state,
|
|
active,
|
|
onMouseEnter,
|
|
...props
|
|
}: {
|
|
state: State;
|
|
active: boolean;
|
|
onMouseEnter: () => any;
|
|
}): JSX.Element {
|
|
return (
|
|
<div
|
|
className={`state ${active ? "active" : ""}`}
|
|
onMouseEnter={onMouseEnter}
|
|
{...props}
|
|
>
|
|
<div className="ip">Ip: {state.ip}</div>
|
|
<span>Stack:</span> <Stack stack={state.stack} />
|
|
<span>Output:</span> <Output output={state.output} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Stack({ stack }: { stack: number[] }): JSX.Element {
|
|
let ns = [...stack];
|
|
ns.reverse();
|
|
return (
|
|
<div
|
|
className="stack"
|
|
children={ns.map((item, i) => (
|
|
<StackItem item={item} key={i} />
|
|
))}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function StackItem({ item, ...props }: { item: number }): JSX.Element {
|
|
return (
|
|
<div className="stack-item" {...props}>
|
|
{item}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// bad
|
|
function Output({ output }: { output: number[] }): JSX.Element {
|
|
return (
|
|
<div
|
|
className="output"
|
|
children={output.map((item, i) => (
|
|
<StackItem item={item} key={i} />
|
|
))}
|
|
></div>
|
|
);
|
|
}
|
|
|
|
type Option<T> = T | undefined;
|
|
|
|
function is_some<T>(a: Option<T>): a is T {
|
|
return typeof a != "undefined";
|
|
}
|
|
|
|
function is_none<T>(a: Option<T>): a is undefined {
|
|
return typeof a == "undefined";
|
|
}
|
|
|
|
function cons<T>(x: T, xs: T[]): T[] {
|
|
return [x, ...xs];
|
|
}
|
|
|
|
function somes<T>(os: Option<T>[]): T[] {
|
|
if (os.length == 0) {
|
|
return [];
|
|
} else {
|
|
let first = os[0];
|
|
if (is_some(first)) {
|
|
return cons(first, somes(os.slice(1)));
|
|
} else {
|
|
return somes(os.slice(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
type Op =
|
|
| number
|
|
| "add"
|
|
| "sub"
|
|
| "mul"
|
|
| "div"
|
|
| "put"
|
|
| "jmp"
|
|
| "dup"
|
|
| "del"
|
|
| "cmp"
|
|
| "swp";
|
|
|
|
function parse_int(n: string, radix?: number): Option<number> {
|
|
let maybe_int = parseInt(n, radix);
|
|
// eslint-disable-next-line no-self-compare
|
|
if (maybe_int == maybe_int) {
|
|
return maybe_int;
|
|
}
|
|
}
|
|
|
|
function parse_op(op: string): Option<Op> {
|
|
if (parse_int(op)) {
|
|
return parse_int(op);
|
|
}
|
|
switch (op) {
|
|
case "add":
|
|
case "sub":
|
|
case "mul":
|
|
case "div":
|
|
case "put":
|
|
case "jmp":
|
|
case "dup":
|
|
case "del":
|
|
case "cmp":
|
|
case "swp":
|
|
return op;
|
|
}
|
|
}
|
|
|
|
type State = {
|
|
ip: number;
|
|
stack: number[];
|
|
output: number[];
|
|
};
|
|
|
|
const INITIAL_STATE: State = {
|
|
ip: 0,
|
|
stack: [],
|
|
output: [],
|
|
};
|
|
|
|
function steps(state: State, ops: Op[], limit = 100, num_steps = 0): State[] {
|
|
if (!ops[state.ip] || num_steps > limit) {
|
|
return [state];
|
|
}
|
|
return cons(
|
|
state,
|
|
steps(step(state, ops[state.ip]), ops, limit, num_steps + 1)
|
|
);
|
|
}
|
|
|
|
// all but jmp advance the ip by one
|
|
// cmp: lt => -1, eq => 0, gt => 1
|
|
function step(state: State, op: Op): State {
|
|
let ns = JSON.parse(JSON.stringify(state));
|
|
ns.ip++;
|
|
|
|
function binary_op(
|
|
default_a: number,
|
|
default_b: number,
|
|
f: (a: number, b: number) => number
|
|
) {
|
|
let [b, a] = [ns.stack.pop() ?? default_b, ns.stack.pop() ?? default_a];
|
|
ns.stack.push(f(a, b));
|
|
}
|
|
|
|
if (typeof op == "number") {
|
|
ns.stack.push(op);
|
|
} else {
|
|
switch (op) {
|
|
case "add":
|
|
binary_op(0, 0, (a, b) => a + b);
|
|
break;
|
|
case "sub":
|
|
binary_op(0, 0, (a, b) => a - b);
|
|
break;
|
|
case "mul":
|
|
binary_op(0, 1, (a, b) => a * b);
|
|
break;
|
|
case "div":
|
|
binary_op(0, 1, (a, b) => a / b);
|
|
break;
|
|
case "put": {
|
|
let popped = ns.stack.pop();
|
|
if (typeof popped != "undefined") {
|
|
ns.output.push(popped);
|
|
}
|
|
break;
|
|
}
|
|
case "jmp":
|
|
ns.ip--;
|
|
ns.ip += ns.stack.pop() ?? 1;
|
|
break;
|
|
case "dup": {
|
|
let popped = ns.stack.pop();
|
|
if (typeof popped != "undefined") {
|
|
ns.stack.push(popped, popped);
|
|
}
|
|
break;
|
|
}
|
|
case "del":
|
|
ns.stack.pop();
|
|
break;
|
|
case "cmp": {
|
|
let [a, b] = [ns.stack.pop() ?? 0, ns.stack.pop() ?? 0];
|
|
// matter of taste
|
|
ns.stack.push(cmp(b, a));
|
|
break;
|
|
}
|
|
case "swp": {
|
|
let [a, b] = [ns.stack.pop(), ns.stack.pop()];
|
|
if (typeof a == "undefined" || typeof b == "undefined") {
|
|
break;
|
|
}
|
|
ns.stack.push(a, b);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ns;
|
|
}
|
|
|
|
function cmp(a: number, b: number): number {
|
|
if (a < b) {
|
|
return -1;
|
|
} else if (a == b) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|