import { Scope } from "./scope" type NativeFunction = (...args: Value[]) => Promise | Value export type Value = | { type: 'null', value: null } | { type: 'boolean', value: boolean } | { type: 'number', value: number } | { type: 'string', value: string } | { type: 'array', value: Value[] } | { type: 'dict', value: Dict } | { type: 'regex', value: RegExp } | { type: 'function', params: string[], defaults: Record, // indices into constants body: number, parentScope: Scope, variadic: boolean, named: boolean, value: '' } | { type: 'native_function', fn: NativeFunction, value: '' } export type Dict = Map export type FunctionDef = { type: 'function_def' params: string[] defaults: Record body: number variadic: boolean named: boolean } export function toValue(v: any): Value /* throws */ { if (v === null || v === undefined) return { type: 'null', value: null } if (v && typeof v === 'object' && 'type' in v && 'value' in v) return v as Value if (Array.isArray(v)) return { type: 'array', value: v.map(toValue) } if (v instanceof RegExp) return { type: 'regex', value: v } switch (typeof v) { case 'boolean': return { type: 'boolean', value: v } case 'number': return { type: 'number', value: v } case 'string': return { type: 'string', value: v } case 'function': throw "can't toValue() a js function yet" case 'object': const dict: Dict = new Map() for (const key of Object.keys(v)) dict.set(key, toValue(v[key])) return { type: 'dict', value: dict } default: throw `can't toValue this: ${v}` } } export function toNumber(v: Value): number { switch (v.type) { case 'number': return v.value case 'boolean': return v.value ? 1 : 0 case 'string': { const parsed = parseFloat(v.value) return isNaN(parsed) ? 0 : parsed } default: return 0 } } export function isTrue(v: Value): boolean { switch (v.type) { case 'null': return false case 'boolean': return v.value default: return true } } export function toString(v: Value): string { switch (v.type) { case 'string': return v.value case 'number': return String(v.value) case 'boolean': return String(v.value) case 'null': return 'null' case 'function': return '' case 'native_function': return '' case 'array': return `[${v.value.map(toString).join(', ')}]` case 'dict': { const pairs = Array.from(v.value.entries()).map(([k, v]) => `${k}: ${toString(v)}`) return `{${pairs.join(', ')}}` } case 'regex': return String(v.value) default: return String(v) } } export function isEqual(a: Value, b: Value): boolean { if (a.type !== b.type) return false switch (a.type) { case 'null': return true case 'boolean': return a.value === b.value case 'number': return a.value === b.value case 'string': return a.value === b.value case 'array': { const bArr = b as typeof a if (a.value.length !== bArr.value.length) return false return a.value.every((v, i) => isEqual(v, bArr.value[i]!)) } case 'dict': { const bDict = b as typeof a if (a.value.size !== bDict.value.size) return false for (const [k, v] of a.value) { const bVal = bDict.value.get(k) if (!bVal || !isEqual(v, bVal)) return false } return true } case 'regex': { return String(a.value) === String(b.value) } case 'function': return false // functions never equal case 'native_function': return false // native functions never equal default: return false } } export function fromValue(v: Value): any { switch (v.type) { case 'null': return null case 'boolean': return v.value case 'number': return v.value case 'string': return v.value case 'array': return v.value.map(fromValue) case 'dict': return Object.fromEntries(v.value.entries().map(([k, v]) => [k, fromValue(v)])) case 'regex': return v.value case 'function': return '' case 'native_function': return '' } } export function toNull(): Value { return toValue(null) } const WRAPPED_MARKER = Symbol('reef-wrapped') export function wrapNative(fn: Function): (...args: Value[]) => Promise { const wrapped = async (...values: Value[]) => { const nativeArgs = values.map(fromValue) const result = await fn(...nativeArgs) return toValue(result) } const wrappedObj = wrapped as any wrappedObj[WRAPPED_MARKER] = true return wrapped } export function isWrapped(fn: Function): boolean { return !!(fn as any)[WRAPPED_MARKER] }