Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fill.asm tests + other small test script fixes #213

Merged
merged 13 commits into from
Jan 6, 2024
14 changes: 12 additions & 2 deletions components/src/stores/cpu.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
} from "@nand2tetris/simulator/cpu/memory.js";
import { Span } from "@nand2tetris/simulator/languages/base.js";
import { TST } from "@nand2tetris/simulator/languages/tst.js";
import { HACK } from "@nand2tetris/simulator/testing/mult.js";
import { CPUTest } from "@nand2tetris/simulator/test/cputst.js";
import { HACK } from "@nand2tetris/simulator/testing/mult.js";
import { Dispatch, MutableRefObject, useContext, useMemo, useRef } from "react";
import { compare } from "../compare.js";
import { useImmerReducer } from "../react.js";
Expand Down Expand Up @@ -76,6 +76,7 @@ export function makeCpuStore(
dispatch: MutableRefObject<CpuStoreDispatch>
) {
let test = new CPUTest(new ROM(HACK));
let animate = true;

const reducers = {
update(state: CpuPageState) {
Expand All @@ -95,6 +96,9 @@ export function makeCpuStore(
},

testFinished(state: CpuPageState) {
if (state.test.cmp.trim() === "") {
return;
}
const passed = compare(state.test.cmp.trim(), test.log().trim());
setStatus(
passed
Expand All @@ -109,9 +113,15 @@ export function makeCpuStore(
test.cpu.tick();
},

setAnimate(value: boolean) {
animate = value;
},

testStep() {
const done = test.step();
dispatch.current({ action: "testStep" });
if (animate || done) {
DavidSouther marked this conversation as resolved.
Show resolved Hide resolved
dispatch.current({ action: "testStep" });
}
if (done) {
dispatch.current({ action: "testFinished" });
}
Expand Down
54 changes: 54 additions & 0 deletions projects/src/project_04/02_fill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const tst = `// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/04/fill/Fill.tst

// Tests the Fill.hack program in the CPU emulator.

echo "Make sure the highest speed is selected. Then, select the keyboard, press any key for some time, and inspect the screen.";

repeat {
ticktock;
}`;

export const autoTst = `// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/04/fill/FillAutomatic.tst

// This script can be used to test the Fill program automatically,
// rather than interactively. Specifically, the script sets the keyboard
// memory map (RAM[24576]) to 0, runs a million cycles, then it sets it
// to 1, runs a million cycles, then it sets it again to 0, and runs a
// million cycles. These actions simulate the events of leaving the keyboard
// untouched, pressing some key, and then releasing the key.
// After each on of these simulated events, the script outputs the values
// of selected registers from the screen memory map (RAM[16384] - RAM[24576]).
// This is done in order to test that these registers are set to 000...0 or to
// 111....1 (-1 in decimal), as mandated by how the Fill program should
// react to keyboard events.

output-list RAM[16384]%D2.6.2 RAM[17648]%D2.6.2 RAM[18349]%D2.6.2 RAM[19444]%D2.6.2 RAM[20771]%D2.6.2 RAM[21031]%D2.6.2 RAM[22596]%D2.6.2 RAM[23754]%D2.6.2 RAM[24575]%D2.6.2;

set RAM[24576] 0; // the keyboard is untouched
repeat 1000000 {
ticktock;
}
output; // tests that the screen is white

set RAM[24576] 1; // a keyboard key is pressed (1 is an arbitrary non-zero value)
repeat 1000000 {
ticktock;
}
output; // tests that the screen is black

set RAM[24576] 0; // the keyboard in untouched
repeat 1000000 {
ticktock;
}
output;`;

export const autoCmp = `|RAM[16384]|RAM[17648]|RAM[18349]|RAM[19444]|RAM[20771]|RAM[21031]|RAM[22596]|RAM[23754]|RAM[24575]|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |`;
6 changes: 6 additions & 0 deletions projects/src/project_04/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { FileSystem, reset } from "@davidsouther/jiffies/lib/esm/fs.js";

import * as Mult from "./01_mult.js";
import * as Fill from "./02_fill.js";

export const TESTS = {
Mult: {
"Mult.tst": Mult.tst,
"Mult.cmp": Mult.cmp,
},
Fill: {
"Fill.tst": Fill.tst,
"FillAutomatic.tst": Fill.autoTst,
"FillAutomatic.cmp": Fill.autoCmp,
},
};

export async function resetFiles(fs: FileSystem): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion simulator/src/languages/tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ tstSemantics.addAttribute<TstStatement>("statement", {
TstRepeat(op, count, _o, statements, _c) {
return {
statements: statements.children.map(({ statement }) => statement),
count: count.child(0)?.value ?? -1,
count: count.sourceString ? Number(count.sourceString) : -1,
span: span(this.source),
};
},
Expand Down
4 changes: 2 additions & 2 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import Header from "./shell/header";
import { Settings } from "./shell/settings";
import urls from "./urls";

import { ErrorBoundary, RenderError } from "./ErrorBoundary";
import { Redirect } from "./pages/redirect";
import "./pico/flex.scss";
import "./pico/pico.scss";
import "./pico/tooltip.scss";
import { TrackingBanner } from "./tracking";
import { ErrorBoundary, RenderError } from "./ErrorBoundary";
import { updateVersion } from "./versions";
import { Redirect } from "./pages/redirect";

i18n.load("en", messages.messages);
i18n.load("en-PL", plMessages.messages);
Expand Down
3 changes: 3 additions & 0 deletions web/src/pages/cpu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ export const CPU = () => {
out={[out, setOut]}
cmp={[cmp, setCmp]}
onLoadTest={onLoadTest}
onSpeedChange={(speed) => {
actions.setAnimate(speed <= 2);
}}
/>
)}
</div>
Expand Down
9 changes: 6 additions & 3 deletions web/src/shell/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import loaders from "@nand2tetris/projects/loader.js";
import { i18n } from "@lingui/core";
import { Trans } from "@lingui/macro";
import { useContext, useEffect, useMemo } from "react";
import { BaseContext } from "@nand2tetris/components/stores/base.context.js";
import loaders from "@nand2tetris/projects/loader.js";
import { useContext, useEffect, useMemo } from "react";
import { AppContext } from "../App.context";

import "../pico/button-group.scss";
import "../pico/property.scss";
import { TrackingDisclosure } from "src/tracking";
import { TrackingDisclosure } from "../tracking";
import { getVersion, setVersion } from "../versions";
import { useDialog } from "./dialog";

export const Settings = () => {
Expand All @@ -32,7 +33,9 @@ export const Settings = () => {
const resetWarning = useDialog();

const resetFiles = async () => {
const version = getVersion();
localStorage.clear();
setVersion(version);
localStorage["/chip/project"] = "01";
localStorage["/chip/chip"] = "Not";
await loaders.resetFiles(fs);
Expand Down
29 changes: 19 additions & 10 deletions web/src/shell/test_panel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { Trans } from "@lingui/macro";
import { DiffTable } from "@nand2tetris/components/difftable.js";
import { Runbar } from "@nand2tetris/components/runbar.js";
import { BaseContext } from "@nand2tetris/components/stores/base.context.js";
import { Span } from "@nand2tetris/simulator/languages/base";
import { CMP } from "@nand2tetris/simulator/languages/cmp.js";
import { TST } from "@nand2tetris/simulator/languages/tst.js";
import { Timer } from "@nand2tetris/simulator/timer.js";
import {
CSSProperties,
Dispatch,
Expand All @@ -6,17 +14,9 @@ import {
useContext,
useState,
} from "react";
import { Trans } from "@lingui/macro";
import { DiffTable } from "@nand2tetris/components/difftable.js";
import { Runbar } from "@nand2tetris/components/runbar.js";
import { CMP } from "@nand2tetris/simulator/languages/cmp.js";
import { BaseContext } from "@nand2tetris/components/stores/base.context.js";
import { Timer } from "@nand2tetris/simulator/timer.js";
import { TST } from "@nand2tetris/simulator/languages/tst.js";
import { AppContext } from "../App.context";
import { Editor } from "./editor";
import { Panel } from "./panel";
import { Span } from "@nand2tetris/simulator/languages/base";

export const TestPanel = ({
runner,
Expand All @@ -25,13 +25,15 @@ export const TestPanel = ({
out: [out],
disabled = false,
onLoadTest = undefined,
onSpeedChange = undefined,
}: {
runner: RefObject<Timer | undefined>;
tst: [string, Dispatch<string>, Span | undefined];
cmp: [string, Dispatch<string>];
out: [string, Dispatch<string>];
disabled?: boolean;
onLoadTest?: (tst: string, cmp?: string) => void;
onSpeedChange?: (speed: number) => void;
}) => {
const { fs, setStatus } = useContext(BaseContext);
const { filePicker, tracking } = useContext(AppContext);
Expand All @@ -52,7 +54,12 @@ export const TestPanel = ({
try {
const path = await filePicker.select();
const tst = await fs.readFile(path);
const cmp = await fs.readFile(path.replace(/\.tst$/, ".cmp"));
let cmp: string | undefined = undefined;
try {
cmp = await fs.readFile(path.replace(/\.tst$/, ".cmp"));
} catch (e) {
// There doesn't have to be a compare file
}
onLoadTest?.(tst, cmp);
// await compile.current({ tst });
} catch (e) {
Expand All @@ -70,7 +77,9 @@ export const TestPanel = ({
<Trans>Test</Trans>
</div>
<div className="flex-1">
{runner.current && <Runbar runner={runner.current} />}
{runner.current && (
<Runbar runner={runner.current} onSpeedChange={onSpeedChange} />
)}
</div>
<div>
<fieldset role="group">
Expand Down
28 changes: 23 additions & 5 deletions web/src/versions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { FileSystem } from "@davidsouther/jiffies/lib/esm/fs";
import * as project_04 from "@nand2tetris/projects/project_04/index.js";

const CURRENT_VERSION = 1;
const VERSION_KEY = "version";
const CURRENT_VERSION = 2;

export function getVersion() {
return Number(localStorage.getItem(VERSION_KEY) ?? "0");
}

export function setVersion(version: number) {
localStorage.setItem(VERSION_KEY, version.toString());
}

export async function updateVersion(fs: FileSystem) {
let version = Number(localStorage.getItem("version")) ?? 0;
let version = getVersion();

while (version < CURRENT_VERSION) {
await versionUpdates[version](fs);
version++;
try {
await versionUpdates[version](fs);
version++;
} catch (e) {
console.warn(`Error loading files at version ${version}`, e);
version++;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably get logged somewhere but we don't have a good logging system

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I log it to the console for now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. So if a version doesn't work, we don't have its content in localstorage, which does put it an an inconsistent (but overall, usually functional) state. Probably a console.warn and a setStatus.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setStatus will be immediately overriden by something else so I added an alert instead.
Is that okay?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely do not ever use alert. It is highly disruptive to any browser based workflow, and we should all just forget it exists.

Because we have ways to reset the loaded files, I think the best answer today is to use

console.warn(`Error loading files at version ${version}\`, e)

And at some point we'll come back and add proper application analytics logging.

}
}

localStorage.setItem("version", CURRENT_VERSION.toString());
setVersion(CURRENT_VERSION);
}

const versionUpdates: Record<number, (fs: FileSystem) => Promise<void>> = {
Expand All @@ -22,4 +37,7 @@ const versionUpdates: Record<number, (fs: FileSystem) => Promise<void>> = {
);
}
},
1: async (fs: FileSystem) => {
await project_04.resetFiles(fs);
},
};