seroval
Version:
Stringify JS values
286 lines (261 loc) • 6.61 kB
text/typescript
import {
createIndexedValueNode,
createReferenceNode,
createWKSymbolNode,
} from '../base-primitives';
import { ALL_ENABLED } from '../compat';
import type { WellKnownSymbols } from '../constants';
import { INV_SYMBOL_REF, NIL, SerovalNodeType } from '../constants';
import { SerovalUnsupportedTypeError } from '../errors';
import { createSerovalNode } from '../node';
import type { Plugin, PluginAccessOptions, SerovalMode } from '../plugin';
import { hasReferenceID } from '../reference';
import {
ASYNC_ITERATOR,
ITERATOR,
SPECIAL_REFS,
SpecialReference,
} from '../special-reference';
import type {
SerovalAbortSignalConstructorNode,
SerovalAsyncIteratorFactoryNode,
SerovalIndexedValueNode,
SerovalIteratorFactoryNode,
SerovalMapNode,
SerovalNode,
SerovalNullConstructorNode,
SerovalObjectNode,
SerovalObjectRecordNode,
SerovalPromiseConstructorNode,
SerovalReferenceNode,
SerovalSpecialReferenceNode,
SerovalWKSymbolNode,
} from '../types';
import assert from '../utils/assert';
import { getObjectFlag } from '../utils/get-object-flag';
export interface BaseParserContextOptions extends PluginAccessOptions {
disabledFeatures?: number;
refs?: Map<unknown, number>;
}
export const enum ParserNodeType {
Fresh = 0,
Indexed = 1,
Referenced = 2,
}
export interface FreshNode {
type: ParserNodeType.Fresh;
value: number;
}
export interface IndexedNode {
type: ParserNodeType.Indexed;
value: SerovalIndexedValueNode;
}
export interface ReferencedNode {
type: ParserNodeType.Referenced;
value: SerovalReferenceNode;
}
type ObjectNode = FreshNode | IndexedNode | ReferencedNode;
export abstract class BaseParserContext implements PluginAccessOptions {
abstract readonly mode: SerovalMode;
features: number;
marked = new Set<number>();
refs: Map<unknown, number>;
plugins?: Plugin<any, any>[] | undefined;
constructor(options: BaseParserContextOptions) {
this.plugins = options.plugins;
this.features = ALL_ENABLED ^ (options.disabledFeatures || 0);
this.refs = options.refs || new Map<unknown, number>();
}
protected markRef(id: number): void {
this.marked.add(id);
}
protected isMarked(id: number): boolean {
return this.marked.has(id);
}
protected getIndexedValue<T>(current: T): FreshNode | IndexedNode {
const registeredId = this.refs.get(current);
if (registeredId != null) {
this.markRef(registeredId);
return {
type: ParserNodeType.Indexed,
value: createIndexedValueNode(registeredId),
};
}
const id = this.refs.size;
this.refs.set(current, id);
return {
type: ParserNodeType.Fresh,
value: id,
};
}
protected getReference<T>(current: T): ObjectNode {
const indexed = this.getIndexedValue(current);
if (indexed.type === ParserNodeType.Indexed) {
return indexed;
}
if (hasReferenceID(current)) {
return {
type: ParserNodeType.Referenced,
value: createReferenceNode(indexed.value, current),
};
}
return indexed;
}
protected parseWellKnownSymbol(
current: symbol,
): SerovalIndexedValueNode | SerovalWKSymbolNode | SerovalReferenceNode {
const ref = this.getReference(current);
if (ref.type !== ParserNodeType.Fresh) {
return ref.value;
}
assert(current in INV_SYMBOL_REF, new SerovalUnsupportedTypeError(current));
return createWKSymbolNode(ref.value, current as WellKnownSymbols);
}
protected parseSpecialReference(
ref: SpecialReference,
): SerovalIndexedValueNode | SerovalSpecialReferenceNode {
const result = this.getIndexedValue(SPECIAL_REFS[ref]);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.SpecialReference,
result.value,
ref,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
);
}
protected parseIteratorFactory():
| SerovalIndexedValueNode
| SerovalIteratorFactoryNode {
const result = this.getIndexedValue(ITERATOR);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.IteratorFactory,
result.value,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
this.parseWellKnownSymbol(Symbol.iterator),
NIL,
NIL,
);
}
protected parseAsyncIteratorFactory():
| SerovalIndexedValueNode
| SerovalAsyncIteratorFactoryNode {
const result = this.getIndexedValue(ASYNC_ITERATOR);
if (result.type === ParserNodeType.Indexed) {
return result.value;
}
return createSerovalNode(
SerovalNodeType.AsyncIteratorFactory,
result.value,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
[
this.parseSpecialReference(SpecialReference.PromiseConstructor),
this.parseWellKnownSymbol(Symbol.asyncIterator),
],
NIL,
NIL,
NIL,
);
}
protected createObjectNode(
id: number,
current: Record<string, unknown>,
empty: boolean,
record: SerovalObjectRecordNode,
): SerovalObjectNode | SerovalNullConstructorNode {
return createSerovalNode(
empty ? SerovalNodeType.NullConstructor : SerovalNodeType.Object,
id,
NIL,
NIL,
NIL,
NIL,
record,
NIL,
NIL,
NIL,
NIL,
getObjectFlag(current),
);
}
protected createMapNode(
id: number,
k: SerovalNode[],
v: SerovalNode[],
s: number,
): SerovalMapNode {
return createSerovalNode(
SerovalNodeType.Map,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
{ k, v, s },
NIL,
this.parseSpecialReference(SpecialReference.MapSentinel),
NIL,
NIL,
);
}
protected createPromiseConstructorNode(
id: number,
): SerovalPromiseConstructorNode {
return createSerovalNode(
SerovalNodeType.PromiseConstructor,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
this.parseSpecialReference(SpecialReference.PromiseConstructor),
NIL,
NIL,
);
}
protected createAbortSignalConstructorNode(
id: number,
): SerovalAbortSignalConstructorNode {
return createSerovalNode(
SerovalNodeType.AbortSignalConstructor,
id,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
NIL,
this.parseSpecialReference(SpecialReference.AbortSignalConstructor),
NIL,
NIL,
);
}
}