diff options
Diffstat (limited to 'js/xeval.ts')
-rw-r--r-- | js/xeval.ts | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/js/xeval.ts b/js/xeval.ts new file mode 100644 index 000000000..f769a2ead --- /dev/null +++ b/js/xeval.ts @@ -0,0 +1,99 @@ +import { Buffer } from "./buffer"; +import { stdin } from "./files"; +import { TextEncoder, TextDecoder } from "./text_encoding"; +import { Reader } from "./io"; + +export type XevalFunc = (v: string) => void; + +async function writeAll(buffer: Buffer, arr: Uint8Array): Promise<void> { + let bytesWritten = 0; + while (bytesWritten < arr.length) { + try { + const nwritten = await buffer.write(arr.subarray(bytesWritten)); + bytesWritten += nwritten; + } catch { + return; + } + } +} + +// TODO(kevinkassimo): Move this utility to deno_std. +// Import from there once doable. +// Read from reader until EOF and emit string chunks separated +// by the given delimiter. +async function* chunks( + reader: Reader, + delim: string +): AsyncIterableIterator<string> { + const inputBuffer = new Buffer(); + const inspectArr = new Uint8Array(1024); + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + // Avoid unicode problems + const delimArr = encoder.encode(delim); + + // Record how far we have gone with delimiter matching. + let nextMatchIndex = 0; + while (true) { + const rr = await reader.read(inspectArr); + if (rr.nread < 0) { + // Silently fail. + break; + } + const sliceRead = inspectArr.subarray(0, rr.nread); + // Remember how far we have scanned through inspectArr. + let nextSliceStartIndex = 0; + for (let i = 0; i < sliceRead.length; i++) { + if (sliceRead[i] == delimArr[nextMatchIndex]) { + // One byte matches with delimiter, move 1 step forward. + nextMatchIndex++; + } else { + // Match delimiter failed. Start from beginning. + nextMatchIndex = 0; + } + // A complete match is found. + if (nextMatchIndex === delimArr.length) { + nextMatchIndex = 0; // Reset delim match index. + const sliceToJoin = sliceRead.subarray(nextSliceStartIndex, i + 1); + // Record where to start next chunk when a subsequent match is found. + nextSliceStartIndex = i + 1; + // Write slice to buffer before processing, since potentially + // part of the delimiter is stored in the buffer. + await writeAll(inputBuffer, sliceToJoin); + + let readyBytes = inputBuffer.bytes(); + inputBuffer.reset(); + // Remove delimiter from buffer bytes. + readyBytes = readyBytes.subarray( + 0, + readyBytes.length - delimArr.length + ); + let readyChunk = decoder.decode(readyBytes); + yield readyChunk; + } + } + // Write all unprocessed chunk to buffer for future inspection. + await writeAll(inputBuffer, sliceRead.subarray(nextSliceStartIndex)); + if (rr.eof) { + // Flush the remainder unprocessed chunk. + const lastChunk = inputBuffer.toString(); + yield lastChunk; + break; + } + } +} + +export async function xevalMain( + xevalFunc: XevalFunc, + delim_: string | null +): Promise<void> { + if (!delim_) { + delim_ = "\n"; + } + for await (const chunk of chunks(stdin, delim_)) { + // Ignore empty chunks. + if (chunk.length > 0) { + xevalFunc(chunk); + } + } +} |