concat/src/App.tsx

420 lines
9.5 KiB
TypeScript

/* 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 (
<>
<TextArea
onChange={(e: any) => 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}
state={state}
// // hack
// top={document.getElementById("input")?.clientHeight ?? 0}
/>
<Stack state={state} just_popped={state.just_popped} />
<States
states={states}
activeStateIdx={activeStateIdx}
setActiveStateIdx={n => setActiveStateIdx(n)}
/>
</div>
</>
);
}
function Ops({
parsed,
state,
}: // top,
{
parsed: Op[];
state: State;
// top: number;
}): JSX.Element {
return (
<div
className="ops"
children={parsed.map((op, i) => (
<Op
op={op}
highlighted={i == state.ip}
jumped_from={i == state.jumped_from}
num={i}
key={i}
/>
))}
// 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 (
<div
className={`op ${highlighted ? "active" : ""} ${
jumped_from ? "jumped-from" : ""
}`}
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}
onFocus={onMouseEnter}
onClick={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({
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) => (
<StackItem
item={just_popped}
key={-(i + 1)}
highlighted={false}
just_popped={true}
/>
));
ns.map((item, i) => (
<StackItem
item={item}
key={i}
highlighted={
(state.just_pushed && i == 0) || (state.just_swapped && i < 2)
}
/>
)).forEach(child => children.push(child));
return <div className="stack" children={children} {...props} />;
}
function StackItem({
item,
highlighted = false,
just_popped = false,
...props
}: {
item: number;
highlighted: boolean;
just_popped?: boolean;
}): JSX.Element {
return (
<div
className={`stack-item ${highlighted ? "active" : ""} ${
just_popped ? "just-popped" : ""
}`}
{...props}
>
{item}
</div>
);
}
// bad
function Output({ output }: { output: number[] }): JSX.Element {
return (
<div
className="output"
children={output.map((item, i) => (
<span key={i}>{String.fromCharCode(item)}</span>
))}
></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> {
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<number>;
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;
}
}