import { ObjectType, ValueType, StringType, NumberType, BooleanType, checkSubtype, typeToString, array } from '../types'; import {RuntimeError} from '../runtime_error'; import {typeOf} from '../values'; import type {Expression} from '../expression'; import type {ParsingContext} from '../parsing_context'; import type {EvaluationContext} from '../evaluation_context'; import type {Type} from '../types'; const types = { string: StringType, number: NumberType, boolean: BooleanType, object: ObjectType }; export class Assertion implements Expression { type: Type; args: Array; constructor(type: Type, args: Array) { this.type = type; this.args = args; } static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length < 2) return context.error('Expected at least one argument.') as null; let i = 1; let type; const name: string = args[0] as any; if (name === 'array') { let itemType; if (args.length > 2) { const type = args[1]; if (typeof type !== 'string' || !(type in types) || type === 'object') return context.error( 'The item type argument of "array" must be one of string, number, boolean', 1 ) as null; itemType = types[type]; i++; } else { itemType = ValueType; } let N; if (args.length > 3) { if ( args[2] !== null && (typeof args[2] !== 'number' || args[2] < 0 || args[2] !== Math.floor(args[2])) ) { return context.error( 'The length argument to "array" must be a positive integer literal', 2 ) as null; } N = args[2]; i++; } type = array(itemType, N); } else { if (!types[name]) throw new Error(`Types doesn't contain name = ${name}`); type = types[name]; } const parsed = []; for (; i < args.length; i++) { const input = context.parse(args[i], i, ValueType); if (!input) return null; parsed.push(input); } return new Assertion(type, parsed); } evaluate(ctx: EvaluationContext) { for (let i = 0; i < this.args.length; i++) { const value = this.args[i].evaluate(ctx); const error = checkSubtype(this.type, typeOf(value)); if (!error) { return value; } else if (i === this.args.length - 1) { throw new RuntimeError( `Expected value to be of type ${typeToString(this.type)}, but found ${typeToString(typeOf(value))} instead.` ); } } throw new Error(); } eachChild(fn: (_: Expression) => void) { this.args.forEach(fn); } outputDefined(): boolean { return this.args.every((arg) => arg.outputDefined()); } }