Skip to content

Commit

Permalink
[autorelease] Use stdout/stderr redirection instead of print/`p…
Browse files Browse the repository at this point in the history
…rintLine`.

All YoWASP projects are moving towards uniform `stdout`/`stderr` based
interface.
  • Loading branch information
whitequark committed Jan 6, 2024
1 parent 369b16d commit 0ac2d70
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 36 deletions.
7 changes: 5 additions & 2 deletions npmjs/lib/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ export type Tree = {
[name: string]: Tree | string | Uint8Array
};

export type OutputStream =
(bytes: Uint8Array | null) => void;

export class Exit extends Error {
code: number;
files: Tree;
}

export type Command = {
(args?: string[], files?: Tree, options?: {
print?: (chars: string) => void,
printLine?: (line: string) => void
stdout?: OutputStream | null,
stderr?: OutputStream | null,
}): Promise<Tree>;

requiresUSBDevice: {
Expand Down
84 changes: 50 additions & 34 deletions npmjs/lib/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import instantiate from '../gen/openFPGALoader.mjs';
import { lineBuffered } from '@yowasp/runtime/util';

export class Exit extends Error {
constructor(code, files) {
Expand All @@ -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}`;
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions npmjs/package-in.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
"types": "./lib/api.d.ts",
"devDependencies": {
"@yowasp/runtime": "^6.0",
"esbuild": "^0.19.8"
},
"scripts": {
Expand Down

0 comments on commit 0ac2d70

Please sign in to comment.