forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
166 lines (145 loc) · 4.32 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { parse } from "../flags/mod.ts";
const { Buffer, EOF, args, exit, stdin, writeAll } = Deno;
type Reader = Deno.Reader;
/* eslint-disable-next-line max-len */
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction.
const AsyncFunction = Object.getPrototypeOf(async function(): Promise<void> {})
.constructor;
function showHelp(): void {
console.log(`Deno xeval
USAGE:
deno -A https://deno.land/std/xeval/mod.ts [OPTIONS] <code>
OPTIONS:
-d, --delim <delim> Set delimiter, defaults to newline
-I, --replvar <replvar> Set variable name to be used in eval, defaults to $
ARGS:
<code>
`);
}
export type XevalFunc = (v: string) => void;
export interface XevalOptions {
delimiter?: string;
}
const DEFAULT_DELIMITER = "\n";
// Generate longest proper prefix which is also suffix array.
function createLPS(pat: Uint8Array): Uint8Array {
const lps = new Uint8Array(pat.length);
lps[0] = 0;
let prefixEnd = 0;
let i = 1;
while (i < lps.length) {
if (pat[i] == pat[prefixEnd]) {
prefixEnd++;
lps[i] = prefixEnd;
i++;
} else if (prefixEnd === 0) {
lps[i] = 0;
i++;
} else {
prefixEnd = pat[prefixEnd - 1];
}
}
return lps;
}
// TODO(kevinkassimo): Move this utility somewhere public in 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 encoder = new TextEncoder();
const decoder = new TextDecoder();
// Avoid unicode problems
const delimArr = encoder.encode(delim);
const delimLen = delimArr.length;
const delimLPS = createLPS(delimArr);
let inputBuffer = new Buffer();
const inspectArr = new Uint8Array(Math.max(1024, delimLen + 1));
// Modified KMP
let inspectIndex = 0;
let matchIndex = 0;
while (true) {
let result = await reader.read(inspectArr);
if (result === EOF) {
// Yield last chunk.
const lastChunk = inputBuffer.toString();
yield lastChunk;
return;
}
if ((result as number) < 0) {
// Discard all remaining and silently fail.
return;
}
let sliceRead = inspectArr.subarray(0, result as number);
await writeAll(inputBuffer, sliceRead);
let sliceToProcess = inputBuffer.bytes();
while (inspectIndex < sliceToProcess.length) {
if (sliceToProcess[inspectIndex] === delimArr[matchIndex]) {
inspectIndex++;
matchIndex++;
if (matchIndex === delimLen) {
// Full match
const matchEnd = inspectIndex - delimLen;
const readyBytes = sliceToProcess.subarray(0, matchEnd);
// Copy
const pendingBytes = sliceToProcess.slice(inspectIndex);
const readyChunk = decoder.decode(readyBytes);
yield readyChunk;
// Reset match, different from KMP.
sliceToProcess = pendingBytes;
inspectIndex = 0;
matchIndex = 0;
}
} else {
if (matchIndex === 0) {
inspectIndex++;
} else {
matchIndex = delimLPS[matchIndex - 1];
}
}
}
// Keep inspectIndex and matchIndex.
inputBuffer = new Buffer(sliceToProcess);
}
}
export async function xeval(
reader: Reader,
xevalFunc: XevalFunc,
{ delimiter = DEFAULT_DELIMITER }: XevalOptions = {}
): Promise<void> {
for await (const chunk of chunks(reader, delimiter)) {
// Ignore empty chunks.
if (chunk.length > 0) {
await xevalFunc(chunk);
}
}
}
async function main(): Promise<void> {
const parsedArgs = parse(args.slice(1), {
boolean: ["help"],
string: ["delim", "replvar"],
alias: {
delim: ["d"],
replvar: ["I"],
help: ["h"]
}
});
if (parsedArgs.help || parsedArgs._.length != 1) {
return showHelp();
}
const delimiter = parsedArgs.delim || DEFAULT_DELIMITER;
const replVar = parsedArgs.replvar || "$";
const code = parsedArgs._[0];
// new AsyncFunction()'s error message for this particular case isn't great.
if (!replVar.match(/^[_$A-z][_$A-z0-9]*$/)) {
console.error(`Bad replvar identifier: "${replVar}"`);
exit(1);
}
const xEvalFunc = new AsyncFunction(replVar, code);
await xeval(stdin, xEvalFunc, { delimiter });
}
if (import.meta.main) {
main();
}