/* eslint-disable eqeqeq */
import React, { useState } from "react";
import { useHash } from "react-use";
import TextArea from "./TextArea";
import "./App.css";
export default function App() {
let [hash, setHash] = useHash();
let input = decodeURI(hash.slice(1));
function setInput(s: string) {
setHash(encodeURI(s));
}
let [activeStateIdx, setActiveStateIdx] = useState(0);
let parsed = somes(input.split(/[^a-z0-9-]+/).map(parse_op));
let states = steps(INITIAL_STATE, parsed);
let state = states[activeStateIdx] ?? INITIAL_STATE;
return (
<>
setActiveStateIdx(n)}
/>
>
);
}
function Ops({
parsed,
state,
}: // top,
{
parsed: Op[];
state: State;
// top: number;
}): JSX.Element {
return (
(
))}
// style={{
// top: `${top + 5}px`,
// }}
/>
);
}
function Op({
op,
highlighted,
num,
jumped_from,
...props
}: {
op: Op;
num: number;
highlighted: boolean;
jumped_from: boolean;
}): JSX.Element {
return (
);
}
function States({
states,
activeStateIdx,
setActiveStateIdx,
}: {
states: State[];
activeStateIdx: number;
setActiveStateIdx: (_: number) => any;
}): JSX.Element {
return (
(
setActiveStateIdx(i)}
/>
))}
/>
);
}
function State({
state,
active,
onMouseEnter,
...props
}: {
state: State;
active: boolean;
onMouseEnter: () => any;
}): JSX.Element {
return (
Ip: {state.ip}
{/*
Stack: */}
Output:
);
}
function Stack({
state,
just_popped,
...props
}: {
state: State;
just_popped?: number[];
}): JSX.Element {
let ns = [...state.stack];
ns.reverse();
console.log(just_popped);
let children = (just_popped ?? []).map((just_popped, i) => (
));
ns.map((item, i) => (
)).forEach(child => children.push(child));
return ;
}
function StackItem({
item,
highlighted = false,
just_popped = false,
...props
}: {
item: number;
highlighted: boolean;
just_popped?: boolean;
}): JSX.Element {
return (
{item}
);
}
// bad
function Output({ output }: { output: number[] }): JSX.Element {
return (
(
{String.fromCharCode(item)}
))}
>
);
}
type Option = T | undefined;
function is_some(a: Option): a is T {
return typeof a != "undefined";
}
function is_none(a: Option): a is undefined {
return typeof a == "undefined";
}
function cons(x: T, xs: T[]): T[] {
return [x, ...xs];
}
function somes(os: Option[]): 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 {
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 {
let maybe_int = parse_int(op);
if (is_some(maybe_int)) {
return maybe_int;
}
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[];
jumped_from: Option;
just_pushed: boolean;
just_swapped: boolean;
just_popped?: number[];
};
const INITIAL_STATE: State = {
ip: 0,
stack: [],
output: [],
jumped_from: undefined,
just_pushed: false,
just_swapped: false,
};
function steps(state: State, ops: Op[], limit = 300, num_steps = 0): State[] {
if (!(state.ip in ops) || 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)) as State;
ns.jumped_from = ns.ip;
ns.just_pushed = false;
ns.just_swapped = false;
ns.just_popped = undefined;
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);
}
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.jumped_from = 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.just_swapped = true;
ns.stack.push(a, b);
break;
}
}
if (ns.stack.length > state.stack.length) {
ns.just_pushed = true;
} else if (ns.stack.length < state.stack.length) {
ns.just_popped = state.stack.slice(ns.stack.length - 1).reverse();
if (state.stack.length - ns.stack.length == 1) {
ns.just_pushed = true;
}
}
return ns;
}
function cmp(a: number, b: number): number {
if (a < b) {
return -1;
} else if (a == b) {
return 0;
} else {
return 1;
}
}