You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
85 lines
1.7 KiB
85 lines
1.7 KiB
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) { |
|
const skip = this.peekWhile(predicate); |
|
return this.peek(skip.length + 1).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() === ''; |
|
} |
|
}
|
|
|