/* 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 ( <>
setActiveStateIdx(n)} />
); } function Ops({ parsed, ip, }: // top, { parsed: Op[]; ip: number; // top: number; }): JSX.Element { return (
( ))} // style={{ // top: `${top + 5}px`, // }} /> ); } function Op({ op, highlighted, num, ...props }: { op: Op; num: number; highlighted: boolean; }): JSX.Element { return (
{num}:
{op}
); } 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({ stack }: { stack: number[] }): JSX.Element { let ns = [...stack]; ns.reverse(); return (
( ))} /> ); } function StackItem({ item, ...props }: { item: number }): JSX.Element { return (
{item}
); } // bad function Output({ output }: { output: number[] }): JSX.Element { return (
( ))} >
); } 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 { 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; } }