/* 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 (
{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({ 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; } }