Web-Ide mit aufgenommen
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
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 (
|
||||
<>
|
||||
<style>{`
|
||||
table.pinout th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.pinout tbody td:first-child {
|
||||
text-align: right;
|
||||
--font-size: 1rem;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
border-right: var(--border-width) solid var(--table-border-color);
|
||||
}
|
||||
|
||||
table.pinout tbody button {
|
||||
--font-size: 0.875em;
|
||||
font-family: var(--font-family-monospace);
|
||||
max-width: 2em;
|
||||
}
|
||||
`}</style>
|
||||
<table className="pinout">
|
||||
<tbody>
|
||||
<PinoutBlock
|
||||
pins={inPins}
|
||||
header="Input pins"
|
||||
toggle={props.toggle}
|
||||
setInputValid={props.setInputValid}
|
||||
displayInfo={displayInfo}
|
||||
/>
|
||||
<PinoutBlock
|
||||
pins={outPins}
|
||||
header="Output pins"
|
||||
disabled={props.sim.pending}
|
||||
enableEdit={false}
|
||||
displayInfo={displayInfo}
|
||||
/>
|
||||
{!props.hideInternal && (
|
||||
<PinoutBlock
|
||||
pins={internalPins}
|
||||
header="Internal pins"
|
||||
disabled={props.sim.pending}
|
||||
enableEdit={false}
|
||||
displayInfo={displayInfo}
|
||||
/>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PinoutBlock = (
|
||||
props: PinoutPins & {
|
||||
header: string;
|
||||
disabled?: boolean;
|
||||
enableEdit?: boolean;
|
||||
setInputValid?: (valid: boolean) => void;
|
||||
displayInfo: ChipDisplayInfo;
|
||||
},
|
||||
) => (
|
||||
<>
|
||||
{props.pins.length > 0 && (
|
||||
<tr>
|
||||
<th colSpan={2}>{props.header}</th>
|
||||
</tr>
|
||||
)}
|
||||
{[...props.pins].map((immPin) => (
|
||||
<tr key={immPin.pin.name}>
|
||||
<td>{immPin.pin.name}</td>
|
||||
<td>
|
||||
<Pin
|
||||
pin={immPin}
|
||||
toggle={props.toggle}
|
||||
disabled={props.disabled}
|
||||
enableEdit={props.enableEdit}
|
||||
signed={props.displayInfo.isSigned(immPin.pin.name)}
|
||||
setInputValid={props.setInputValid}
|
||||
internal={props.header === "Internal pins" ? true : false}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
export const Pinout = ({
|
||||
pins,
|
||||
toggle,
|
||||
}: {
|
||||
pins: ImmPin[];
|
||||
toggle?: (pin: ChipPin, bit?: number) => void;
|
||||
}) => {
|
||||
if (pins.length === 0) {
|
||||
return <>None</>;
|
||||
}
|
||||
return (
|
||||
<table className="pinout">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[...pins].map((immPin) => (
|
||||
<tr key={immPin.pin.name}>
|
||||
<td>{immPin.pin.name}</td>
|
||||
<td>
|
||||
<Pin pin={immPin} toggle={toggle} internal />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
<fieldset role="group" style={{ width: `${pin.bits.length}rem` }}>
|
||||
{isBin ? (
|
||||
pin.bits.map(([i, v]) => (
|
||||
<button
|
||||
key={i}
|
||||
disabled={disabled}
|
||||
style={internal ? { backgroundColor: "grey" } : {}}
|
||||
onClick={() => toggle?.(pin.pin, i)}
|
||||
data-testid={`pin-${i}`}
|
||||
>
|
||||
{v}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<input
|
||||
className="colored"
|
||||
value={decimal}
|
||||
onChange={(e) => {
|
||||
handleDecimalChange(e.target.value);
|
||||
}}
|
||||
disabled={!enableEdit}
|
||||
/>
|
||||
)}
|
||||
</fieldset>
|
||||
{pin.bits.length > 1 && (
|
||||
<>
|
||||
<div style={{ width: "1em" }} />
|
||||
<button className="pin-control" onClick={() => toggleBin()}>
|
||||
{isBin ? "dec" : "bin"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export class PinResetDispatcher {
|
||||
private callbacks: (() => void)[] = [];
|
||||
|
||||
registerCallback(callback: () => void) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
reset() {
|
||||
for (const callback of this.callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user