Skip to content

Commit

Permalink
Add support for stdin stream and chunked utility function.
Browse files Browse the repository at this point in the history
This is required by the amaranth-yosys invocation.
  • Loading branch information
whitequark committed Feb 13, 2024
1 parent 279effd commit 01db971
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 8 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ Utility API reference
This package also exports utilities that are useful when running other JavaScript YoWASP packages. These can be used as:

```js
import { lineBuffered } from '@yowasp/runtime/util';
import { lineBuffered, chunked } from '@yowasp/runtime/util';
```

- The `lineBuffered(processLine)` function takes a function `processLine(line)` that accepts a line of text (e.g. `console.log`), and returns a function `processBytes(bytes)` that accepts a `Uint8Array` of encoded characters. Each byte sequence ending in the `\n` byte, not including it, is decoded as UTF-8 (invalid sequences are substituted with a replacement character) and passed to `processLine()`.
- The `lineBuffered(processLine)` function takes a function `processLine(line)` that accepts a line of text (e.g. `console.log`), and returns a function `processBytes(bytes)` that accepts a `Uint8Array` of encoded characters (or `null`, which is ignored). Each byte sequence ending in the `\n` byte, not including it, is decoded as UTF-8 (invalid sequences are substituted with a replacement character) and passed to `processLine()`.
- The `chunked(text)` function takes text and returns a function `getChunk(byteLength)` that slices off a `Uint8Array` of UTF-8 code points of the requested length and returns it. Once it exhausts the input, `getChunk()` returns `null` whenever it is called.


License
Expand Down
4 changes: 4 additions & 0 deletions lib/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ export type Tree = {
[name: string]: Tree | string | Uint8Array
};

export type InputStream =
(byteLength: number) => Uint8Array | null;

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

export type RunOptions = {
stdin?: InputStream | null;
stdout?: OutputStream | null,
stderr?: OutputStream | null,
decodeASCII?: boolean
Expand Down
3 changes: 2 additions & 1 deletion lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ export class Application {
}

// The `printLine` option is deprecated and not documented but still accepted for compatibility.
execute(args, files = {}, { stdout, stderr, decodeASCII = true, printLine } = {}) {
execute(args, files = {}, { stdin, stdout, stderr, decodeASCII = true, printLine } = {}) {
const environment = new Environment();
environment.args = [this.argv0].concat(args);
environment.root = directoryFromTree(files);
for (const [dirName, dirContents] of Object.entries(this.resourceData.filesystem))
environment.root.files[dirName] = directoryFromTree(dirContents);
const lineBufferedConsole = lineBuffered(printLine ?? console.log);
environment.stdin = stdin === undefined ? null : stdin;
environment.stdout = stdout === undefined ? lineBufferedConsole : stdout;
environment.stderr = stderr === undefined ? lineBufferedConsole : stderr;

Expand Down
1 change: 1 addition & 0 deletions lib/util.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export function lineBuffered(processLine: (line: string) => void): (bytes: Uint8Array) => void;
export function chunked(text: string): (byteLength: number) => Uint8Array | null;
12 changes: 12 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ export function lineBuffered(processLine) {
buffer = buffer.subarray(newlineAt + 1);
};
}

export function chunked(text) {
let buffer = new TextEncoder().encode(text);
return (length) => {
if (buffer.length === 0)
return null;

let chunk = buffer.subarray(0, length);
buffer = buffer.subarray(length);
return chunk;
};
}
26 changes: 25 additions & 1 deletion lib/wasi-virt.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ class OutputStream {
}
}

class CallbackInputStream extends InputStream {
constructor(callback = null) {
super();
this.callback = callback;
}

read(len) {
if (this.callback === null)
throw { tag: 'closed' };
let contents = this.callback(Number(len));
if (contents === null)
throw { tag: 'closed' };
return contents;
}
}

class CallbackOutputStream extends OutputStream {
constructor(callback = null) {
super();
Expand Down Expand Up @@ -350,7 +366,7 @@ export class Environment {
constructor() {
this.prng = new Xoroshiro128StarStar(1n);

this.standardInputStream = new InputStream();
this.standardInputStream = new CallbackInputStream();
this.standardOutputStream = new CallbackOutputStream();
this.standardErrorStream = new CallbackOutputStream();

Expand Down Expand Up @@ -398,6 +414,14 @@ export class Environment {
};
}

get stdin() {
return this.standardInputStream.callback;
}

set stdin(callback) {
this.standardInputStream.callback = callback;
}

get stdout() {
return this.standardOutputStream.callback;
}
Expand Down
16 changes: 12 additions & 4 deletions test/yowasp_runtime_test/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Application, Exit } from '@yowasp/runtime';
import { lineBuffered } from '@yowasp/runtime/util';
import { lineBuffered, chunked } from '@yowasp/runtime/util';
import { instantiate } from './gen/copy.js';


Expand Down Expand Up @@ -42,13 +42,21 @@ if (!(files5['b.txt'] instanceof Uint8Array && files5['b.txt'].length === 1 && f

let files6 = await yowaspRuntimeTest.run([], {'sp.txt': '\r\n\t'});
if (files6['sp.txt'] !== '\r\n\t')
throw 'test 6';
throw 'test 6 failed';

let files7 = await yowaspRuntimeTest.run([]);
if (typeof files7['share'] !== 'undefined')
throw 'test 7';
throw 'test 7 failed';

lines = [];
await yowaspRuntimeTest.run([], {}, {
stdin: chunked('some text\n'),
stdout: lineBuffered((line) => lines.push(line))
});
if (!(lines.length === 1 || lines[0] === 'some text'))
throw 'test 8 failed';

await yowaspRuntimeTest.preload();

if ((yowaspRuntimeTest.execute(['share/foo.txt', 'foo.txt'], {}))['foo.txt'] !== 'contents of foo')
throw 'test 8 failed';
throw 'test 9 failed';

0 comments on commit 01db971

Please sign in to comment.