Web-Ide mit aufgenommen

This commit is contained in:
Riwoldt
2026-04-09 14:14:56 +02:00
parent 64816c45cc
commit 15cfaf332d
489 changed files with 186891 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
import {
COMMANDS_ALU,
COMMANDS_OP,
Flags,
} from "@nand2tetris/simulator/cpu/alu.js";
export const ALUComponent = ({
A,
op,
D,
out,
flag,
}: {
A: number;
op: COMMANDS_OP;
D: number;
out: number;
flag: keyof typeof Flags;
}) => (
<div className="alu">
<span>ALU</span>
<svg width="250" height="250" version="1.1">
<defs>
<rect
x="34.442518"
y="54.335354"
width="0.91770717"
height="20.780869"
/>
</defs>
<g>
<polygon
points="70,10 180,85 180,165 70,240 70,135 90,125 70,115"
stroke="#000"
fill="#6D97AB"
/>
<text
xmlSpace="preserve"
textAnchor="middle"
y="61"
x="35"
// fill="#000000" // use style from chip.scss
>
{A}
</text>
<text
xmlSpace="preserve"
textAnchor="middle"
y="176"
x="35"
// fill="#000000" // use style from chip.scss
>
{D}
</text>
<text
xmlSpace="preserve"
textAnchor="middle"
y="121"
x="207"
// fill="#000000" // use style from chip.scss
>
{out}
</text>
<text
xmlSpace="preserve"
y="130.50002"
x="110.393929"
fill="#ffffff"
fontSize={24}
>
{COMMANDS_ALU.op[op] ?? "(??)"}
</text>
<g>
<path /*stroke="black"*/ d="M 6,67.52217 H 68.675994" />
<path
/*stroke="black"*/ d="M 68.479388,67.746136 60.290279,61.90711"
/>
<path
/*stroke="black"*/ d="m 68.479388,67.40711 -8.189109,5.839026"
/>
</g>
<g transform="translate(0,115.5)">
<path /*stroke="black"*/ d="M 6,67.52217 H 68.675994" />
<path
d="M 68.479388,67.746136 60.290279,61.90711" /*stroke="black"*/
/>
<path
/*stroke="black"*/ d="m 68.479388,67.40711 -8.189109,5.839026"
/>
</g>
<g transform="translate(176,57.5)">
<path /*stroke="black"*/ d="M 6,67.52217 H 68.675994" />
<path
/*stroke="black"*/ d="M 68.479388,67.746136 60.290279,61.90711"
/>
<path
/*stroke="black"*/ d="m 68.479388,67.40711 -8.189109,5.839026"
/>
</g>
</g>
</svg>
</div>
);
@@ -0,0 +1,155 @@
import { KeyboardAdapter } from "@nand2tetris/simulator/cpu/memory.js";
import { useEffect, useRef, useState } from "react";
import { RegisterComponent } from "./register.js";
const KeyMap: Record<string, number | undefined> = {
// Delete: 127,
Enter: 128,
Backspace: 129,
ArrowLeft: 130,
ArrowUp: 131,
ArrowRight: 132,
ArrowDown: 133,
Home: 134,
End: 135,
PageUp: 136,
PageDown: 137,
Insert: 138,
Delete: 139,
Escape: 140,
F1: 141,
F2: 142,
F3: 143,
F4: 144,
F5: 145,
F6: 146,
F7: 147,
F8: 148,
F9: 149,
F10: 150,
F11: 151,
F12: 152,
};
const keyDisplays: Record<string, string> = {
ArrowLeft: "L-arrow",
ArrowUp: "U-arrow",
ArrowRight: "R-arrow",
ArrowDown: "D-arrow",
};
function getKeyDisplay(key: string) {
return keyDisplays[key] ?? key;
}
function keyPressToHackCharacter(keypress: KeyboardEvent): number {
const mapping = KeyMap[keypress.key];
if (mapping !== undefined) {
return mapping;
}
if (keypress.key.length === 1) {
const code = keypress.key.charCodeAt(0);
if (code >= 32 && code <= 126) {
return code;
}
}
return 0;
}
export const Keyboard = ({
keyboard,
update,
}: {
keyboard: KeyboardAdapter;
update?: () => void;
}) => {
const [enabled, setEnabled] = useState(false);
const [character, setCharacter] = useState("");
const [bits, setBits] = useState(keyboard.getKey());
let currentKey = 0;
const toggleRef = useRef<HTMLButtonElement>(null);
const toggleEnabled = () => {
setEnabled(!enabled);
};
const onKeyDown = (event: KeyboardEvent) => {
if (!enabled) {
return;
}
setCharacter(getKeyDisplay(event.key));
toggleRef.current?.blur();
const key = keyPressToHackCharacter(event);
if (key) {
event.preventDefault();
}
if (key === currentKey) {
return;
}
setKey(key);
update?.();
};
const onKeyUp = (event: KeyboardEvent) => {
toggleRef.current?.blur();
if (!enabled) {
return;
}
if (keyboard.getKey()) {
event.preventDefault();
}
currentKey = 0;
keyboard.clearKey();
update?.();
setBits(keyboard.getKey());
setCharacter("");
};
// note on setCharacter vs setKey:
// setCharacter sets the string value that will be displayed in the component,
// while setKey actually sets and tracks the value that will be stored in the keyboard memory
const setKey = (key: number) => {
if (key === 0) {
return;
}
keyboard.setKey(key);
setBits(keyboard.getKey());
currentKey = key;
};
useEffect(() => {
window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("keyup", onKeyUp);
};
});
return (
<article className="panel">
<div className="flex row align-baseline">
<button
onClick={toggleEnabled}
ref={toggleRef}
className="flex-0"
style={{ whiteSpace: "pre" }}
>
{`${enabled ? "Disable" : "Enable"} Keyboard`}
</button>
<div className="flex-1"></div> {/* padding */}
<div className="flex-4">Key: {character}</div>
<div className="flex-4">
<RegisterComponent name="Char code" bits={bits} />
</div>
</div>
</article>
);
};
@@ -0,0 +1,38 @@
import { Memory as MemoryChip } from "@nand2tetris/simulator/cpu/memory.js";
import { range } from "@davidsouther/jiffies/lib/esm/range.js";
import { render, screen } from "@testing-library/react";
import { MemoryBlock, MemoryCell } from "./memory.js";
describe("<Memory />", () => {
describe("<MemoryCell />", () => {
it("renders a read-only cell", () => {
render(<MemoryCell index={16} value={"34"} />);
const addr = screen.getByText("16");
expect(addr).toBeVisible();
const cell = screen.getByText("34");
expect(cell).toBeVisible();
});
});
describe("<MemoryBlock />", () => {
it.skip("renders a small amount of memory", () => {
const memory = new MemoryChip(
new Int16Array(
range(0, 16).map((i) => (Math.pow(i, 12) ^ 0x9753) & 0xffff)
).buffer
);
render(<MemoryBlock memory={memory} />);
const zero = screen.getByText("0x0000");
expect(zero).toBeVisible();
// const indexes = document.querySelectorAll("code:nth-of-type(even)");
// expect(indexes.length).toBe(16);
// const cells = document.querySelectorAll("code:nth-of-type(even)");
// expect(cells.length).toBe(16);
});
});
});
@@ -0,0 +1,396 @@
import { rounded } from "@davidsouther/jiffies/lib/esm/dom/css/border.js";
import {
forwardRef,
ReactNode,
useCallback,
useContext,
useImperativeHandle,
useMemo,
useState,
} from "react";
import {
Format,
FORMATS,
MemoryAdapter,
} from "@nand2tetris/simulator/cpu/memory.js";
import { loadAsm, loadBlob, loadHack } from "@nand2tetris/simulator/loader.js";
import { asm } from "@nand2tetris/simulator/util/asm.js";
import { bin, dec, hex } from "@nand2tetris/simulator/util/twos.js";
import { useClockReset } from "../clockface.js";
import InlineEdit from "../inline_edit.js";
import { LOADING } from "../messages.js";
import { useStateInitializer } from "../react.js";
import { BaseContext } from "../stores/base.context.js";
import VirtualScroll, { VirtualScrollSettings } from "../virtual_scroll.js";
const ITEM_HEIGHT = 34;
export const MemoryBlock = ({
memory,
jmp = { value: 0 },
highlight = -1,
editable = false,
justifyLeft = false, // TODO: handle this in css in the future
count,
maxSize,
offset = 0,
cellLabels,
format = dec,
onChange = () => undefined,
onFocus = () => undefined,
}: {
jmp?: { value: number };
memory: MemoryAdapter;
highlight?: number;
editable?: boolean;
justifyLeft?: boolean;
count?: number;
offset?: number;
maxSize?: number;
cellLabels?: string[];
format?: (v: number) => string;
onChange?: (i: number, value: string, previous: number) => void;
onFocus?: (i: number) => void;
}) => {
const settings = useMemo<Partial<VirtualScrollSettings>>(
() => ({
count: Math.min(memory.size, count ?? 25),
maxIndex: maxSize ?? memory.size,
itemHeight: ITEM_HEIGHT,
startIndex: jmp.value,
}),
[memory.size, jmp],
);
const get = useCallback(
(pos: number, count: number): [number, number][] =>
memory
.range(pos + offset, pos + offset + count)
.map((v, i) => [i + pos + offset, v]),
[memory],
);
const row = useCallback(
([i, v]: [number, number]) => (
<MemoryCell
index={i}
value={format(v)}
label={(cellLabels?.[i] ?? "").padStart(
cellLabels ? Math.max(...cellLabels.map((label) => label.length)) : 0,
)}
showLabel={cellLabels != undefined}
size={memory.size}
editable={editable}
justifyLeft={justifyLeft}
highlight={i === highlight}
onChange={onChange}
onFocus={onFocus}
/>
),
[format, editable, highlight, onChange],
);
return (
<VirtualScroll<[number, number], ReactNode>
settings={settings}
get={get}
row={row}
rowKey={([i]) => i}
/>
);
};
export const MemoryCell = ({
index,
value,
label,
showLabel = false,
size,
highlight = false,
editable = false,
justifyLeft = false,
onChange = () => undefined,
onFocus = () => undefined,
}: {
index: number;
value: string;
label?: string;
showLabel?: boolean;
size?: number;
highlight?: boolean;
editable?: boolean;
justifyLeft?: boolean;
onChange?: (i: number, value: string, previous: number) => void;
onFocus?: (i: number) => void;
}) => (
<div style={{ display: "flex", height: "100%" }}>
{showLabel && (
<code
style={{
...rounded("none"),
...(highlight ? { background: "var(--mark-background-color)" } : {}),
whiteSpace: "pre",
}}
>
{label ?? ""}
</code>
)}
<code
style={{
...rounded("none"),
...(highlight ? { background: "var(--mark-background-color)" } : {}),
whiteSpace: "pre",
}}
>
{size
? dec(index).padStart(Math.ceil(Math.log10(size)), " ")
: dec(index)}
</code>
<code
style={{
flex: "1",
textAlign: justifyLeft ? "left" : "right",
color: "var(--text-color)",
...rounded("none"),
...(highlight ? { background: "var(--mark-background-color)" } : {}),
}}
>
{editable ? (
<InlineEdit
value={value}
highlight={highlight}
onChange={(newValue: string) =>
onChange(index, newValue, Number(value))
}
onFocus={() => onFocus(index)}
/>
) : (
<span style={{ color: "var(--text-color)" }}>{value}</span>
)}
</code>
</div>
);
export const Memory = forwardRef(
(
{
name = "Memory",
className,
displayEnabled = true,
highlight = -1,
editable = true,
memory,
format = "dec",
onSetFormat,
excludedFormats = [],
count,
maxSize,
offset,
initialAddr,
cellLabels,
fileSelect,
showClear = true,
onChange = undefined,
onClear = undefined,
loadTooltip = undefined,
}: {
name?: string;
className?: string;
displayEnabled?: boolean;
editable?: boolean;
highlight?: number;
memory: MemoryAdapter;
count?: number;
maxSize?: number;
offset?: number;
initialAddr?: number;
format: Format;
onSetFormat?: (format: Format) => void;
excludedFormats?: Format[];
cellLabels?: string[];
fileSelect?: () => Promise<{ name: string; content: string }>;
showClear?: boolean;
onChange?: () => void;
onClear?: () => void;
loadTooltip?: { value: string; placement: string };
},
ref,
) => {
const [fmt, setFormat] = useStateInitializer(format);
const [jmp, setJmp] = useState("");
const [goto, setGoto] = useState({ value: initialAddr ?? 0 });
const [highlighted, setHighlighted] = useStateInitializer(highlight);
const [renderKey, setRenderKey] = useState(0);
const jumpTo = () => {
const value =
!isNaN(parseInt(jmp)) && isFinite(parseInt(jmp)) ? Number(jmp) : 0;
setHighlighted(value);
setGoto({
value: value,
});
rerenderMemoryBlock();
};
const doLoad = async () => {
onChange?.();
if (fileSelect) {
const { name, content } = await fileSelect();
setStatus(LOADING);
requestAnimationFrame(async () => {
const loader = name.endsWith("hack")
? loadHack
: name.endsWith("asm")
? loadAsm
: loadBlob;
requestAnimationFrame(async () => {
try {
const bytes = await loader(content);
memory.loadBytes(bytes);
setStatus("");
setFormat(
name.endsWith("hack")
? "bin"
: name.endsWith("asm")
? "asm"
: fmt,
);
jumpTo();
} catch (e) {
setStatus({
message: `Error loading memory: ${(e as Error).message}`,
severity: "ERROR",
});
return;
}
});
});
}
};
const { setStatus } = useContext(BaseContext);
const rerenderMemoryBlock = () => {
setRenderKey(renderKey + 1);
};
useImperativeHandle(ref, () => ({
rerender: rerenderMemoryBlock,
}));
const clear = () => {
memory.reset();
onChange?.();
onClear?.();
rerenderMemoryBlock();
};
const doUpdate = (i: number, v: string) => {
memory.update(i, v, fmt ?? "dec");
onChange?.();
rerenderMemoryBlock();
};
useClockReset(() => {
setJmp("");
setGoto({ value: 0 });
});
const doSetFormat = (format: Format) => {
setFormat(format);
onSetFormat?.(format);
};
return (
<article className={`panel memory ${className ?? name}`}>
<header>
<div style={{ whiteSpace: "nowrap" }}>{name}</div>
<fieldset role="group">
{fileSelect && (
<button
onClick={doLoad}
className="flex-0"
data-tooltip={loadTooltip?.value ?? "Load file"}
data-placement={loadTooltip?.placement ?? "bottom"}
>
{/* <Icon name="upload_file" /> */}
📂
</button>
)}
{showClear && (
<button
onClick={clear}
className="flex-0"
data-tooltip={"Clear"}
data-placement="bottom"
>
{/* <Icon name="upload_file" /> */}
🆑
</button>
)}
<input
style={{ width: "4em", height: "100%" }}
placeholder="Addr"
value={jmp}
onKeyDown={({ key }) => key === "Enter" && jumpTo()}
onChange={({ target: { value } }) => setJmp(value)}
/>
<button
onClick={jumpTo}
className="flex-0"
data-tooltip={"Scroll to address"}
data-placement="bottom"
>
{/* <Icon name="move_down" /> */}
</button>
<select value={fmt} onChange={(e) => doSetFormat(e.target.value)}>
{FORMATS.filter(
(option) => !excludedFormats.includes(option),
).map((option) => (
<option key={option}>{option}</option>
))}
</select>
</fieldset>
</header>
{displayEnabled ? (
<MemoryBlock
key={renderKey}
jmp={goto}
memory={memory}
highlight={highlighted}
editable={editable}
justifyLeft={fmt == "asm"}
count={count}
format={(v: number) => doFormat(fmt, v)}
cellLabels={cellLabels}
maxSize={maxSize}
offset={offset}
onChange={doUpdate}
onFocus={(i) => setHighlighted(i)}
/>
) : (
"Memory display is disabled"
)}
</article>
);
},
);
Memory.displayName = "Memory";
export default Memory;
function doFormat(format: Format, v: number): string {
switch (format) {
case "bin":
return bin(v);
case "hex":
return hex(v);
case "asm":
return asm(v);
case "dec":
default:
return dec(v);
}
}
@@ -0,0 +1,13 @@
import { dec } from "@nand2tetris/simulator/util/twos.js";
export const RegisterComponent = ({
name,
bits,
}: {
name: string;
bits: number;
}) => (
<div>
{name}: {dec(bits)}
</div>
);
@@ -0,0 +1,153 @@
import { assertExists } from "@davidsouther/jiffies/lib/esm/assert.js";
import { Memory } from "@nand2tetris/simulator/cpu/memory.js";
import { useCallback, useRef, useState } from "react";
import { useClockFrame, useClockReset } from "../clockface.js";
const WHITE = "white";
const BLACK = "black";
type COLOR = typeof WHITE | typeof BLACK;
export interface ScreenMemory {
get(idx: number): number;
}
export function reduceScreen(memory: Memory, offset = 0): ScreenMemory {
return {
get(idx: number): number {
return memory.get(offset + idx);
},
};
}
function get(mem: ScreenMemory, x: number, y: number): COLOR {
const byte = mem.get(32 * y + ((x / 16) | 0));
const bit = byte & (1 << x % 16);
return bit === 0 ? WHITE : BLACK;
}
function set(data: Uint8ClampedArray, x: number, y: number, value: COLOR) {
const pixel = (y * 512 + x) * 4;
const color = value === WHITE ? 255 : 0;
data[pixel] = color;
data[pixel + 1] = color;
data[pixel + 2] = color;
data[pixel + 3] = 255;
}
function drawImage(ctx: CanvasRenderingContext2D, memory: ScreenMemory) {
const image = assertExists(
ctx.getImageData(0, 0, 512, 256),
"Failed to create Context2d",
);
for (let col = 0; col < 512; col++) {
for (let row = 0; row < 256; row++) {
const color = get(memory, col, row);
set(image.data, col, row, color);
}
}
ctx.putImageData(image, 0, 0);
}
export type ScreenScales = 0 | 1 | 2;
export const Screen = ({
memory,
showScaleControls = false,
scale = 1,
onScale,
}: {
memory: ScreenMemory;
showScaleControls?: boolean;
scale?: ScreenScales;
onScale?: (scale: ScreenScales) => void;
}) => {
const canvas = useRef<HTMLCanvasElement>();
const [screenScale, setScreenScale] = useState<ScreenScales>(scale);
const onScaleCB = (scale: ScreenScales) => {
onScale?.(scale);
setScreenScale(scale);
};
const draw = useCallback(() => {
const ctx =
canvas.current?.getContext("2d", { willReadFrequently: true }) ??
undefined;
if (ctx) {
drawImage(ctx, memory);
}
}, [memory]);
const ctxRef = useCallback(
(ref: HTMLCanvasElement | null) => {
canvas.current = ref ?? undefined;
draw();
},
[canvas, draw],
);
useClockFrame(draw);
useClockReset(() => {
canvas.current
?.getContext("2d")
?.clearRect(0, 0, canvas.current.width, canvas.current.height);
});
return (
<article className="panel">
<header>
<div>Screen</div>
{showScaleControls && (
<fieldset role="group">
<button
aria-current={screenScale === 0}
onClick={() => onScaleCB(0)}
>
x0
</button>
<button
aria-current={screenScale === 1}
onClick={() => onScaleCB(1)}
>
x1
</button>
<button
aria-current={screenScale === 2}
onClick={() => onScaleCB(2)}
>
x2
</button>
</fieldset>
)}
</header>
{screenScale > 0 && (
<main style={{ backgroundColor: "var(--code-background-color)" }}>
<figure
style={{
width: `${512 * screenScale}px`,
height: `${256 * screenScale}px`,
boxSizing: "content-box",
marginInline: "auto",
margin: "auto",
borderTop: "2px solid gray",
borderLeft: "2px solid gray",
borderBottom: "2px solid lightgray",
borderRight: "2px solid lightgray",
}}
>
<canvas
ref={ctxRef}
width={512}
height={256}
style={{
transform: `translate(-50%, -50%) scale(${screenScale}) translate(50%, 50%)`,
imageRendering: "pixelated",
}}
></canvas>
</figure>
</main>
)}
</article>
);
};
@@ -0,0 +1,29 @@
import { ALU } from "@nand2tetris/simulator/chip/builtins/index.js";
import { Chip } from "@nand2tetris/simulator/chip/chip.js";
import { render, screen } from "@testing-library/react";
import { makeVisualization, makeVisualizationsWithId } from "./visualizations";
describe("visualizations", () => {
it("returns empty for chips with no parts", () => {
const chip = new Chip([], [], "test");
expect(makeVisualization(chip)).toBeUndefined();
expect(makeVisualizationsWithId({ parts: [chip] })).toEqual([]);
});
it("returns vis for builtins", async () => {
const alu = new ALU();
const vis = makeVisualizationsWithId({ parts: [alu] });
expect(vis.length).toBe(1);
render(
<>
{vis.map(([k, v]) => (
<div key={k}>{v}</div>
))}
</>,
);
const rendered = await screen.findAllByText(/ALU/);
expect(rendered).toBeDefined();
});
});
@@ -0,0 +1,156 @@
import {
CPU,
Computer,
Keyboard,
ROM32K,
Screen,
} from "@nand2tetris/simulator/chip/builtins/computer/computer.js";
import { ALU } from "@nand2tetris/simulator/chip/builtins/index.js";
import {
PC,
Register,
} from "@nand2tetris/simulator/chip/builtins/sequential/bit.js";
import {
RAM,
RAM8,
} from "@nand2tetris/simulator/chip/builtins/sequential/ram.js";
import { Chip, HIGH } from "@nand2tetris/simulator/chip/chip.js";
import { Flags } from "@nand2tetris/simulator/cpu/alu.js";
import { decode } from "@nand2tetris/simulator/cpu/cpu.js";
import { ReactElement } from "react";
import { NO_SCREEN } from "../stores/chip.store.js";
import { ALUComponent } from "./alu.js";
import { Keyboard as KeyboardComponent } from "./keyboard.js";
import { Memory as MemoryComponent } from "./memory.js";
import { RegisterComponent } from "./register.js";
import { Screen as ScreenComponent } from "./screen.js";
export function getBuiltinVisualization(part: Chip): ReactElement | undefined {
switch (part.name) {
case "Register":
case "ARegister":
case "DRegister":
case "PC":
case "KEYBOARD":
case "RAM8":
case "RAM64":
case "RAM512":
case "RAM4K":
case "RAM16K":
case "ROM32K":
case "Screen":
case "Memory":
default:
return undefined;
}
}
function makeMemoryVisualization(chip: RAM) {
return (
<MemoryComponent
name={chip.name}
memory={chip.memory}
format={chip instanceof ROM32K ? "asm" : "dec"}
highlight={chip.address}
count={5}
/>
);
}
export function makeVisualization(
chip: Chip,
updateAction?: () => void,
parameters?: Set<string>,
): ReactElement | undefined {
if (chip instanceof ALU) {
return (
<ALUComponent
A={chip.in("x").busVoltage}
op={chip.op()}
D={chip.in("y").busVoltage}
out={chip.out().busVoltage}
flag={
(chip.out("zr").voltage() === HIGH
? Flags.Zero
: chip.out("ng").voltage() === HIGH
? Flags.Negative
: Flags.Positive) as keyof typeof Flags
}
/>
);
}
if (chip instanceof Register) {
return (
<RegisterComponent
name={chip.name ?? `Chip ${chip.id}`}
bits={chip.bits}
/>
);
}
if (chip instanceof PC) {
return <RegisterComponent name="PC" bits={chip.bits} />;
}
if (chip instanceof Keyboard) {
return <KeyboardComponent keyboard={chip} update={updateAction} />;
}
if (chip instanceof Screen) {
return <ScreenComponent memory={chip.memory} />;
}
if (chip instanceof RAM) {
return makeMemoryVisualization(chip);
}
if (chip instanceof RAM8) {
return <span>RAM {chip.width}</span>;
}
if (chip instanceof CPU) {
const bits = decode(chip.in("instruction").busVoltage);
return (
<>
<RegisterComponent name={"A"} bits={chip.state.A} />
<RegisterComponent name={"D"} bits={chip.state.D} />
<RegisterComponent name={"PC"} bits={chip.state.PC} />
<ALUComponent
A={bits.am ? chip.in("inM").busVoltage : chip.state.A}
D={chip.state.D}
out={chip.state.ALU}
op={bits.op}
flag={chip.state.flag as keyof typeof Flags}
/>
</>
);
}
if (chip instanceof Computer) {
return (
<>
<RegisterComponent name={"A"} bits={chip.cpu.state.A} />
<RegisterComponent name={"D"} bits={chip.cpu.state.D} />
<RegisterComponent name={"PC"} bits={chip.cpu.state.PC} />
{!parameters?.has(NO_SCREEN) && (
<ScreenComponent memory={chip.ram.screen.memory} />
)}
{makeMemoryVisualization(chip.rom)}
{makeMemoryVisualization(chip.ram.ram)}
</>
);
}
const vis = [...chip.parts]
.map((chip) => makeVisualization(chip, updateAction))
.filter((v) => v !== undefined);
return vis.length > 0 ? <>{vis}</> : undefined;
}
export function makeVisualizationsWithId(
chip: {
parts: Chip[];
},
updateAction?: () => void,
parameters?: Set<string>,
): [string, ReactElement][] {
return [...chip.parts]
.map((part, i): [string, ReactElement | undefined] => [
`${part.id}_${i}`,
makeVisualization(part, updateAction, parameters),
])
.filter(([_, v]) => v !== undefined) as [string, ReactElement][];
}