2021-02-17 15:52:01 +00:00
|
|
|
import { Encoder } from "./codec";
|
|
|
|
import { Tag } from "./constants";
|
|
|
|
import { AsPreserve, PreserveOn } from "./symbols";
|
|
|
|
import { DefaultPointer, is, Value } from "./values";
|
|
|
|
import { Record } from './record';
|
|
|
|
import { Dictionary, Set } from './dictionary';
|
|
|
|
|
|
|
|
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
|
|
|
|
|
|
|
export class Annotated<T extends object = DefaultPointer> {
|
|
|
|
readonly annotations: Array<Value<T>>;
|
|
|
|
readonly item: Value<T>;
|
|
|
|
|
|
|
|
constructor(item: Value<T>) {
|
|
|
|
this.annotations = [];
|
|
|
|
this.item = item;
|
|
|
|
}
|
|
|
|
|
|
|
|
[AsPreserve](): Value<T> {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
[PreserveOn](encoder: Encoder<T>) {
|
|
|
|
if (encoder.includeAnnotations) {
|
|
|
|
for (const a of this.annotations) {
|
|
|
|
encoder.emitbyte(Tag.Annotation);
|
|
|
|
encoder.push(a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
encoder.push(this.item);
|
|
|
|
}
|
|
|
|
|
|
|
|
equals(other: any): boolean {
|
|
|
|
return is(this.item, Annotated.isAnnotated(other) ? other.item : other);
|
|
|
|
}
|
|
|
|
|
|
|
|
// hashCode(): number {
|
|
|
|
// return hash(this.item);
|
|
|
|
// }
|
|
|
|
|
|
|
|
toString(): string {
|
|
|
|
return this.asPreservesText();
|
|
|
|
}
|
|
|
|
|
|
|
|
asPreservesText(): string {
|
|
|
|
const anns = this.annotations.map((a) => '@' + a.asPreservesText()).join(' ');
|
|
|
|
return (anns ? anns + ' ' : anns) + this.item.asPreservesText();
|
|
|
|
}
|
|
|
|
|
|
|
|
get [IsPreservesAnnotated](): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static isAnnotated<T extends object = DefaultPointer>(x: any): x is Annotated<T> {
|
|
|
|
return !!x?.[IsPreservesAnnotated];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function peel<T extends object = DefaultPointer>(v: Value<T>): Value<T> {
|
|
|
|
return strip(v, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function strip<T extends object = DefaultPointer>(v: Value<T>, depth: number = Infinity): Value<T> {
|
|
|
|
function step(v: Value<T>, depth: number): Value<T> {
|
|
|
|
if (depth === 0) return v;
|
|
|
|
if (!Annotated.isAnnotated<T>(v)) return v;
|
|
|
|
|
|
|
|
const nextDepth = depth - 1;
|
|
|
|
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
|
|
|
|
|
|
|
|
if (Record.isRecord<T>(v.item)) {
|
2021-02-22 11:51:17 +00:00
|
|
|
return Record(step(v.item.label, depth), v.item.map(walk));
|
2021-02-17 15:52:01 +00:00
|
|
|
} else if (Array.isArray(v.item)) {
|
2021-02-22 11:51:17 +00:00
|
|
|
return (v.item as Array<Value<T>>).map(walk);
|
2021-02-17 15:52:01 +00:00
|
|
|
} else if (Set.isSet<T>(v.item)) {
|
|
|
|
return v.item.map(walk);
|
|
|
|
} else if (Dictionary.isDictionary<Value<T>, T>(v.item)) {
|
|
|
|
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
|
|
|
} else if (Annotated.isAnnotated(v.item)) {
|
|
|
|
throw new Error("Improper annotation structure");
|
|
|
|
} else {
|
|
|
|
return v.item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return step(v, depth);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function annotate<T extends object = DefaultPointer>(v0: Value<T>, ...anns: Value<T>[]): Annotated<T> {
|
|
|
|
const v = Annotated.isAnnotated<T>(v0) ? v0 : new Annotated(v0);
|
|
|
|
anns.forEach((a) => v.annotations.push(a));
|
|
|
|
return v;
|
|
|
|
}
|