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 (
| Name |
Value |
{[...pins].map((immPin) => (
| {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 (
{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();
}
}
}