import { range } from "@davidsouther/jiffies/lib/esm/range.js"; import { Pin as ChipPin, Pins, Voltage, } from "@nand2tetris/simulator/chip/chip.js"; import { createContext, useContext, useEffect, useState } from "react"; import { ChipDisplayInfo, getDisplayInfo } from "./pin_display.js"; import "./public/pin.css"; import { ChipSim } from "./stores/chip.store.js"; export const PinContext = createContext({}); export interface ImmPin { bits: [number, Voltage][]; pin: ChipPin; } export function reducePin(pin: ChipPin) { return { pin, bits: range(0, pin.width) .map((i) => [i, pin.voltage(i)] as [number, Voltage]) .reverse(), }; } export function reducePins(pins: Pins): ImmPin[] { return [...pins.entries()].map(reducePin); } export interface PinoutPins { pins: ImmPin[]; toggle?: (pin: ChipPin, bit?: number) => void; } export const FullPinout = (props: { sim: ChipSim; toggle: (pin: ChipPin, i: number | undefined) => void; setInputValid: (pending: boolean) => void; hideInternal?: boolean; }) => { const { inPins, outPins, internalPins } = props.sim; const displayInfo = getDisplayInfo(props.sim.chip[0].name ?? ""); return ( <> {!props.hideInternal && ( )}
); }; export const PinoutBlock = ( props: PinoutPins & { header: string; disabled?: boolean; enableEdit?: boolean; setInputValid?: (valid: boolean) => void; displayInfo: ChipDisplayInfo; }, ) => ( <> {props.pins.length > 0 && ( {props.header} )} {[...props.pins].map((immPin) => ( {immPin.pin.name} ))} ); export const Pinout = ({ pins, toggle, }: { pins: ImmPin[]; toggle?: (pin: ChipPin, bit?: number) => void; }) => { if (pins.length === 0) { return <>None; } return ( {[...pins].map((immPin) => ( ))}
Name Value
{immPin.pin.name}
); }; const Pin = ({ pin, toggle, disabled = false, enableEdit = true, signed = true, setInputValid, internal = false, }: { pin: ImmPin; toggle: ((pin: ChipPin, bit?: number) => void) | undefined; disabled?: boolean; enableEdit?: boolean; signed?: boolean; setInputValid?: (valid: boolean) => void; internal: boolean; }) => { const [isBin, setIsBin] = useState(true); let inputValid = true; const [decimal, setDecimal] = useState(""); const toggleBin = () => { setIsBin(!isBin); }; const resetDispatcher = useContext(PinContext); if (resetDispatcher instanceof PinResetDispatcher) { resetDispatcher.registerCallback(() => { setIsBin(true); }); } const setInputValidity = (valid: boolean) => { inputValid = valid; setInputValid?.(valid); }; const handleDecimalChange = (value: string) => { const positive = value.replace(/[^\d]/g, ""); const numeric = signed && value[0] === "-" ? `-${positive}` : positive; setDecimal(numeric); if (isNaN(parseInt(numeric))) { setInputValidity(false); } else { const newValue = parseInt(numeric); if ( (!signed && newValue >= Math.pow(2, pin.bits.length)) || (signed && (newValue >= Math.pow(2, pin.bits.length - 1) || newValue < -Math.pow(2, pin.bits.length - 1))) ) { setInputValidity(false); } else { updatePins(newValue); setInputValidity(true); } } }; const updatePins = (n: number) => { for (let i = 0; i < pin.bits.length; i++) { if (pin.bits[pin.bits.length - i - 1][1] !== ((n >> i) & 1)) { toggle?.(pin.pin, i); } } }; useEffect(() => { if (!isBin && inputValid) { let value = 0; if (signed && pin.bits[0][1]) { // negative for (const [i, v] of pin.bits) { if (i < pin.bits.length - 1 && !v) { value += 2 ** i; } } value = -value - 1; } else { // positive const limit = signed ? pin.bits.length - 1 : pin.bits.length; for (const [i, v] of pin.bits) { if (i < limit && v) { value += 2 ** i; } } } setDecimal(value.toString()); } }, [pin, isBin]); return (
{isBin ? ( pin.bits.map(([i, v]) => ( )) ) : ( { handleDecimalChange(e.target.value); }} disabled={!enableEdit} /> )}
{pin.bits.length > 1 && ( <>
)}
); }; export class PinResetDispatcher { private callbacks: (() => void)[] = []; registerCallback(callback: () => void) { this.callbacks.push(callback); } reset() { for (const callback of this.callbacks) { callback(); } } }