import { ObjectType, ValueType, StringType, NumberType, BooleanType, checkSubtype, toString, 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 }; 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 ${toString(this.type)}, but found ${toString(typeOf(value))} instead.`); } } throw new Error(); } eachChild(fn: (_: Expression) => void) { this.args.forEach(fn); } outputDefined(): boolean { return this.args.every(arg => arg.outputDefined()); } } export default Assertion;