export type NullTypeT = { kind: 'null'; }; export type NumberTypeT = { kind: 'number'; }; export type StringTypeT = { kind: 'string'; }; export type BooleanTypeT = { kind: 'boolean'; }; export type ColorTypeT = { kind: 'color'; }; export type ProjectionDefinitionTypeT = { kind: 'projectionDefinition'; }; export type ObjectTypeT = { kind: 'object'; }; export type ValueTypeT = { kind: 'value'; }; export type ErrorTypeT = { kind: 'error'; }; export type CollatorTypeT = { kind: 'collator'; }; export type FormattedTypeT = { kind: 'formatted'; }; export type PaddingTypeT = { kind: 'padding'; }; export type NumberArrayTypeT = { kind: 'numberArray'; }; export type ColorArrayTypeT = { kind: 'colorArray'; }; export type ResolvedImageTypeT = { kind: 'resolvedImage'; }; export type VariableAnchorOffsetCollectionTypeT = { kind: 'variableAnchorOffsetCollection'; }; export type EvaluationKind = 'constant' | 'source' | 'camera' | 'composite'; export type Type = | NullTypeT | NumberTypeT | StringTypeT | BooleanTypeT | ColorTypeT | ProjectionDefinitionTypeT | ObjectTypeT | ValueTypeT | ArrayType | ErrorTypeT | CollatorTypeT | FormattedTypeT | PaddingTypeT | NumberArrayTypeT | ColorArrayTypeT | ResolvedImageTypeT | VariableAnchorOffsetCollectionTypeT; export interface ArrayType { kind: 'array'; itemType: T; N: number; } export type NativeType = 'number' | 'string' | 'boolean' | 'null' | 'array' | 'object'; export const NullType = {kind: 'null'} as NullTypeT; export const NumberType = {kind: 'number'} as NumberTypeT; export const StringType = {kind: 'string'} as StringTypeT; export const BooleanType = {kind: 'boolean'} as BooleanTypeT; export const ColorType = {kind: 'color'} as ColorTypeT; export const ProjectionDefinitionType = { kind: 'projectionDefinition' } as ProjectionDefinitionTypeT; export const ObjectType = {kind: 'object'} as ObjectTypeT; export const ValueType = {kind: 'value'} as ValueTypeT; export const ErrorType = {kind: 'error'} as ErrorTypeT; export const CollatorType = {kind: 'collator'} as CollatorTypeT; export const FormattedType = {kind: 'formatted'} as FormattedTypeT; export const PaddingType = {kind: 'padding'} as PaddingTypeT; export const ColorArrayType = {kind: 'colorArray'} as ColorArrayTypeT; export const NumberArrayType = {kind: 'numberArray'} as NumberArrayTypeT; export const ResolvedImageType = {kind: 'resolvedImage'} as ResolvedImageTypeT; export const VariableAnchorOffsetCollectionType = { kind: 'variableAnchorOffsetCollection' } as VariableAnchorOffsetCollectionTypeT; export function array(itemType: T, N?: number | null): ArrayType { return { kind: 'array', itemType, N }; } export function typeToString(type: Type): string { if (type.kind === 'array') { const itemType = typeToString(type.itemType); return typeof type.N === 'number' ? `array<${itemType}, ${type.N}>` : type.itemType.kind === 'value' ? 'array' : `array<${itemType}>`; } else { return type.kind; } } const valueMemberTypes = [ NullType, NumberType, StringType, BooleanType, ColorType, ProjectionDefinitionType, FormattedType, ObjectType, array(ValueType), PaddingType, NumberArrayType, ColorArrayType, ResolvedImageType, VariableAnchorOffsetCollectionType ]; /** * Returns null if `t` is a subtype of `expected`; otherwise returns an * error message. * @private */ export function checkSubtype(expected: Type, t: Type): string { if (t.kind === 'error') { // Error is a subtype of every type return null; } else if (expected.kind === 'array') { if ( t.kind === 'array' && ((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) && (typeof expected.N !== 'number' || expected.N === t.N) ) { return null; } } else if (expected.kind === t.kind) { return null; } else if (expected.kind === 'value') { for (const memberType of valueMemberTypes) { if (!checkSubtype(memberType, t)) { return null; } } } return `Expected ${typeToString(expected)} but found ${typeToString(t)} instead.`; } export function isValidType(provided: Type, allowedTypes: Array): boolean { return allowedTypes.some((t) => t.kind === provided.kind); } export function isValidNativeType(provided: any, allowedTypes: Array): boolean { return allowedTypes.some((t) => { if (t === 'null') { return provided === null; } else if (t === 'array') { return Array.isArray(provided); } else if (t === 'object') { return provided && !Array.isArray(provided) && typeof provided === 'object'; } else { return t === typeof provided; } }); } /** * Verify whether the specified type is of the same type as the specified sample. * * @param provided Type to verify * @param sample Sample type to reference * @returns `true` if both objects are of the same type, `false` otherwise * @example basic types * if (verifyType(outputType, ValueType)) { * // type narrowed to: * outputType.kind; // 'value' * } * @example array types * if (verifyType(outputType, array(NumberType))) { * // type narrowed to: * outputType.kind; // 'array' * outputType.itemType; // NumberTypeT * outputType.itemType.kind; // 'number' * } */ export function verifyType(provided: Type, sample: T): provided is T { if (provided.kind === 'array' && sample.kind === 'array') { return provided.itemType.kind === sample.itemType.kind && typeof provided.N === 'number'; } return provided.kind === sample.kind; }