Web-Ide mit aufgenommen
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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) {}
|
||||
Reference in New Issue
Block a user