122 lines
3.1 KiB
TypeScript
122 lines
3.1 KiB
TypeScript
import { Scope } from "./scope"
|
|
|
|
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: 'function',
|
|
params: string[],
|
|
defaults: Record<string, Value>,
|
|
body: number,
|
|
parentScope: Scope,
|
|
variadic: boolean,
|
|
kwargs: boolean
|
|
}
|
|
|
|
export type Dict = Map<string, Value>
|
|
|
|
export type FunctionDef = {
|
|
type: 'function_def'
|
|
params: string[]
|
|
defaults: Record<string, number>
|
|
body: number
|
|
variadic: boolean
|
|
kwargs: boolean
|
|
}
|
|
|
|
export function toValue(v: any): Value /* throws */ {
|
|
if (v === null || v === undefined)
|
|
return { type: 'null', value: null }
|
|
|
|
if (Array.isArray(v))
|
|
return { type: 'array', value: v.map(toValue) }
|
|
|
|
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 in 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 '<function>'
|
|
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(', ')}}`
|
|
}
|
|
}
|
|
}
|
|
|
|
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 as typeof a).value
|
|
case 'number': return a.value === (b as typeof a).value
|
|
case 'string': return a.value === (b as typeof a).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 'function': return false // functions never equal
|
|
}
|
|
} |