171 lines
4.2 KiB
TypeScript
171 lines
4.2 KiB
TypeScript
import { Effect } from "effect"
|
|
import { preserveConsumerError, type SyncIteratorRunner } from "../interpreter/iterator.js"
|
|
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
|
|
// Bun exposes ES2026 Math.sumPrecise before TypeScript's standard library types.
|
|
declare global {
|
|
interface Math {
|
|
sumPrecise(values: Iterable<number>): number
|
|
}
|
|
}
|
|
|
|
export const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"])
|
|
|
|
export const mathMethods = new Set([
|
|
"random",
|
|
"max",
|
|
"min",
|
|
"abs",
|
|
"acos",
|
|
"acosh",
|
|
"asin",
|
|
"asinh",
|
|
"atan",
|
|
"atan2",
|
|
"atanh",
|
|
"floor",
|
|
"ceil",
|
|
"round",
|
|
"trunc",
|
|
"sign",
|
|
"sqrt",
|
|
"cbrt",
|
|
"pow",
|
|
"hypot",
|
|
"cos",
|
|
"cosh",
|
|
"sin",
|
|
"sinh",
|
|
"tan",
|
|
"tanh",
|
|
"log",
|
|
"log2",
|
|
"log10",
|
|
"log1p",
|
|
"exp",
|
|
"expm1",
|
|
"f16round",
|
|
"fround",
|
|
"clz32",
|
|
"imul",
|
|
"sumPrecise",
|
|
])
|
|
|
|
export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
|
|
if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available.`, node)
|
|
if (name === "random") return Math.random()
|
|
// Validate only the arguments the method consumes; like JS, extras are ignored
|
|
// (so built-ins work as callbacks receiving (element, index, array)).
|
|
const num = (index: number): number => {
|
|
if (index >= args.length) return Number.NaN
|
|
const arg = args[index]
|
|
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
|
|
return arg
|
|
}
|
|
const nums = () =>
|
|
args.map((arg) => {
|
|
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
|
|
return arg
|
|
})
|
|
const a = num(0)
|
|
const b = () => num(1)
|
|
switch (name) {
|
|
case "max":
|
|
return Math.max(...nums())
|
|
case "min":
|
|
return Math.min(...nums())
|
|
case "abs":
|
|
return Math.abs(a)
|
|
case "acos":
|
|
return Math.acos(a)
|
|
case "acosh":
|
|
return Math.acosh(a)
|
|
case "asin":
|
|
return Math.asin(a)
|
|
case "asinh":
|
|
return Math.asinh(a)
|
|
case "atan":
|
|
return Math.atan(a)
|
|
case "atan2":
|
|
return Math.atan2(a, b())
|
|
case "atanh":
|
|
return Math.atanh(a)
|
|
case "floor":
|
|
return Math.floor(a)
|
|
case "ceil":
|
|
return Math.ceil(a)
|
|
case "round":
|
|
return Math.round(a)
|
|
case "trunc":
|
|
return Math.trunc(a)
|
|
case "sign":
|
|
return Math.sign(a)
|
|
case "sqrt":
|
|
return Math.sqrt(a)
|
|
case "cbrt":
|
|
return Math.cbrt(a)
|
|
case "pow":
|
|
return Math.pow(a, b())
|
|
case "hypot":
|
|
return Math.hypot(...nums())
|
|
case "cos":
|
|
return Math.cos(a)
|
|
case "cosh":
|
|
return Math.cosh(a)
|
|
case "sin":
|
|
return Math.sin(a)
|
|
case "sinh":
|
|
return Math.sinh(a)
|
|
case "tan":
|
|
return Math.tan(a)
|
|
case "tanh":
|
|
return Math.tanh(a)
|
|
case "log":
|
|
return Math.log(a)
|
|
case "log2":
|
|
return Math.log2(a)
|
|
case "log10":
|
|
return Math.log10(a)
|
|
case "log1p":
|
|
return Math.log1p(a)
|
|
case "exp":
|
|
return Math.exp(a)
|
|
case "expm1":
|
|
return Math.expm1(a)
|
|
case "f16round":
|
|
return Math.f16round(a)
|
|
case "fround":
|
|
return Math.fround(a)
|
|
case "clz32":
|
|
return Math.clz32(a)
|
|
case "imul":
|
|
return Math.imul(a, b())
|
|
}
|
|
throw new InterpreterRuntimeError(`Math.${name} is not available.`, node)
|
|
}
|
|
|
|
export const invokeMathSumPrecise = <R>(
|
|
runner: SyncIteratorRunner<R>,
|
|
source: unknown,
|
|
node: AstNode,
|
|
): Effect.Effect<number, unknown, R> =>
|
|
Effect.gen(function* () {
|
|
const cursor = yield* runner.syncIterator(source, node)
|
|
if (cursor === undefined) {
|
|
throw new InterpreterRuntimeError("Math.sumPrecise expects a synchronous iterable.", node).as("TypeError")
|
|
}
|
|
const numbers: Array<number> = []
|
|
while (true) {
|
|
const step = yield* cursor.next
|
|
if (step.done) return Math.sumPrecise(numbers)
|
|
yield* preserveConsumerError(
|
|
cursor,
|
|
Effect.sync(() => {
|
|
if (typeof step.value !== "number") {
|
|
throw new InterpreterRuntimeError("Math.sumPrecise expects an iterable of numbers.", node).as("TypeError")
|
|
}
|
|
numbers.push(step.value)
|
|
}),
|
|
)
|
|
}
|
|
})
|