2022-08-28 21:12:25 +02:00
|
|
|
|
2023-03-11 14:24:02 +01:00
|
|
|
import path from "path"
|
2022-09-09 20:18:51 +02:00
|
|
|
import stream from "stream"
|
|
|
|
|
2023-03-11 14:24:02 +01:00
|
|
|
export function first<T>(iter: Iterator<T>): T | undefined {
|
|
|
|
return iter.next().value;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function last<T>(iter: Iterator<T>): T | undefined {
|
|
|
|
let prevValue;
|
|
|
|
for (;;) {
|
|
|
|
const { done, value } = iter.next();
|
|
|
|
if (done) {
|
|
|
|
return prevValue;
|
|
|
|
}
|
|
|
|
prevValue = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stripExtension(filepath: string): string {
|
|
|
|
const basename = path.basename(filepath);
|
|
|
|
const i = basename.lastIndexOf('.');
|
|
|
|
if (i === -1) {
|
|
|
|
return filepath;
|
|
|
|
}
|
|
|
|
return path.join(path.dirname(filepath), basename.substring(0, i));
|
|
|
|
}
|
|
|
|
|
2022-09-09 20:18:51 +02:00
|
|
|
export class IndentWriter {
|
|
|
|
|
|
|
|
private atBlankLine = true;
|
|
|
|
private indentLevel = 0;
|
|
|
|
|
|
|
|
public constructor(
|
|
|
|
private output: stream.Writable,
|
|
|
|
private indentation = ' ',
|
|
|
|
) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public write(text: string): void {
|
|
|
|
for (const ch of text) {
|
|
|
|
if (ch === '\n') {
|
|
|
|
this.atBlankLine = true;
|
|
|
|
} else if (!/[\t ]/.test(ch) && this.atBlankLine) {
|
|
|
|
this.output.write(this.indentation.repeat(this.indentLevel));
|
|
|
|
this.atBlankLine = false;
|
|
|
|
}
|
|
|
|
this.output.write(ch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public indent(): void {
|
|
|
|
this.indentLevel++;
|
|
|
|
}
|
|
|
|
|
|
|
|
public dedent(): void {
|
|
|
|
this.indentLevel--;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-08-29 16:17:55 +02:00
|
|
|
export function assert(test: boolean): asserts test {
|
|
|
|
if (!test) {
|
|
|
|
throw new Error(`Assertion failed. See the stack trace for more information.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-11 14:24:02 +01:00
|
|
|
export function assertNever(value: never): never {
|
|
|
|
console.error(value);
|
|
|
|
throw new Error(`Assertion failed. See the stack trace for more information.`);
|
|
|
|
}
|
|
|
|
|
2022-08-31 13:29:56 +02:00
|
|
|
export function countDigits(x: number, base: number = 10) {
|
|
|
|
return x === 0 ? 1 : Math.ceil(Math.log(x+1) / Math.log(base))
|
|
|
|
}
|
|
|
|
|
2022-09-06 15:13:07 +02:00
|
|
|
export function isEmpty<T>(iter: Iterable<T> | Iterator<T>): boolean {
|
|
|
|
if ((iter as any)[Symbol.iterator] !== undefined) {
|
|
|
|
iter = (iter as any)[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
return !!(iter as Iterator<T>).next().done;
|
|
|
|
}
|
|
|
|
|
2022-08-29 16:17:55 +02:00
|
|
|
export type JSONValue = null | boolean | number | string | JSONArray | JSONObject
|
|
|
|
export type JSONArray = Array<JSONValue>;
|
|
|
|
export type JSONObject = { [key: string]: JSONValue };
|
2022-08-28 21:12:25 +02:00
|
|
|
|
|
|
|
export class MultiDict<K, V> {
|
|
|
|
|
|
|
|
private mapping = new Map<K, V[]>();
|
|
|
|
|
|
|
|
public constructor(iterable?: Iterable<[K, V]>) {
|
|
|
|
if (iterable) {
|
|
|
|
for (const [key, value] of iterable) {
|
|
|
|
this.add(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(key: K): Iterable<V> {
|
|
|
|
return this.mapping.get(key) ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
public add(key: K, value: V): void {
|
|
|
|
const values = this.mapping.get(key);
|
|
|
|
if (values) {
|
|
|
|
values.push(value);
|
|
|
|
} else {
|
|
|
|
this.mapping.set(key, [ value ])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public *[Symbol.iterator](): Iterator<[K, V]> {
|
|
|
|
for (const [key, values] of this.mapping) {
|
|
|
|
for (const value of values) {
|
|
|
|
yield [key, value];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Stream<T> {
|
|
|
|
get(): T;
|
|
|
|
peek(offset?: number): T;
|
|
|
|
}
|
|
|
|
|
|
|
|
export abstract class BufferedStream<T> {
|
|
|
|
|
|
|
|
private buffer: Array<T> = [];
|
|
|
|
|
|
|
|
public abstract read(): T;
|
|
|
|
|
|
|
|
public get(): T {
|
|
|
|
if (this.buffer.length > 0) {
|
|
|
|
return this.buffer.shift()!;
|
|
|
|
}
|
|
|
|
return this.read();
|
|
|
|
}
|
|
|
|
|
|
|
|
public peek(offset = 1): T {
|
|
|
|
while (this.buffer.length < offset) {
|
|
|
|
this.buffer.push(this.read());
|
|
|
|
}
|
|
|
|
return this.buffer[offset-1];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-15 11:49:53 +02:00
|
|
|
export class MultiMap<K, V> {
|
|
|
|
|
|
|
|
private mapping = new Map<K, V[]>();
|
|
|
|
|
|
|
|
public get(key: K): V[] {
|
|
|
|
return this.mapping.get(key) ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
public add(key: K, value: V): void {
|
|
|
|
let elements = this.mapping.get(key);
|
|
|
|
if (elements === undefined) {
|
|
|
|
elements = [];
|
|
|
|
this.mapping.set(key, elements);
|
|
|
|
}
|
|
|
|
elements.push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public has(key: K, value?: V): boolean {
|
|
|
|
if (value === undefined) {
|
|
|
|
return this.mapping.has(key);
|
|
|
|
}
|
|
|
|
const elements = this.mapping.get(key);
|
|
|
|
if (elements === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return elements.indexOf(value) !== -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
public keys(): Iterable<K> {
|
|
|
|
return this.mapping.keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
public *values(): Iterable<V> {
|
|
|
|
for (const elements of this.mapping.values()) {
|
|
|
|
yield* elements;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public *[Symbol.iterator](): Iterable<[K, V]> {
|
|
|
|
for (const [key, elements] of this.mapping) {
|
|
|
|
for (const value of elements) {
|
|
|
|
yield [key, value];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public delete(key: K, value?: V): number {
|
|
|
|
const elements = this.mapping.get(key);
|
|
|
|
if (elements === undefined) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (value === undefined) {
|
|
|
|
this.mapping.delete(key);
|
|
|
|
return elements.length;
|
|
|
|
}
|
|
|
|
const i = elements.indexOf(value);
|
|
|
|
if (i !== -1) {
|
|
|
|
elements.splice(i, 1);
|
|
|
|
if (elements.length === 0) {
|
|
|
|
this.mapping.delete(key);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|