preserves/implementations/javascript/packages/schema/src/block.ts

161 lines
4.2 KiB
TypeScript

export type Item = Emittable | string;
export class Formatter {
width = 80;
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;
}
}
export abstract class Emittable {
abstract writeOn(f: Formatter): void;
}
export class Sequence extends Emittable {
items: Array<Item>;
constructor(items: Array<Item>) {
super();
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);
export const block = (... items: Item[]) => new Block(items);