|
|
|
import { readFile } from 'node:fs/promises';
|
|
|
|
import { basename } from 'node:path';
|
|
|
|
|
|
|
|
export class CharacterStream {
|
|
|
|
constructor() {
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
this._data = '';
|
|
|
|
this.fileName = '';
|
|
|
|
this._pos = 0;
|
|
|
|
this.line = 0;
|
|
|
|
this.column = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
async loadFile(fileName) {
|
|
|
|
this.reset();
|
|
|
|
this.fileName = basename(fileName);
|
|
|
|
this._data = await readFile(fileName, 'utf-8');
|
|
|
|
}
|
|
|
|
|
|
|
|
peek(length = 1) {
|
|
|
|
return this._data.substring(this._pos, this._pos + length);
|
|
|
|
}
|
|
|
|
|
|
|
|
peekWhile(predicate) {
|
|
|
|
let buffer = '';
|
|
|
|
for (
|
|
|
|
let offset = 0, c = this._data[this._pos + offset];
|
|
|
|
c !== undefined && predicate(c);
|
|
|
|
offset++, c = this._data[this._pos + offset]
|
|
|
|
) {
|
|
|
|
buffer += c;
|
|
|
|
}
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Peek first character after any characters that match predicate
|
|
|
|
peekAfter(predicate, length = 1) {
|
|
|
|
const skip = this.peekWhile(predicate);
|
|
|
|
return this.peek(skip.length + length).substring(skip.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
isNext(str) {
|
|
|
|
return this.peek(str.length) === str;
|
|
|
|
}
|
|
|
|
|
|
|
|
next(length = 1) {
|
|
|
|
const chunk = this.peek(length);
|
|
|
|
|
|
|
|
this._pos += chunk.length;
|
|
|
|
|
|
|
|
this.line += this._countOccurrences(chunk, '\n');
|
|
|
|
|
|
|
|
const lastNewLine = chunk.lastIndexOf('\n');
|
|
|
|
if (lastNewLine === -1) {
|
|
|
|
this.column += chunk.length;
|
|
|
|
} else {
|
|
|
|
this.column = chunk.length - lastNewLine - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunk;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextWhile(predicate) {
|
|
|
|
return this.next(this.peekWhile(predicate).length);
|
|
|
|
}
|
|
|
|
|
|
|
|
_countOccurrences = (str, substr) => {
|
|
|
|
let count = 0;
|
|
|
|
let i = 0;
|
|
|
|
while(true) {
|
|
|
|
i = str.indexOf(substr, i);
|
|
|
|
if (i === -1) break;
|
|
|
|
i += substr.length;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
eof() {
|
|
|
|
return this.peek() === '';
|
|
|
|
}
|
|
|
|
}
|