From 0ac2d708585e567fe2ad9ea2143752f86e14806a Mon Sep 17 00:00:00 2001 From: Catherine Date: Sat, 6 Jan 2024 20:31:14 +0000 Subject: [PATCH] [autorelease] Use `stdout`/`stderr` redirection instead of `print`/`printLine`. All YoWASP projects are moving towards uniform `stdout`/`stderr` based interface. --- npmjs/lib/api.d.ts | 7 ++-- npmjs/lib/api.js | 84 +++++++++++++++++++++++++------------------ npmjs/package-in.json | 1 + 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/npmjs/lib/api.d.ts b/npmjs/lib/api.d.ts index a41d839..8212bd3 100644 --- a/npmjs/lib/api.d.ts +++ b/npmjs/lib/api.d.ts @@ -2,6 +2,9 @@ export type Tree = { [name: string]: Tree | string | Uint8Array }; +export type OutputStream = + (bytes: Uint8Array | null) => void; + export class Exit extends Error { code: number; files: Tree; @@ -9,8 +12,8 @@ export class Exit extends Error { export type Command = { (args?: string[], files?: Tree, options?: { - print?: (chars: string) => void, - printLine?: (line: string) => void + stdout?: OutputStream | null, + stderr?: OutputStream | null, }): Promise; requiresUSBDevice: { diff --git a/npmjs/lib/api.js b/npmjs/lib/api.js index 7de1fc5..d11fc9d 100644 --- a/npmjs/lib/api.js +++ b/npmjs/lib/api.js @@ -1,4 +1,5 @@ import instantiate from '../gen/openFPGALoader.mjs'; +import { lineBuffered } from '@yowasp/runtime/util'; export class Exit extends Error { constructor(code, files) { @@ -8,41 +9,63 @@ export class Exit extends Error { } } -function makeTerminalWrite({ print = null, printLine = null }) { - let terminalBuffer = []; - return function(charCode) { - // `print` takes priority over `printLine` - if (print !== null) { - if (charCode !== null) - terminalBuffer.push(charCode); - // flush explicitly, on CR and LF; this avoids flickering of progress messages - if (charCode === null || charCode === 10 || charCode == 13) - print(String.fromCharCode(...terminalBuffer.splice(0))); - } else if (printLine !== null) { - // only flush on LF - if (charCode === null) { - } else if (charCode === 10) { - printLine(String.fromCharCode(...terminalBuffer.splice(0))); - } else { - terminalBuffer.push(charCode); +function createStreamDevice(FS, path, index, ops) { + const dev = FS.makedev(2, index); + FS.registerDevice(dev, { + open(stream) { + stream.seekable = false; + }, + read(stream, buffer, offset, length, pos) { + throw new FS.ErrnoError(29); /* EIO */ + }, + write(stream, buffer, offset, length, pos) { + if (ops.write !== undefined) { + // `buffer` is a slice of `HEAP8`, which is a `Int8Array`. + ops.write(new Uint8Array(buffer.slice(offset, offset + length))); + return length; } + throw new FS.ErrnoError(29); /* EIO */ + }, + fsync(stream) { + if (ops.write !== undefined) + ops.write(null); } - }; + }); + FS.mkdev(path, dev); + return path; } -function writeTreeToMEMFS(FS, tree, path = '/') { +function createStandardStreams(FS, { stdout, stderr }) { + const lineBufferedConsole = lineBuffered(console.log); + function makeStream(streamOption) { + if (streamOption === undefined) + return lineBufferedConsole; + if (streamOption === null) + return (_bytes) => {}; + return streamOption; + } + + createStreamDevice(FS, '/dev/stdin', 0, {}); + createStreamDevice(FS, '/dev/stdout', 1, { write: makeStream(stdout) }); + createStreamDevice(FS, '/dev/stderr', 2, { write: makeStream(stderr) }); + FS.open('/dev/stdin', 0); + FS.open('/dev/stdout', 1); + FS.open('/dev/stderr', 1); +} + +function writeTree(FS, tree, path = '/') { for(const [filename, data] of Object.entries(tree)) { const filepath = `${path}${filename}`; if (typeof data === 'string' || data instanceof Uint8Array) { FS.writeFile(filepath, data); } else { FS.mkdir(filepath); - writeTreeToMEMFS(FS, data, `${filepath}/`); + writeTree(FS, data, `${filepath}/`); } } } -function readTreeFromMEMFS(FS, path = '/') { +function readTree(FS, path = '/') { const tree = {}; for (const filename of FS.readdir(path)) { const filepath = `${path}${filename}`; @@ -54,36 +77,29 @@ function readTreeFromMEMFS(FS, path = '/') { if (FS.isFile(stat.mode)) { tree[filename] = FS.readFile(filepath, { encoding: 'binary' }); } else if (FS.isDir(stat.mode)) { - tree[filename] = readTreeFromMEMFS(FS, `${filepath}/`); + tree[filename] = readTree(FS, `${filepath}/`); } } return tree; } -export async function runOpenFPGALoader(args = null, filesIn = {}, options = { printLine: console.log }) { +export async function runOpenFPGALoader(args = null, filesIn = {}, options = {}) { if (args === null) return; - const terminalWrite = makeTerminalWrite(options); - const inst = await instantiate({ - stdin: () => null, - stdout: terminalWrite, - stderr: terminalWrite, - }); - inst.FS.rmdir('/home/web_user'); - inst.FS.rmdir('/home'); - writeTreeToMEMFS(inst.FS, filesIn); + const inst = await instantiate({ noFSInit: true }); + createStandardStreams(inst.FS, options); + writeTree(inst.FS, filesIn); const argv = ['openFPGALoader', ...args, null]; const argvPtr = inst.stackAlloc(argv.length * 4); argv.forEach((arg, off) => inst.HEAPU32[(argvPtr>>2)+off] = arg === null ? 0 : inst.stringToUTF8OnStack(arg)); - const exitCode = await inst.ccall('main', 'number', ['number', 'number'], [argv.length - 1, argvPtr], { async: true }); inst._fflush(0); - const filesOut = readTreeFromMEMFS(inst.FS); + const filesOut = readTree(inst.FS); if (exitCode == 0) { return filesOut; } else { diff --git a/npmjs/package-in.json b/npmjs/package-in.json index 4ebc879..882b744 100644 --- a/npmjs/package-in.json +++ b/npmjs/package-in.json @@ -25,6 +25,7 @@ }, "types": "./lib/api.d.ts", "devDependencies": { + "@yowasp/runtime": "^6.0", "esbuild": "^0.19.8" }, "scripts": {