2021-03-09 14:59:40 +00:00
|
|
|
export type Item = Emittable | string;
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
export const DEFAULT_WIDTH = 80;
|
|
|
|
|
2021-03-09 14:59:40 +00:00
|
|
|
export class Formatter {
|
2021-03-18 21:33:37 +00:00
|
|
|
width = DEFAULT_WIDTH;
|
2021-03-09 14:59:40 +00:00
|
|
|
indentDelta = ' ';
|
|
|
|
currentIndent = '\n';
|
|
|
|
buffer: Array<string> = [];
|
|
|
|
|
|
|
|
get indentSize(): number { return this.indentDelta.length; }
|
|
|
|
set indentSize(n: number) { this.indentDelta = new Array(n + 1).join(' '); }
|
|
|
|
|
|
|
|
write(i: Item) {
|
|
|
|
if (typeof i === 'string') {
|
|
|
|
this.buffer.push(i);
|
|
|
|
} else {
|
|
|
|
i.writeOn(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newline() {
|
|
|
|
this.write(this.currentIndent);
|
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return this.buffer.join('');
|
|
|
|
}
|
|
|
|
|
|
|
|
withIndent(f: () => void): void {
|
|
|
|
const oldIndent = this.currentIndent;
|
|
|
|
try {
|
|
|
|
this.currentIndent = this.currentIndent + this.indentDelta;
|
|
|
|
f();
|
|
|
|
} finally {
|
|
|
|
this.currentIndent = oldIndent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
clone(): Formatter {
|
|
|
|
const f = Object.assign(new Formatter(), this);
|
|
|
|
f.buffer = [];
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
export function formatItems(i: Item[], width = DEFAULT_WIDTH): string {
|
|
|
|
const f = new Formatter();
|
|
|
|
f.width = width;
|
|
|
|
i.forEach(i => f.write(i));
|
|
|
|
return f.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Emittable {
|
|
|
|
writeOn(f: Formatter): void;
|
2021-03-09 14:59:40 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
export class Sequence implements Emittable {
|
2021-03-09 14:59:40 +00:00
|
|
|
items: Array<Item>;
|
|
|
|
|
|
|
|
constructor(items: Array<Item>) {
|
2021-03-18 10:15:10 +00:00
|
|
|
if (items.some(i => i === void 0)) throw new Error('aiee');
|
2021-03-09 14:59:40 +00:00
|
|
|
this.items = items;
|
|
|
|
}
|
|
|
|
|
|
|
|
get separator(): string { return ''; }
|
|
|
|
get terminator(): string { return ''; }
|
|
|
|
|
|
|
|
writeOn(f: Formatter): void {
|
|
|
|
let needSeparator = false;
|
|
|
|
this.items.forEach(i => {
|
|
|
|
if (needSeparator) {
|
|
|
|
f.write(this.separator);
|
|
|
|
} else {
|
|
|
|
needSeparator = true;
|
|
|
|
}
|
|
|
|
f.write(i);
|
|
|
|
});
|
|
|
|
f.write(this.terminator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class CommaSequence extends Sequence {
|
|
|
|
get separator(): string { return ', '; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export abstract class Grouping extends CommaSequence {
|
|
|
|
abstract get open(): string;
|
|
|
|
abstract get close(): string;
|
|
|
|
|
|
|
|
writeHorizontally(f: Formatter): void {
|
|
|
|
f.write(this.open);
|
|
|
|
super.writeOn(f);
|
|
|
|
f.write(this.close);
|
|
|
|
}
|
|
|
|
|
|
|
|
writeVertically(f: Formatter): void {
|
|
|
|
f.write(this.open);
|
|
|
|
if (this.items.length > 0) {
|
|
|
|
f.withIndent(() => {
|
|
|
|
this.items.forEach((i, index) => {
|
|
|
|
f.newline();
|
|
|
|
f.write(i);
|
|
|
|
const delim = index === this.items.length - 1 ? this.terminator : this.separator;
|
|
|
|
f.write(delim.trimRight());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
f.newline();
|
|
|
|
}
|
|
|
|
f.write(this.close);
|
|
|
|
}
|
|
|
|
|
|
|
|
writeOn(f: Formatter): void {
|
|
|
|
const g = f.clone();
|
|
|
|
this.writeHorizontally(g);
|
|
|
|
const s = g.toString();
|
|
|
|
if (s.length <= f.width) {
|
|
|
|
f.write(s);
|
|
|
|
} else {
|
|
|
|
this.writeVertically(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Parens extends Grouping {
|
|
|
|
get open(): string { return '('; }
|
|
|
|
get close(): string { return ')'; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export class OperatorSequence extends Parens {
|
|
|
|
operator: string;
|
|
|
|
|
|
|
|
constructor(operator: string, items: Array<Item>) {
|
|
|
|
super(items);
|
|
|
|
this.operator = operator;
|
|
|
|
}
|
|
|
|
|
|
|
|
get separator(): string { return this.operator; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Brackets extends Grouping {
|
|
|
|
get open(): string { return '['; }
|
|
|
|
get close(): string { return ']'; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export class AngleBrackets extends Grouping {
|
|
|
|
get open(): string { return '<'; }
|
|
|
|
get close(): string { return '>'; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Braces extends Grouping {
|
|
|
|
get open(): string { return '{'; }
|
|
|
|
get close(): string { return '}'; }
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Block extends Braces {
|
|
|
|
get separator(): string { return '; ' }
|
|
|
|
get terminator(): string { return ';' }
|
|
|
|
}
|
|
|
|
|
|
|
|
export const seq = (... items: Item[]) => new Sequence(items);
|
|
|
|
export const commas = (... items: Item[]) => new CommaSequence(items);
|
|
|
|
export const parens = (... items: Item[]) => new Parens(items);
|
|
|
|
export const opseq = (zero: string, op: string, ... items: Item[]) =>
|
|
|
|
(items.length === 0) ? zero : new OperatorSequence(op, items);
|
|
|
|
export const brackets = (... items: Item[]) => new Brackets(items);
|
|
|
|
export const anglebrackets = (... items: Item[]) => new AngleBrackets(items);
|
|
|
|
export const braces = (... items: Item[]) => new Braces(items);
|
2021-03-18 21:33:37 +00:00
|
|
|
export const block = (... items: Item[]) => {
|
|
|
|
if (items.length === 1 && items[0] instanceof Block) {
|
|
|
|
return items[0];
|
|
|
|
} else {
|
|
|
|
return new Block(items);
|
|
|
|
}
|
|
|
|
}
|
2021-03-18 10:15:10 +00:00
|
|
|
export const fnblock = (... items: Item[]) => seq('((() => ', block(... items), ')())');
|
2021-03-18 21:33:37 +00:00
|
|
|
export const keyvalue = (k: string, v: Item) => seq(JSON.stringify(k), ': ', v);
|