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
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@nand2tetris/cli",
"description": "NAND2Tetris Command Line tools",
"repository": {
"type": "git",
"url": "git+https://github.com/nand2tetris/web-ide.git"
},
"author": "David Souther <davidsouther@gmail.com>",
"license": "ISC",
"bugs": {
"url": "https://github.com/nand2tetris/web-ide/issues"
},
"homepage": "https://github.com/nand2tetris/web-ide",
"main": "dist/index.js",
"type": "module",
"bin": {
"nand2tetris": "dist/index.js"
},
"scripts": {
"build": "tsc",
"test": "jest"
},
"devDependencies": {
"@davidsouther/jiffies": "^2.2.5",
"@nand2tetris/runner": "file:../runner",
"@nand2tetris/simulator": "file:../simulator"
},
"dependencies": {
"@types/node": "^20.14.2",
"@types/yargs": "^17.0.32",
"yargs": "^17.7.2"
}
}
+79
View File
@@ -0,0 +1,79 @@
import { FileSystem } from "@davidsouther/jiffies/lib/esm/fs.js";
import { NodeFileSystemAdapter } from "@davidsouther/jiffies/lib/esm/fs_node.js";
import type { Assignment } from "@nand2tetris/projects/base.js";
import { Assignments } from "@nand2tetris/projects/full.js";
import { JavaRunner } from "@nand2tetris/runner/index.js";
import {
AssignmentFiles,
hasTest,
runTests,
} from "@nand2tetris/simulator/projects/runner.js";
import { join, parse } from "path";
/**
* Given a FileSystem wrapper, curry a function that loads the necessary files for running an HDL test.
* For grading, tests come from the built-in assignment master test list.
*/
const loadAssignment = (fs: FileSystem) =>
async function (file: Assignment): Promise<AssignmentFiles> {
const hdl = await fs.readFile(file.base);
const tst = Assignments[
`${file.name}.tst` as keyof typeof Assignments
] as string;
const cmp = Assignments[
`${file.name}.cmp` as keyof typeof Assignments
] as string;
return { ...file, hdl, tst, cmp };
};
/**
* Run the grader using a NodeJS file system.
*
* Report results using a simple `{Name} passed/failed`, and if given a java_id, the same for shadow mode results.
*
* Returns 1 if at least one test was failed or no tests were found to run. Returns 0 otherwise.
*/
export async function main(folder = process.cwd(), java_ide = "") {
const fs = new FileSystem(new NodeFileSystemAdapter());
fs.cd(folder);
const directory = [...(await fs.readdir("."))];
const runFiles = directory.filter((file) => file.endsWith(".hdl"));
const files = runFiles
.map((f) => join(folder, f))
.map(parse)
.filter(hasTest);
const ideRunner = await JavaRunner.try_init(java_ide);
const tests = await runTests(files, loadAssignment(fs), fs, ideRunner);
if (!tests.length) {
console.log("No tests have run!");
return 1;
}
let failsCount = 0;
for (const test of tests) {
if (!test.pass) {
failsCount++;
}
console.log(
`Test ${test.name}: ${test.pass ? `Passed` : `Failed (${test.out})`}`,
);
if (test.shadow) {
if (test.shadow.code !== 0) {
failsCount++;
}
console.log(
`\tShadow: ${
test.shadow.code === 0
? `Passed`
: `Errored (${test.shadow.stderr.trim()})`
}`,
);
} else {
console.log("\tNo shadow");
}
}
return failsCount > 0 ? 1 : 0;
}
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env node
import { FileSystem } from "@davidsouther/jiffies/lib/esm/fs.js";
import { NodeFileSystemAdapter } from "@davidsouther/jiffies/lib/esm/fs_node.js";
import { compile } from "@nand2tetris/simulator/jack/compiler.js";
import * as fsCore from "fs";
import path, { dirname, parse, resolve } from "path";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { main } from "./grading.js";
import { testRunner } from "./testrunner.js";
yargs(hideBin(process.argv))
.usage("$0 <cmd>")
.command(
"grade [directory]",
"Grade all NAND2Tetris projects in a directory tree.",
(yargs) =>
yargs
.positional("directory", {
type: "string",
default: process.cwd(),
describe: "Path to a folder to grade for nand2tetris projects.",
})
.option("java_ide", {
type: "string",
default: process.env.NAND2TETRIS_PATH,
describe:
"When set, look for the java IDE jars in this path and compare both runs.",
}),
async (argv) => {
console.log("grade", argv.directory, "nand2tetris grader!");
const exitCodePromise = main(argv.directory, argv.java_ide);
const exitCode = await exitCodePromise;
if (exitCode) {
process.exit(exitCode);
}
},
)
.command(
"run <file>",
"Run a NAND2Tetris file. If the file is .tst, executes the test. If the file is .hdl, starts a terminal session for the chip. If the file is .asm, .hack, .vm, or .jack, loads (possibly after compilation) the file into memory and starts the machine execution.",
(yargs) =>
yargs
.positional("file", {
type: "string",
describe: "Path to nand2tetris tst file to execute.",
})
.option("debug", {
type: "boolean",
default: false,
describe: "Port for the debugger protocol to listen on.",
})
.option("debug_port", {
type: "number",
default: 6163,
describe: "Port for the debugger protocol to listen on.",
})
.option("java_ide", {
type: "string",
describe:
"When set, look for the java IDE jars in this path and compare both runs.",
}),
(argv) => {
console.log("nand2tetris command run", argv);
const { name, ext } = parse(argv.file ?? "");
switch (ext) {
case "":
case ".tst":
console.log("tst");
testRunner(dirname(resolve(argv.file ?? process.cwd())), name);
break;
case ".hdl":
console.log("hdl");
break;
default:
console.log("unknown", ext);
break;
}
},
)
.command(
"compile <src> [dst]",
"Compile .jack files inside a folder",
(yargs) =>
yargs
.positional("src", {
type: "string",
describe: "Path to input folder with jack files",
})
.option("dst", {
type: "string",
describe: "Path to destination folder",
default: "",
})
.coerce(["src", "dst"], function (arg) {
return path.resolve(arg) + "/";
})
.check((argv, options) => {
const src = argv.src;
const dst = argv.dst;
if (src === undefined) {
throw Error("Please provide input folder path");
}
if (dst && !fsCore.lstatSync(dst).isDirectory()) {
throw Error(src + " is not a folder");
}
if (!fsCore.lstatSync(src).isDirectory()) {
throw Error(src + " is not a folder");
}
return true;
})
.showHelpOnFail(false, "Specify --help for available options"),
async (argv) => {
enum Colors {
Red = "\x1b[31m",
Green = "\x1b[32m",
Reset = "\u001b[0m",
}
const JACK_EXT = ".jack";
const src = argv.src;
const dst = argv.dst ?? src;
if (src === undefined) {
throw Error("Please provde input folder path");
}
if (dst === undefined) {
throw Error("Please provde input folder path");
}
const fs = new FileSystem(new NodeFileSystemAdapter());
const files = await fs.readdir(src);
const jackFiles = files.filter((file) => file.endsWith(JACK_EXT));
if (jackFiles.length === 0) {
throw Error("No jack files inside a folder");
}
const nameToContent = {} as Record<string, string>;
for (const file of jackFiles) {
const filepath = path.join(src, file);
const content = await fs.readFile(filepath);
nameToContent[file.replace(JACK_EXT, "")] = content;
}
let error = false;
for (const [name, compiled] of Object.entries(compile(nameToContent))) {
if (typeof compiled === "string") {
const outputFilename = name + ".vm";
const outpath = path.join(dst, outputFilename);
await fs.writeFile(outpath, compiled);
} else {
if (!error) {
console.error("Compilation failed\n");
}
console.error(
Colors.Red +
compiled.message.replace(
/Line\s([\d]+):/g,
name + ".jack:$1" + Colors.Reset,
),
);
error = true;
}
}
if (error) {
process.exit(1);
} else {
console.log(Colors.Green + "Compiled files" + Colors.Reset);
}
},
)
.help()
.demandCommand(1)
.parse();
+41
View File
@@ -0,0 +1,41 @@
import { FileSystem } from "@davidsouther/jiffies/lib/esm/fs.js";
import { NodeFileSystemAdapter } from "@davidsouther/jiffies/lib/esm/fs_node.js";
import type { Assignment } from "@nand2tetris/projects/base.js";
import { Assignments } from "@nand2tetris/projects/full.js";
import { runner } from "@nand2tetris/simulator/projects/runner.js";
import { parse } from "path";
/**
* Load an assignment from the local folder.
* Uses built in assignments when the local tests are missing.
*/
async function loadAssignment(fs: FileSystem, file: Assignment) {
const assignment = Assignments[file.name as keyof typeof Assignments];
const hdl = await fs.readFile(`${file.name}.hdl`);
const tst = await fs
.readFile(`${file.name}.tst`)
.catch(
() => assignment[`${file.name}.tst` as keyof typeof assignment] as string,
);
const cmp = await fs
.readFile(`${file.name}.cmp`)
.catch(
() => assignment[`${file.name}.cmp` as keyof typeof assignment] as string,
);
return { ...file, hdl, tst, cmp };
}
/**
* Run a nand2tetris.tst file.
*/
export async function testRunner(dir: string, file: string) {
const fs = new FileSystem(new NodeFileSystemAdapter());
fs.cd(dir);
const assignment = await loadAssignment(fs, parse(file));
const tryRun = runner(fs);
const run = await tryRun(assignment);
console.log(run);
}
// export async function testDebugger(root: string, name: string, port: number) {}
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}