From b34403708eb19ded2983fa03e4577d9b48f81358 Mon Sep 17 00:00:00 2001 From: Wangdahai Date: Wed, 30 Apr 2025 23:56:58 +0800 Subject: [PATCH 1/4] constants and functions in Python stdlib --- src/stdlib.ts | 1786 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1785 insertions(+), 1 deletion(-) diff --git a/src/stdlib.ts b/src/stdlib.ts index 12c4c22..0d3366a 100644 --- a/src/stdlib.ts +++ b/src/stdlib.ts @@ -1,17 +1,1789 @@ import { ArrowFunctionExpression } from "estree"; import { Closure } from "./cse-machine/closure"; import { Value } from "./cse-machine/stash"; +// npm install mathjs +import { gamma, lgamma, erf } from 'mathjs'; +import { addPrint } from "./cse-machine/interpreter"; +import { handleRuntimeError } from "./cse-machine/utils"; +import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueError, TypeError } from "./errors/errors"; +import { ControlItem } from "./cse-machine/control"; +import { Context } from "./cse-machine/context"; +import * as es from 'estree'; +import { Instr } from "./cse-machine/types"; +import { Identifier } from "./conductor/types"; /* Create a map to hold built-in constants. Each constant is stored with a string key and its corresponding value object. */ export const builtInConstants = new Map(); +const math_e = { type: 'number', value: Math.E }; +const math_inf = { type: 'number', value: Infinity }; +const math_nan = { type: 'number', value: NaN }; +const math_pi = { type: 'number', value: Math.PI }; +const math_tau = { type: 'number', value: 2 * Math.PI }; + +builtInConstants.set('math_e', math_e); +builtInConstants.set('math_inf', math_inf); +builtInConstants.set('math_nan', math_nan); +builtInConstants.set('math_pi', math_pi); +builtInConstants.set('math_tau', math_tau); + /* Create a map to hold built-in functions. The keys are strings (function names) and the values are functions that can take any arguments. */ export const builtIns = new Map any>(); +builtIns.set('_int', _int); +builtIns.set('_int_from_string', _int_from_string); +builtIns.set('abs', abs); +builtIns.set('char_at', char_at); +builtIns.set('error', error); +builtIns.set('input', input); +builtIns.set('isinstance', isinstance); +builtIns.set('math_acos', math_acos); +builtIns.set('math_acosh', math_acosh); +builtIns.set('math_asin', math_asin); +builtIns.set('math_asinh', math_asinh); +builtIns.set('math_atan', math_atan); +builtIns.set('math_atan2', math_atan2); +builtIns.set('math_atanh', math_atanh); +builtIns.set('math_cbrt', math_cbrt); +builtIns.set('math_ceil', math_ceil); +builtIns.set('math_comb', math_comb); +builtIns.set('math_copysign', math_copysign); +builtIns.set('math_cos', math_cos); +builtIns.set('math_cosh', math_cosh); +builtIns.set('math_degrees', math_degrees); +builtIns.set('math_erf', math_erf); +builtIns.set('math_erfc', math_erfc); +builtIns.set('math_exp', math_exp); +builtIns.set('math_exp2', math_exp2); +builtIns.set('math_expm1', math_expm1); +builtIns.set('math_fabs', math_fabs); +builtIns.set('math_factorial', math_factorial); +builtIns.set('math_floor', math_floor); +builtIns.set('math_fma', math_fma); +builtIns.set('math_fmod', math_fmod); +builtIns.set('math_gamma', math_gamma); +builtIns.set('math_lgamma', math_lgamma); +builtIns.set('math_gcd', math_gcd); +builtIns.set('math_isfinite', math_isfinite); +builtIns.set('math_isinf', math_isinf); +builtIns.set('math_isnan', math_isnan); +builtIns.set('math_isqrt', math_isqrt); +builtIns.set('math_lcm', math_lcm); +builtIns.set('math_ldexp', math_ldexp); +builtIns.set('math_log', math_log); +builtIns.set('math_log10', math_log10); +builtIns.set('math_log1p', math_log1p); +builtIns.set('math_log2', math_log2); +builtIns.set('math_nextafter', math_nextafter); +builtIns.set('math_perm', math_perm); +builtIns.set('math_pow', math_pow); +builtIns.set('math_radians', math_radians); +builtIns.set('math_remainder', math_remainder); +builtIns.set('math_sin', math_sin); +builtIns.set('math_sinh', math_sinh); +builtIns.set('math_sqrt', math_sqrt); +builtIns.set('math_tan', math_tan); +builtIns.set('math_tanh', math_tanh); +builtIns.set('math_trunc', math_trunc); +builtIns.set('math_ulp', math_ulp); +builtIns.set('max', max); +builtIns.set('min', min); +builtIns.set('print', print); +builtIns.set('random_random', random_random); +builtIns.set('round', round); +builtIns.set('str', str); +builtIns.set('time_time', time_time); + +export function _int(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: '0' }; + } + if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), '_int', Number(1), args, true)); + } + + const arg = args[0]; + // If the value is a number, use Math.trunc to truncate toward zero. + if (arg.type === 'number') { + const truncated = Math.trunc(arg.value); + return { type: 'bigint', value: BigInt(truncated) }; + } + // If the value is a bigint, simply return the same value. + if (arg.type === 'bigint') { + return { type: 'bigint', value: arg.value }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, arg.type, "float' or 'int")); +} + +export function _int_from_string(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), '_int_from_string', Number(1), args, true)); + } + if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), '_int_from_string', Number(2), args, true)); + } + + const strVal = args[0]; + if (strVal.type !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); + } + + let base: number = 10; + if (args.length === 2) { + // The second argument must be either a bigint or a number (it will be converted to a number for uniform processing). + const baseVal = args[1]; + if (baseVal.type === 'bigint') { + base = Number(baseVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "float' or 'int")); + } + } + + // base should be in between 2 and 36 + if (base < 2 || base > 36) { + throw new Error(`_int_from_string: base must be in [2..36], got ${base}`); + } + + let str = strVal.value as string; + str = str.trim(); + str = str.replace(/_/g, ''); + + // Parse the sign (determine if the value is positive or negative) + let sign: bigint = BigInt(1); + if (str.startsWith('+')) { + str = str.slice(1); + } else if (str.startsWith('-')) { + sign = BigInt(-1); + str = str.slice(1); + } + + // The remaining portion must consist of valid characters for the specified base. + const parsedNumber = parseInt(str, base); + if (isNaN(parsedNumber)) { + throw new Error(`_int_from_string: cannot parse "${strVal.value}" with base ${base}`); + } + + const result: bigint = sign * BigInt(parsedNumber); + + return { type: 'bigint', value: result }; +} + +export function abs(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'abs', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'abs', Number(1), args, false)); + } + + const x = args[0]; + switch (x.type) { + case 'bigint': { + const intVal = x.value; + const result: bigint = intVal < 0 ? -intVal : intVal; + return { type: 'int', value: result }; + } + case 'number': { + return { type: 'number', value: Math.abs(x.value) }; + } + case 'complex': { + // Calculate the modulus (absolute value) of a complex number. + const real = x.value.real; + const imag = x.value.imag; + const modulus = Math.sqrt(real * real + imag * imag); + return { type: 'number', value: modulus }; + } + default: + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float', 'int' or 'complex")); + } +} + +function toStr(val: Value): string { + return String(val.value); +} + +export function error(args: Value[], source: string, command: ControlItem, context: Context): Value { + const output = "Error: " + args.map(arg => toStr(arg)).join(' ') + '\n'; + throw new Error(output); +} + +export function isinstance(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'isinstance', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'isinstance', Number(2), args, false)); + } + + const obj = args[0]; + const classinfo = args[1]; + + let expectedType: string; + if (classinfo.type === 'string') { + switch (classinfo.value) { + case 'int': + expectedType = 'bigint'; + break; + case 'float': + expectedType = 'number'; + break; + case 'string': + expectedType = 'string'; + break; + case 'bool': + expectedType = 'bool'; + break; + case 'complex': + expectedType = 'complex'; + break; + case 'NoneType': + expectedType = 'NoneType'; + break; + default: + throw new Error(`isinstance: unknown type '${classinfo.value}'`); + } + } else { + // TODO: If the value is not in string format, additional handling can be added as needed. + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); + return; + } + + const result = obj.type === expectedType; + + return { type: 'bool', value: result }; +} + +export function math_acos(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_acos', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_acos', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < -1 || num > 1) { + throw new Error(`math_acos: argument must be in the interval [-1, 1], but got ${num}`); + } + + const result = Math.acos(num); + return { type: 'number', value: result }; +} + +export function math_acosh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_acosh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_acosh', Number(1), args, false)); + } + + const x = args[0]; + + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < 1) { + throw new Error(`math_acosh: argument must be greater than or equal to 1, but got ${num}`); + } + + const result = Math.acosh(num); + return { type: 'number', value: result }; +} + +export function math_asin(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_asin', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_asin', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < -1 || num > 1) { + throw new Error(`math_asin: argument must be in the interval [-1, 1], but got ${num}`); + } + + const result = Math.asin(num); + return { type: 'number', value: result }; +} + +export function math_asinh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_asinh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_asinh', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.asinh(num); + return { type: 'number', value: result }; +} + +export function math_atan(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atan', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atan', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.atan(num); + return { type: 'number', value: result }; +} + +export function math_atan2(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atan', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atan', Number(2), args, false)); + } + + const y = args[0]; + const x = args[1]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } else if (y.type !== 'number' && y.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + } + + let yNum: number, xNum: number; + if (y.type === 'number') { + yNum = y.value; + } else { + yNum = Number(y.value); + } + + if (x.type === 'number') { + xNum = x.value; + } else { + xNum = Number(x.value); + } + + const result = Math.atan2(yNum, xNum); + return { type: 'number', value: result }; +} + +export function math_atanh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atanh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atanh', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num <= -1 || num >= 1) { + throw new Error(`math_atanh: argument must be in the interval (-1, 1), but got ${num}`); + } + + const result = Math.atanh(num); + return { type: 'number', value: result }; +} + +export function math_cos(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cos', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cos', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.cos(num); + return { type: 'number', value: result }; +} + +export function math_cosh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cosh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cosh', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.cosh(num); + return { type: 'number', value: result }; +} + +export function math_degrees(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_degrees', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_degrees', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = num * 180 / Math.PI; + return { type: 'number', value: result }; +} + +export function math_erf(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_erf', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_erf', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const erfnum = erf(num); + + return { type: 'number', value: erfnum }; +} + +export function math_erfc(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_erfc', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_erfc', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + const erfc = 1 - math_erf(args[0], source, command, context).value; + + return { type: 'number', value: erfc }; +} + +export function char_at(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'char_at', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'char_at', Number(2), args, false)); + } + + const s = args[0]; + const i = args[1]; + + if (s.type !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, s.type, "string")); + } + if (i.type !== 'number' && i.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, i.type, "float' or 'int")); + } + + const index = i.value; + + return { type: 'string', value: (s.value)[index]}; +} + +export function math_comb(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_comb', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_comb', Number(2), args, false)); + } + + const n = args[0]; + const k = args[1]; + + if (n.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); + } else if (k.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, k.type, "int")); + } + + const nVal = BigInt(n.value); + const kVal = BigInt(k.value); + + if (nVal < 0 || kVal < 0) { + throw new Error(`comb: n and k must be non-negative, got n=${nVal}, k=${kVal}`); + } + + if (kVal > nVal) { + return { type: 'bigint', value: BigInt(0) }; + } + + let result: bigint = BigInt(1); + let kk = kVal > nVal - kVal ? nVal - kVal : kVal; + + for (let i: bigint = BigInt(0); i < kk; i++) { + result = result * (nVal - i) / (i + BigInt(1)); + } + + return { type: 'bigint', value: result }; +} + +export function math_factorial(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_factorial', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_factorial', Number(1), args, false)); + } + + const n = args[0]; + + if (n.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); + } + + const nVal = BigInt(n.value); + + if (nVal < 0) { + throw new Error(`factorial: argument must be non-negative, but got ${nVal}`); + } + + // 0! = 1 + if (nVal === BigInt(0)) { + return { type: 'bigint', value: BigInt(1) }; + } + + let result: bigint = BigInt(1); + for (let i: bigint = BigInt(1); i <= nVal; i++) { + result *= i; + } + + return { type: 'bigint', value: result }; +} + +export function math_gcd(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: BigInt(0) }; + } + + const values = args.map((v, idx) => { + if (v.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, v.type, "int")); + } + return BigInt(v.value); + }); + + const allZero = values.every(val => val === BigInt(0)); + if (allZero) { + return { type: 'bigint', value: BigInt(0) }; + } + + let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0]; + for (let i = 1; i < values.length; i++) { + currentGcd = gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]); + if (currentGcd === BigInt(1)) { + break; + } + } + + return { type: 'bigint', value: currentGcd }; +} + +function gcdOfTwo(a: bigint, b: bigint): bigint { + let x: bigint = a; + let y: bigint = b; + while (y !== BigInt(0)) { + const temp = x % y; + x = y; + y = temp; + } + return x < 0 ? -x : x; +} + +export function math_isqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isqrt', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isqrt', Number(1), args, false)); + } + + const nValObj = args[0]; + if (nValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); + } + + const n: bigint = nValObj.value; + + if (n < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_isqrt")); + } + + if (n < 2) { + return { type: 'bigint', value: n }; + } + + let low: bigint = BigInt(1); + let high: bigint = n; + + while (low < high) { + const mid = (low + high + BigInt(1)) >> BigInt(1); + const sq = mid * mid; + if (sq <= n) { + low = mid; + } else { + high = mid - BigInt(1); + } + } + + return { type: 'bigint', value: low }; +} + +export function math_lcm(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: BigInt(1) }; + } + + const values = args.map((val, idx) => { + if (val.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "int")); + } + return BigInt(val.value); + }); + + if (values.some(v => v === BigInt(0))) { + return { type: 'bigint', value: BigInt(0) }; + } + + let currentLcm: bigint = absBigInt(values[0]); + for (let i = 1; i < values.length; i++) { + currentLcm = lcmOfTwo(currentLcm, absBigInt(values[i])); + if (currentLcm === BigInt(0)) { + break; + } + } + + return { type: 'bigint', value: currentLcm }; +} + +function lcmOfTwo(a: bigint, b: bigint): bigint { + const gcdVal: bigint = gcdOfTwo(a, b); + return BigInt((a / gcdVal) * b); +} + +function absBigInt(x: bigint): bigint { + return x < 0 ? -x : x; +} + +export function math_perm(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_perm', Number(1), args, true)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_perm', Number(2), args, true)); + } + + const nValObj = args[0]; + if (nValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); + } + const n = BigInt(nValObj.value); + + let k = n; + if (args.length === 2) { + const kValObj = args[1]; + if (kValObj.type === 'null' || kValObj.type === 'undefined') { + k = n; + } else if (kValObj.type === 'bigint') { + k = BigInt(kValObj.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, kValObj.type, "int' or 'None")); + } + } + + if (n < 0 || k < 0) { + throw new Error(`perm: n and k must be non-negative, got n=${n}, k=${k}`); + } + + if (k > n) { + return { type: 'bigint', value: BigInt(0) }; + } + + let result: bigint = BigInt(1); + for (let i: bigint = BigInt(0); i < k; i++) { + result *= (n - i); + } + + return { type: 'bigint', value: result }; +} + +export function math_ceil(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_ceil', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_ceil', Number(1), args, false)); + } + + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const ceiled: bigint = BigInt(Math.ceil(numVal)); + return { type: 'bigint', value: ceiled }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); +} + +export function math_fabs(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fabs', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fabs', Number(1), args, false)); + } + + const x = args[0]; + + if (x.type === 'bigint') { + const bigVal: bigint = BigInt(x.value); + const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal); + return { type: 'number', value: absVal }; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const absVal: number = Math.abs(numVal); + return { type: 'number', value: absVal }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); +} + +export function math_floor(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_floor', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_floor', Number(1), args, false)); + } + + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const floored: bigint = BigInt(Math.floor(numVal)); + return { type: 'bigint', value: floored }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); +} + +// Computes the product of a and b along with the rounding error using Dekker's algorithm. +function twoProd(a: number, b: number): { prod: number; err: number } { + const prod = a * b; + const c = 134217729; // 2^27 + 1 + const a_hi = (a * c) - ((a * c) - a); + const a_lo = a - a_hi; + const b_hi = (b * c) - ((b * c) - b); + const b_lo = b - b_hi; + const err = a_lo * b_lo - (((prod - a_hi * b_hi) - a_lo * b_hi) - a_hi * b_lo); + return { prod, err }; +} + +// Computes the sum of a and b along with the rounding error using Fast TwoSum. +function twoSum(a: number, b: number): { sum: number; err: number } { + const sum = a + b; + const v = sum - a; + const err = (a - (sum - v)) + (b - v); + return { sum, err }; +} + +// Performs a fused multiply-add operation: computes (x * y) + z with a single rounding. +function fusedMultiplyAdd(x: number, y: number, z: number): number { + const { prod, err: prodErr } = twoProd(x, y); + const { sum, err: sumErr } = twoSum(prod, z); + const result = sum + (prodErr + sumErr); + return result; +} + +function toNumber(val: Value, source: string, command: ControlItem, context: Context): number { + if (val.type === 'bigint') { + return Number(val.value); + } else if (val.type === 'number') { + return val.value as number; + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "float' or 'int")); + return 0; + } +} + +export function math_fma(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 3) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fma', Number(3), args, false)); + } else if (args.length > 3) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fma', Number(3), args, false)); + } + + const xVal = toNumber(args[0], source, command, context); + const yVal = toNumber(args[1], source, command, context); + const zVal = toNumber(args[2], source, command, context); + + // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan) + // and fma(inf, 0, nan) should return NaN. + if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + + const result = fusedMultiplyAdd(xVal, yVal, zVal); + return { type: 'number', value: result }; +} + +export function math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fmod', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fmod', Number(2), args, false)); + } + + // Convert inputs to numbers + const xVal = toNumber(args[0], source, command, context); + const yVal = toNumber(args[1], source, command, context); + + // Divisor cannot be zero + if (yVal === 0) { + throw new Error("fmod: divisor (y) must not be zero"); + } + + // JavaScript's % operator behaves similarly to C's fmod + // in that the sign of the result is the same as the sign of x. + // For corner cases (NaN, Infinity), JavaScript remainder + // yields results consistent with typical C library fmod behavior. + const remainder = xVal % yVal; + + return { type: 'number', value: remainder }; +} + +function roundToEven(num: number): number { + const floorVal = Math.floor(num); + const ceilVal = Math.ceil(num); + const diffFloor = num - floorVal; + const diffCeil = ceilVal - num; + if (diffFloor < diffCeil) { + return floorVal; + } else if (diffCeil < diffFloor) { + return ceilVal; + } else { + return (floorVal % 2 === 0) ? floorVal : ceilVal; + } +} + +export function math_remainder(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_remainder', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_remainder', Number(2), args, false)); + } + + const x = args[0]; + const y = args[1]; + + let xValue: number; + if (x.type === 'bigint') { + xValue = Number(x.value); + } else if (x.type === 'number') { + xValue = x.value as number; + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + return; + } + + let yValue: number; + if (y.type === 'bigint') { + yValue = Number(y.value); + } else if (y.type === 'number') { + yValue = y.value as number; + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + return; + } + + if (yValue === 0) { + throw new Error(`remainder: divisor y must not be zero`); + } + + const quotient = xValue / yValue; + const n = roundToEven(quotient); + const remainder = xValue - n * yValue; + + return { type: 'number', value: remainder }; +} + +export function math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_trunc', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_trunc', Number(1), args, false)); + } + + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + let truncated: number; + if (numVal === 0) { + truncated = 0; + } else if (numVal < 0) { + truncated = Math.ceil(numVal); + } else { + truncated = Math.floor(numVal); + } + return { type: 'bigint', value: BigInt(truncated) }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); +} + +export function math_copysign(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_copysign', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_copysign', Number(2), args, false)); + } + + const [x, y] = args; + + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } else if (y.type !== 'number' && y.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + } + + const xVal = Number(x.value) as number; + const yVal = Number(y.value) as number; + + const absVal = Math.abs(xVal); + const isNegative = yVal < 0 || (Object.is(yVal, -0)); + const result = isNegative ? -absVal : absVal; + + return { type: 'number', value: Number(result) }; +} + +export function math_isfinite(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isfinite', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isfinite', Number(1), args, false)); + } + + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = Number.isFinite(x); + + return { type: 'bool', value: result }; +} + +export function math_isinf(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isinf', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isinf', Number(1), args, false)); + } + + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = (x === Infinity || x === -Infinity); + + return { type: 'bool', value: result }; +} + +export function math_isnan(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isnan', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isnan', Number(1), args, false)); + } + + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = Number.isNaN(x); + + return { type: 'bool', value: result }; +} + +export function math_ldexp(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_ldexp', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_ldexp', Number(2), args, false)); + } + + const xVal = toNumber(args[0], source, command, context); + + if (args[1].type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "int")); + } + const expVal = args[1].value; + + // Perform x * 2^expVal + // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively. + // That behavior parallels typical C library rules for ldexp. + const result = xVal * Math.pow(2, Number(expVal)); + + return { type: 'number', value: result }; +} + +export function math_nextafter(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.) + throw new Error("math_nextafter not implemented"); +} + +export function math_ulp(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number. + throw new Error("math_ulp not implemented"); +} + +export function math_cbrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cbrt', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cbrt', Number(1), args, false)); + } + + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } + } else { + x = xVal.value as number; + } + + const result = Math.cbrt(x); + + return { type: 'number', value: result }; +} + +export function math_exp(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_exp', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_exp', Number(1), args, false)); + } + + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } + } else { + x = xVal.value as number; + } + + const result = Math.exp(x); + return { type: 'number', value: result }; +} + +export function math_exp2(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_exp2', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_exp2', Number(1), args, false)); + } + + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } + } else { + x = xVal.value as number; + } + + const result = Math.pow(2, x); + return { type: 'number', value: result }; +} + +export function math_expm1(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_expm1', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_expm1', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.expm1(num); + return { type: 'number', value: result }; +} + +export function math_gamma(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_gamma', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_gamma', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + const z = toNumber(x, source, command, context); + const result = gamma(z); + + return { type: 'number', value: result }; +} + +export function math_lgamma(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_lgamma', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_lgamma', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + const z = toNumber(x, source, command, context); + const result = lgamma(z); + + return { type: 'number', value: result }; +} + +export function math_log(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_gamma', Number(1), args, true)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_gamma', Number(2), args, true)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num <= 0) { + throw new Error(`math_log: argument must be positive, but got ${num}`); + } + + if (args.length === 1) { + return { type: 'number', value: Math.log(num) }; + } + + const baseArg = args[1]; + if (baseArg.type !== 'number' && baseArg.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, baseArg.type, "float' or 'int")); + } + let baseNum: number; + if (baseArg.type === 'number') { + baseNum = baseArg.value; + } else { + baseNum = Number(baseArg.value); + } + if (baseNum <= 0) { + throw new Error(`math_log: base must be positive, but got ${baseNum}`); + } + + const result = Math.log(num) / Math.log(baseNum); + return { type: 'number', value: result }; +} + +export function math_log10(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log10', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log10', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (num <= 0) { + throw new Error(`math_log10: argument must be positive, but got ${num}`); + } + + const result = Math.log10(num); + return { type: 'number', value: result }; +} + +export function math_log1p(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log1p', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log1p', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (1 + num <= 0) { + throw new Error(`math_log1p: 1 + argument must be positive, but got 1 + ${num} = ${1 + num}`); + } + + const result = Math.log1p(num); + return { type: 'number', value: result }; +} + +export function math_log2(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log2', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log2', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (num <= 0) { + throw new Error(`math_log2: argument must be positive, but got ${num}`); + } + + const result = Math.log2(num); + return { type: 'number', value: result }; +} + +export function math_pow(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_pow', Number(2), args, false)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_pow', Number(2), args, false)); + } + + const base = args[0]; + const exp = args[1]; + + if (base.type !== 'number' && base.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, base.type, "float' or 'int")); + } else if (exp.type !== 'number' && exp.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, exp.type, "float' or 'int")); + } + + let baseNum: number; + if (base.type === 'number') { + baseNum = base.value; + } else { // 'bigint' + baseNum = Number(base.value); + } + + let expNum: number; + if (exp.type === 'number') { + expNum = exp.value; + } else { + expNum = Number(exp.value); + } + + const result = Math.pow(baseNum, expNum); + return { type: 'number', value: result }; +} + +export function math_radians(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_radians', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_radians', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let deg: number; + if (x.type === 'number') { + deg = x.value; + } else { + deg = Number(x.value); + } + + const radians = deg * Math.PI / 180; + return { type: 'number', value: radians }; +} + +export function math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sin', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sin', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.sin(num); + return { type: 'number', value: result }; +} + +export function math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sinh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sinh', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.sinh(num); + return { type: 'number', value: result }; +} + +export function math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_tan', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_tan', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.tan(num); + return { type: 'number', value: result }; +} + +export function math_tanh(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_tanh', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_tanh', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.tanh(num); + return { type: 'number', value: result }; +} + +export function math_sqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sqrt', Number(1), args, false)); + } else if (args.length > 1) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sqrt', Number(1), args, false)); + } + + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < 0) { + throw new Error(`math_sqrt: argument must be non-negative, but got ${num}`); + } + + const result = Math.sqrt(num); + return { type: 'number', value: result }; +} + +export function max(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'max', Number(2), args, true)); + } + + const numericTypes = ['bigint', 'number']; + const firstType = args[0].type; + let isNumeric = numericTypes.includes(firstType); + let isString = firstType === 'string'; + + for (let i = 1; i < args.length; i++) { + const t = args[i].type; + if (isNumeric && !numericTypes.includes(t)) { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); + } + if (isString && t !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); + } + } + + let useFloat = false; + if (isNumeric) { + for (const arg of args) { + if (arg.type === 'number') { + useFloat = true; + break; + } + } + } + + let maxIndex = 0; + if (isNumeric) { + if (useFloat) { + let maxVal: number = Number(args[0].value); + for (let i = 1; i < args.length; i++) { + const curr: number = Number(args[i].value); + if (curr > maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + let maxVal: bigint = args[0].value; + for (let i = 1; i < args.length; i++) { + const curr: bigint = args[i].value; + if (curr > maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } + } else if (isString) { + let maxVal = args[0].value as string; + for (let i = 1; i < args.length; i++) { + const curr = args[i].value as string; + if (curr > maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + // Won't happen + throw new Error(`max: unsupported type ${firstType}`); + } + + return args[maxIndex]; +} + +export function min(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'min', Number(2), args, true)); + } + + const numericTypes = ['bigint', 'number']; + const firstType = args[0].type; + let isNumeric = numericTypes.includes(firstType); + let isString = firstType === 'string'; + + for (let i = 1; i < args.length; i++) { + const t = args[i].type; + if (isNumeric && !numericTypes.includes(t)) { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); + } + if (isString && t !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); + } + } + + let useFloat = false; + if (isNumeric) { + for (const arg of args) { + if (arg.type === 'number') { + useFloat = true; + break; + } + } + } + + let maxIndex = 0; + if (isNumeric) { + if (useFloat) { + let maxVal: number = Number(args[0].value); + for (let i = 1; i < args.length; i++) { + const curr: number = Number(args[i].value); + if (curr < maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + let maxVal: bigint = args[0].value; + for (let i = 1; i < args.length; i++) { + const curr: bigint = args[i].value; + if (curr < maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } + } else if (isString) { + let maxVal = args[0].value as string; + for (let i = 1; i < args.length; i++) { + const curr = args[i].value as string; + if (curr < maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + // Won't happen + throw new Error(`min: unsupported type ${firstType}`); + } + + return args[maxIndex]; +} + +export function random_random(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length > 0) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'random_random', Number(0), args, false)); + } + const result = Math.random(); + return { type: 'number', value: result }; +} + +export function round(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 1) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'round', Number(1), args, true)); + } else if (args.length > 2) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'round', Number(2), args, true)); + } + + const numArg = args[0]; + if (numArg.type !== 'number' && numArg.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, numArg.type, "float' or 'int")); + } + + let ndigitsArg = { type: 'bigint', value: BigInt(0) }; + if (args.length === 2 && args[1].type !== 'NoneType') { + ndigitsArg = args[1]; + } + + if (numArg.type === 'number') { + let numberValue: number = numArg.value; + if (ndigitsArg.value > 0) { + const shifted = Number(numberValue.toFixed(Number(ndigitsArg.value))); + return { type: 'number', value: shifted }; + } else if (ndigitsArg.value === BigInt(0)) { + const shifted = Math.round(numArg.value); + return { type: 'bigint', value: BigInt(shifted) }; + } else { + const shifted = Math.round(numArg.value / (10 ** (-Number(ndigitsArg.value)))) * (10 ** (-Number(ndigitsArg.value))); + return { type: 'number', value: shifted }; + } + } else { + if (ndigitsArg.value >= 0) { + return numArg; + } else { + const shifted: bigint = numArg.value / (BigInt(10) ** (-ndigitsArg.value)) * (BigInt(10) ** (-ndigitsArg.value)); + return { type: 'bigint', value: shifted }; + } + } +} + +export function time_time(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length > 0) { + handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'time_time', Number(0), args, false)); + } + const currentTime = Date.now(); + return { type: 'number', value: currentTime }; +} /** * Converts a number to a string that mimics Python's float formatting behavior. @@ -83,7 +1855,7 @@ export function toPythonString(obj: Value): string { return ret; } -export function str(args: Value[]): Value { +export function str(args: Value[], source: string, command: ControlItem, context: Context): Value { if (args.length === 0) { return { type: 'string', value: "" }; } @@ -91,3 +1863,15 @@ export function str(args: Value[]): Value { const result = toPythonString(obj); return { type: 'string', value: result }; } + +export function input(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: call conductor to receive user input +} + +export function print(args: Value[], source: string, command: ControlItem, context: Context) { + const pieces = args.map(arg => toPythonString(arg)); + const output = pieces.join(' '); + addPrint(output); + //return { type: 'string', value: output }; +} + \ No newline at end of file From 6c3617f7a9b418bd5dfc3b297d60a5c5f4f1cdf1 Mon Sep 17 00:00:00 2001 From: Wangdahai Date: Thu, 1 May 2025 15:57:20 +0800 Subject: [PATCH 2/4] removed unintended imports --- src/stdlib.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/stdlib.ts b/src/stdlib.ts index 0d3366a..1ea1a27 100644 --- a/src/stdlib.ts +++ b/src/stdlib.ts @@ -9,8 +9,6 @@ import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueE import { ControlItem } from "./cse-machine/control"; import { Context } from "./cse-machine/context"; import * as es from 'estree'; -import { Instr } from "./cse-machine/types"; -import { Identifier } from "./conductor/types"; /* Create a map to hold built-in constants. From c4f2fd011208c62703146399ed278cd7555f026a Mon Sep 17 00:00:00 2001 From: Wangdahai Date: Thu, 1 May 2025 23:01:42 +0800 Subject: [PATCH 3/4] update error handling logic on stdlib --- src/stdlib.ts | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/stdlib.ts b/src/stdlib.ts index 1ea1a27..db3a51c 100644 --- a/src/stdlib.ts +++ b/src/stdlib.ts @@ -5,7 +5,7 @@ import { Value } from "./cse-machine/stash"; import { gamma, lgamma, erf } from 'mathjs'; import { addPrint } from "./cse-machine/interpreter"; import { handleRuntimeError } from "./cse-machine/utils"; -import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueError, TypeError } from "./errors/errors"; +import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueError, TypeError, ZeroDivisionError } from "./errors/errors"; import { ControlItem } from "./cse-machine/control"; import { Context } from "./cse-machine/context"; import * as es from 'estree'; @@ -144,7 +144,7 @@ export function _int_from_string(args: Value[], source: string, command: Control // base should be in between 2 and 36 if (base < 2 || base > 36) { - throw new Error(`_int_from_string: base must be in [2..36], got ${base}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); } let str = strVal.value as string; @@ -163,7 +163,7 @@ export function _int_from_string(args: Value[], source: string, command: Control // The remaining portion must consist of valid characters for the specified base. const parsedNumber = parseInt(str, base); if (isNaN(parsedNumber)) { - throw new Error(`_int_from_string: cannot parse "${strVal.value}" with base ${base}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); } const result: bigint = sign * BigInt(parsedNumber); @@ -241,10 +241,10 @@ export function isinstance(args: Value[], source: string, command: ControlItem, expectedType = 'NoneType'; break; default: - throw new Error(`isinstance: unknown type '${classinfo.value}'`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "isinstance")); + return; } } else { - // TODO: If the value is not in string format, additional handling can be added as needed. handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); return; } @@ -274,7 +274,7 @@ export function math_acos(args: Value[], source: string, command: ControlItem, c } if (num < -1 || num > 1) { - throw new Error(`math_acos: argument must be in the interval [-1, 1], but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acos")); } const result = Math.acos(num); @@ -302,7 +302,7 @@ export function math_acosh(args: Value[], source: string, command: ControlItem, } if (num < 1) { - throw new Error(`math_acosh: argument must be greater than or equal to 1, but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acosh")); } const result = Math.acosh(num); @@ -329,7 +329,7 @@ export function math_asin(args: Value[], source: string, command: ControlItem, c } if (num < -1 || num > 1) { - throw new Error(`math_asin: argument must be in the interval [-1, 1], but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_asin")); } const result = Math.asin(num); @@ -434,7 +434,7 @@ export function math_atanh(args: Value[], source: string, command: ControlItem, } if (num <= -1 || num >= 1) { - throw new Error(`math_atanh: argument must be in the interval (-1, 1), but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_atanh")); } const result = Math.atanh(num); @@ -593,7 +593,7 @@ export function math_comb(args: Value[], source: string, command: ControlItem, c const kVal = BigInt(k.value); if (nVal < 0 || kVal < 0) { - throw new Error(`comb: n and k must be non-negative, got n=${nVal}, k=${kVal}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_comb")); } if (kVal > nVal) { @@ -626,7 +626,7 @@ export function math_factorial(args: Value[], source: string, command: ControlIt const nVal = BigInt(n.value); if (nVal < 0) { - throw new Error(`factorial: argument must be non-negative, but got ${nVal}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_factorial")); } // 0! = 1 @@ -781,7 +781,7 @@ export function math_perm(args: Value[], source: string, command: ControlItem, c } if (n < 0 || k < 0) { - throw new Error(`perm: n and k must be non-negative, got n=${n}, k=${k}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_perm")); } if (k > n) { @@ -952,7 +952,7 @@ export function math_fmod(args: Value[], source: string, command: ControlItem, c // Divisor cannot be zero if (yVal === 0) { - throw new Error("fmod: divisor (y) must not be zero"); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_fmod")); } // JavaScript's % operator behaves similarly to C's fmod @@ -1009,7 +1009,8 @@ export function math_remainder(args: Value[], source: string, command: ControlIt } if (yValue === 0) { - throw new Error(`remainder: divisor y must not be zero`); + + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_remainder")); } const quotient = xValue / yValue; @@ -1316,7 +1317,7 @@ export function math_log(args: Value[], source: string, command: ControlItem, co } if (num <= 0) { - throw new Error(`math_log: argument must be positive, but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); } if (args.length === 1) { @@ -1334,7 +1335,7 @@ export function math_log(args: Value[], source: string, command: ControlItem, co baseNum = Number(baseArg.value); } if (baseNum <= 0) { - throw new Error(`math_log: base must be positive, but got ${baseNum}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); } const result = Math.log(num) / Math.log(baseNum); @@ -1359,7 +1360,7 @@ export function math_log10(args: Value[], source: string, command: ControlItem, num = Number(x.value); } if (num <= 0) { - throw new Error(`math_log10: argument must be positive, but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log10")); } const result = Math.log10(num); @@ -1384,7 +1385,7 @@ export function math_log1p(args: Value[], source: string, command: ControlItem, num = Number(x.value); } if (1 + num <= 0) { - throw new Error(`math_log1p: 1 + argument must be positive, but got 1 + ${num} = ${1 + num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log1p")); } const result = Math.log1p(num); @@ -1409,7 +1410,7 @@ export function math_log2(args: Value[], source: string, command: ControlItem, c num = Number(x.value); } if (num <= 0) { - throw new Error(`math_log2: argument must be positive, but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log2")); } const result = Math.log2(num); @@ -1435,7 +1436,7 @@ export function math_pow(args: Value[], source: string, command: ControlItem, co let baseNum: number; if (base.type === 'number') { baseNum = base.value; - } else { // 'bigint' + } else { baseNum = Number(base.value); } @@ -1585,7 +1586,7 @@ export function math_sqrt(args: Value[], source: string, command: ControlItem, c } if (num < 0) { - throw new Error(`math_sqrt: argument must be non-negative, but got ${num}`); + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_sqrt")); } const result = Math.sqrt(num); @@ -1863,7 +1864,7 @@ export function str(args: Value[], source: string, command: ControlItem, context } export function input(args: Value[], source: string, command: ControlItem, context: Context): Value { - // TODO: call conductor to receive user input + // TODO: : call conductor to receive user input } export function print(args: Value[], source: string, command: ControlItem, context: Context) { From 7ddc9551445c6a85e46bdb6779afe921256e45e5 Mon Sep 17 00:00:00 2001 From: Wangdahai Date: Fri, 16 May 2025 19:11:40 +0800 Subject: [PATCH 4/4] feat: add @Validate decorator, sublanguage violation error, and extract constants to py_s1_constants.json --- src/errors/errors.ts | 31 + src/stdlib.ts | 3034 ++++++++++++++----------------- src/stdlib/py_s1_constants.json | 74 + tsconfig.json | 8 +- 4 files changed, 1509 insertions(+), 1638 deletions(-) create mode 100644 src/stdlib/py_s1_constants.json diff --git a/src/errors/errors.ts b/src/errors/errors.ts index 1d71e70..3341b73 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -268,3 +268,34 @@ export class TypeError extends RuntimeSourceError { this.message = msg; } } + +export class SublanguageError extends RuntimeSourceError { + constructor ( + source: string, + node: es.Node, + context: Context, + functionName: string, + chapter: string, + details?: string +) { + super(node) + + this.type = ErrorType.TYPE + + const index = (node as any).loc?.start?.index + ?? (node as any).srcNode?.loc?.start?.index + ?? 0 + const { line, fullLine } = getFullLine(source, index) + const snippet = (node as any).loc?.source + ?? (node as any).srcNode?.loc?.source + ?? '' + const offset = fullLine.indexOf(snippet) + const indicator = createErrorIndicator(snippet, '@') + + const name = 'SublanguageError' + const hint = 'Feature not supported in Python §' + chapter + '. ' + const suggestion = `The call to '${functionName}()' relies on behaviour that is valid in full Python but outside the Python §1 sublanguage${details ? ': ' + details : ''}.` + + this.message = `${name} at line ${line}\n\n ${fullLine}\n ${' '.repeat(offset)}${indicator}\n${hint}${suggestion}` + } +} diff --git a/src/stdlib.ts b/src/stdlib.ts index db3a51c..eab420a 100644 --- a/src/stdlib.ts +++ b/src/stdlib.ts @@ -1,1787 +1,1565 @@ -import { ArrowFunctionExpression } from "estree"; import { Closure } from "./cse-machine/closure"; import { Value } from "./cse-machine/stash"; // npm install mathjs import { gamma, lgamma, erf } from 'mathjs'; import { addPrint } from "./cse-machine/interpreter"; import { handleRuntimeError } from "./cse-machine/utils"; -import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueError, TypeError, ZeroDivisionError } from "./errors/errors"; +import { MissingRequiredPositionalError, TooManyPositionalArgumentsError, ValueError, TypeError, ZeroDivisionError, SublanguageError } from "./errors/errors"; import { ControlItem } from "./cse-machine/control"; import { Context } from "./cse-machine/context"; import * as es from 'estree'; -/* - Create a map to hold built-in constants. - Each constant is stored with a string key and its corresponding value object. -*/ -export const builtInConstants = new Map(); -const math_e = { type: 'number', value: Math.E }; -const math_inf = { type: 'number', value: Infinity }; -const math_nan = { type: 'number', value: NaN }; -const math_pi = { type: 'number', value: Math.PI }; -const math_tau = { type: 'number', value: 2 * Math.PI }; - -builtInConstants.set('math_e', math_e); -builtInConstants.set('math_inf', math_inf); -builtInConstants.set('math_nan', math_nan); -builtInConstants.set('math_pi', math_pi); -builtInConstants.set('math_tau', math_tau); - -/* - Create a map to hold built-in functions. - The keys are strings (function names) and the values are functions that can take any arguments. -*/ -export const builtIns = new Map any>(); -builtIns.set('_int', _int); -builtIns.set('_int_from_string', _int_from_string); -builtIns.set('abs', abs); -builtIns.set('char_at', char_at); -builtIns.set('error', error); -builtIns.set('input', input); -builtIns.set('isinstance', isinstance); -builtIns.set('math_acos', math_acos); -builtIns.set('math_acosh', math_acosh); -builtIns.set('math_asin', math_asin); -builtIns.set('math_asinh', math_asinh); -builtIns.set('math_atan', math_atan); -builtIns.set('math_atan2', math_atan2); -builtIns.set('math_atanh', math_atanh); -builtIns.set('math_cbrt', math_cbrt); -builtIns.set('math_ceil', math_ceil); -builtIns.set('math_comb', math_comb); -builtIns.set('math_copysign', math_copysign); -builtIns.set('math_cos', math_cos); -builtIns.set('math_cosh', math_cosh); -builtIns.set('math_degrees', math_degrees); -builtIns.set('math_erf', math_erf); -builtIns.set('math_erfc', math_erfc); -builtIns.set('math_exp', math_exp); -builtIns.set('math_exp2', math_exp2); -builtIns.set('math_expm1', math_expm1); -builtIns.set('math_fabs', math_fabs); -builtIns.set('math_factorial', math_factorial); -builtIns.set('math_floor', math_floor); -builtIns.set('math_fma', math_fma); -builtIns.set('math_fmod', math_fmod); -builtIns.set('math_gamma', math_gamma); -builtIns.set('math_lgamma', math_lgamma); -builtIns.set('math_gcd', math_gcd); -builtIns.set('math_isfinite', math_isfinite); -builtIns.set('math_isinf', math_isinf); -builtIns.set('math_isnan', math_isnan); -builtIns.set('math_isqrt', math_isqrt); -builtIns.set('math_lcm', math_lcm); -builtIns.set('math_ldexp', math_ldexp); -builtIns.set('math_log', math_log); -builtIns.set('math_log10', math_log10); -builtIns.set('math_log1p', math_log1p); -builtIns.set('math_log2', math_log2); -builtIns.set('math_nextafter', math_nextafter); -builtIns.set('math_perm', math_perm); -builtIns.set('math_pow', math_pow); -builtIns.set('math_radians', math_radians); -builtIns.set('math_remainder', math_remainder); -builtIns.set('math_sin', math_sin); -builtIns.set('math_sinh', math_sinh); -builtIns.set('math_sqrt', math_sqrt); -builtIns.set('math_tan', math_tan); -builtIns.set('math_tanh', math_tanh); -builtIns.set('math_trunc', math_trunc); -builtIns.set('math_ulp', math_ulp); -builtIns.set('max', max); -builtIns.set('min', min); -builtIns.set('print', print); -builtIns.set('random_random', random_random); -builtIns.set('round', round); -builtIns.set('str', str); -builtIns.set('time_time', time_time); - -export function _int(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length === 0) { - return { type: 'bigint', value: '0' }; - } - if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), '_int', Number(1), args, true)); - } - - const arg = args[0]; - // If the value is a number, use Math.trunc to truncate toward zero. - if (arg.type === 'number') { - const truncated = Math.trunc(arg.value); - return { type: 'bigint', value: BigInt(truncated) }; - } - // If the value is a bigint, simply return the same value. - if (arg.type === 'bigint') { - return { type: 'bigint', value: arg.value }; +export function Validate( + minArgs: number | null, + maxArgs: number | null, + functionName: string, + strict: boolean +) { + return function ( + target: any, + propertyKey: string, + descriptor: TypedPropertyDescriptor<( + args: Value[], + source: string, + command: ControlItem, + context: Context + ) => Value> + ): void { + const originalMethod = descriptor.value! + + descriptor.value = function ( + args: Value[], + source: string, + command: ControlItem, + context: Context + ): Value { + if (minArgs !== null && args.length < minArgs) { + throw new MissingRequiredPositionalError( + source, + command as es.Node, + functionName, + minArgs, + args, + strict + ) + } + + if (maxArgs !== null && args.length > maxArgs) { + throw new TooManyPositionalArgumentsError( + source, + command as es.Node, + functionName, + maxArgs, + args, + strict + ) + } + + return originalMethod.call(this, args, source, command, context) + } } - - handleRuntimeError(context, new TypeError(source, command as es.Node, context, arg.type, "float' or 'int")); } -export function _int_from_string(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), '_int_from_string', Number(1), args, true)); - } - if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), '_int_from_string', Number(2), args, true)); - } - - const strVal = args[0]; - if (strVal.type !== 'string') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); - } - - let base: number = 10; - if (args.length === 2) { - // The second argument must be either a bigint or a number (it will be converted to a number for uniform processing). - const baseVal = args[1]; - if (baseVal.type === 'bigint') { - base = Number(baseVal.value); - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "float' or 'int")); - } - } - - // base should be in between 2 and 36 - if (base < 2 || base > 36) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); - } - - let str = strVal.value as string; - str = str.trim(); - str = str.replace(/_/g, ''); - - // Parse the sign (determine if the value is positive or negative) - let sign: bigint = BigInt(1); - if (str.startsWith('+')) { - str = str.slice(1); - } else if (str.startsWith('-')) { - sign = BigInt(-1); - str = str.slice(1); - } - - // The remaining portion must consist of valid characters for the specified base. - const parsedNumber = parseInt(str, base); - if (isNaN(parsedNumber)) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); - } - - const result: bigint = sign * BigInt(parsedNumber); - - return { type: 'bigint', value: result }; -} - -export function abs(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'abs', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'abs', Number(1), args, false)); - } - - const x = args[0]; - switch (x.type) { - case 'bigint': { - const intVal = x.value; - const result: bigint = intVal < 0 ? -intVal : intVal; - return { type: 'int', value: result }; - } - case 'number': { - return { type: 'number', value: Math.abs(x.value) }; - } - case 'complex': { - // Calculate the modulus (absolute value) of a complex number. - const real = x.value.real; - const imag = x.value.imag; - const modulus = Math.sqrt(real * real + imag * imag); - return { type: 'number', value: modulus }; - } - default: - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float', 'int' or 'complex")); +export class BuiltInFunctions { + @Validate(null, 1, '_int', true) + static _int(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: '0' }; + } + + const arg = args[0]; + // If the value is a number, use Math.trunc to truncate toward zero. + if (arg.type === 'number') { + const truncated = Math.trunc(arg.value); + return { type: 'bigint', value: BigInt(truncated) }; + } + // If the value is a bigint, simply return the same value. + if (arg.type === 'bigint') { + return { type: 'bigint', value: arg.value }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, arg.type, "float' or 'int")); } -} - -function toStr(val: Value): string { - return String(val.value); -} -export function error(args: Value[], source: string, command: ControlItem, context: Context): Value { - const output = "Error: " + args.map(arg => toStr(arg)).join(' ') + '\n'; - throw new Error(output); -} - -export function isinstance(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'isinstance', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'isinstance', Number(2), args, false)); + @Validate(1, 2, '_int_from_string', true) + static _int_from_string(args: Value[], source: string, command: ControlItem, context: Context): Value { + + const strVal = args[0]; + if (strVal.type !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); + } + + let base: number = 10; + if (args.length === 2) { + // The second argument must be either a bigint or a number (it will be converted to a number for uniform processing). + const baseVal = args[1]; + if (baseVal.type === 'bigint') { + base = Number(baseVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "float' or 'int")); + } + } + + // base should be in between 2 and 36 + if (base < 2 || base > 36) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); + } + + let str = strVal.value as string; + str = str.trim(); + str = str.replace(/_/g, ''); + + // Parse the sign (determine if the value is positive or negative) + let sign: bigint = BigInt(1); + if (str.startsWith('+')) { + str = str.slice(1); + } else if (str.startsWith('-')) { + sign = BigInt(-1); + str = str.slice(1); + } + + // The remaining portion must consist of valid characters for the specified base. + const parsedNumber = parseInt(str, base); + if (isNaN(parsedNumber)) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "_int_from_string")); + } + + const result: bigint = sign * BigInt(parsedNumber); + + return { type: 'bigint', value: result }; } - - const obj = args[0]; - const classinfo = args[1]; - let expectedType: string; - if (classinfo.type === 'string') { - switch (classinfo.value) { - case 'int': - expectedType = 'bigint'; - break; - case 'float': - expectedType = 'number'; - break; - case 'string': - expectedType = 'string'; - break; - case 'bool': - expectedType = 'bool'; - break; - case 'complex': - expectedType = 'complex'; - break; - case 'NoneType': - expectedType = 'NoneType'; - break; + @Validate(1, 1, 'abs', false) + static abs(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + switch (x.type) { + case 'bigint': { + const intVal = x.value; + const result: bigint = intVal < 0 ? -intVal : intVal; + return { type: 'int', value: result }; + } + case 'number': { + return { type: 'number', value: Math.abs(x.value) }; + } + case 'complex': { + // Calculate the modulus (absolute value) of a complex number. + const real = x.value.real; + const imag = x.value.imag; + const modulus = Math.sqrt(real * real + imag * imag); + return { type: 'number', value: modulus }; + } default: - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "isinstance")); - return; + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float', 'int' or 'complex")); } - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); - return; } - - const result = obj.type === expectedType; - - return { type: 'bool', value: result }; -} - -export function math_acos(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_acos', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_acos', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - if (num < -1 || num > 1) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acos")); - } - - const result = Math.acos(num); - return { type: 'number', value: result }; -} - -export function math_acosh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_acosh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_acosh', Number(1), args, false)); - } - - const x = args[0]; - - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - if (num < 1) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acosh")); - } - - const result = Math.acosh(num); - return { type: 'number', value: result }; -} - -export function math_asin(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_asin', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_asin', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - if (num < -1 || num > 1) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_asin")); - } - - const result = Math.asin(num); - return { type: 'number', value: result }; -} - -export function math_asinh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_asinh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_asinh', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - const result = Math.asinh(num); - return { type: 'number', value: result }; -} - -export function math_atan(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atan', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atan', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - const result = Math.atan(num); - return { type: 'number', value: result }; -} - -export function math_atan2(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atan', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atan', Number(2), args, false)); - } - - const y = args[0]; - const x = args[1]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } else if (y.type !== 'number' && y.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); - } - - let yNum: number, xNum: number; - if (y.type === 'number') { - yNum = y.value; - } else { - yNum = Number(y.value); - } - - if (x.type === 'number') { - xNum = x.value; - } else { - xNum = Number(x.value); - } - - const result = Math.atan2(yNum, xNum); - return { type: 'number', value: result }; -} - -export function math_atanh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_atanh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_atanh', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + static toStr(val: Value): string { + return String(val.value); } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + + static error(args: Value[], source: string, command: ControlItem, context: Context): Value { + const output = "Error: " + args.map(arg => BuiltInFunctions.toStr(arg)).join(' ') + '\n'; + throw new Error(output); } - - if (num <= -1 || num >= 1) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_atanh")); + + @Validate(2, 2, 'isinstance', false) + static isinstance(args: Value[], source: string, command: ControlItem, context: Context): Value { + + const obj = args[0]; + const classinfo = args[1]; + + let expectedType: string; + if (classinfo.type === 'string') { + switch (classinfo.value) { + case 'int': + expectedType = 'bigint'; + if (obj.type === 'bool') { + handleRuntimeError(context, new SublanguageError(source, command as es.Node, context, + 'isinstance', + '1', + 'Python §1 does not treat bool as a subtype of int')); + } + break; + case 'float': + expectedType = 'number'; + break; + case 'string': + expectedType = 'string'; + break; + case 'bool': + expectedType = 'bool'; + break; + case 'complex': + expectedType = 'complex'; + break; + case 'NoneType': + expectedType = 'NoneType'; + break; + default: + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "isinstance")); + return; + } + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "string")); + return; + } + + const result = obj.type === expectedType; + + return { type: 'bool', value: result }; } - - const result = Math.atanh(num); - return { type: 'number', value: result }; -} - -export function math_cos(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cos', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cos', Number(1), args, false)); + + @Validate(1, 1, 'math_acos', false) + static math_acos(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < -1 || num > 1) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acos")); + } + + const result = Math.acos(num); + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + @Validate(1, 1, 'math_acosh', false) + static math_acosh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < 1) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_acosh")); + } + + const result = Math.acosh(num); + return { type: 'number', value: result }; } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + @Validate(1, 1, 'math_asin', false) + static math_asin(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < -1 || num > 1) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_asin")); + } + + const result = Math.asin(num); + return { type: 'number', value: result }; } - const result = Math.cos(num); - return { type: 'number', value: result }; -} - -export function math_cosh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cosh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cosh', Number(1), args, false)); + @Validate(1, 1, 'math_asinh', false) + static math_asinh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.asinh(num); + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + @Validate(1, 1, 'math_atan', false) + static math_atan(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.atan(num); + return { type: 'number', value: result }; } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + @Validate(2, 2, 'math_atan2', false) + static math_atan2(args: Value[], source: string, command: ControlItem, context: Context): Value { + const y = args[0]; + const x = args[1]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } else if (y.type !== 'number' && y.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + } + + let yNum: number, xNum: number; + if (y.type === 'number') { + yNum = y.value; + } else { + yNum = Number(y.value); + } + + if (x.type === 'number') { + xNum = x.value; + } else { + xNum = Number(x.value); + } + + const result = Math.atan2(yNum, xNum); + return { type: 'number', value: result }; } - const result = Math.cosh(num); - return { type: 'number', value: result }; -} - -export function math_degrees(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_degrees', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_degrees', Number(1), args, false)); + @Validate(1, 1, 'math_atanh', false) + static math_atanh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num <= -1 || num >= 1) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_atanh")); + } + + const result = Math.atanh(num); + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + @Validate(1, 1, 'math_cos', false) + static math_cos(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.cos(num); + return { type: 'number', value: result }; } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + @Validate(1, 1, 'math_cosh', false) + static math_cosh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.cosh(num); + return { type: 'number', value: result }; } - const result = num * 180 / Math.PI; - return { type: 'number', value: result }; -} - -export function math_erf(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_erf', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_erf', Number(1), args, false)); + @Validate(1, 1, 'math_degrees', false) + static math_degrees(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = num * 180 / Math.PI; + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + @Validate(1, 1, 'math_erf', false) + static math_erf(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const erfnum = erf(num); + + return { type: 'number', value: erfnum }; } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + @Validate(1, 1, 'math_erfc', false) + static math_erfc(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + const erfc = 1 - BuiltInFunctions.math_erf(args[0], source, command, context).value; + + return { type: 'number', value: erfc }; } - const erfnum = erf(num); + @Validate(2, 2, 'char_at', false) + static char_at(args: Value[], source: string, command: ControlItem, context: Context): Value { + const s = args[0]; + const i = args[1]; + + if (s.type !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, s.type, "string")); + } + if (i.type !== 'number' && i.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, i.type, "float' or 'int")); + } + + const index = i.value; - return { type: 'number', value: erfnum }; -} - -export function math_erfc(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_erfc', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_erfc', Number(1), args, false)); + return { type: 'string', value: (s.value)[index]}; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } + @Validate(2, 2, 'math_comb', false) + static math_comb(args: Value[], source: string, command: ControlItem, context: Context): Value { + const n = args[0]; + const k = args[1]; + + if (n.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); + } else if (k.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, k.type, "int")); + } + + const nVal = BigInt(n.value); + const kVal = BigInt(k.value); + + if (nVal < 0 || kVal < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_comb")); + } - const erfc = 1 - math_erf(args[0], source, command, context).value; + if (kVal > nVal) { + return { type: 'bigint', value: BigInt(0) }; + } + + let result: bigint = BigInt(1); + let kk = kVal > nVal - kVal ? nVal - kVal : kVal; + + for (let i: bigint = BigInt(0); i < kk; i++) { + result = result * (nVal - i) / (i + BigInt(1)); + } - return { type: 'number', value: erfc }; -} - -export function char_at(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'char_at', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'char_at', Number(2), args, false)); - } - - const s = args[0]; - const i = args[1]; - - if (s.type !== 'string') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, s.type, "string")); - } - if (i.type !== 'number' && i.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, i.type, "float' or 'int")); - } - - const index = i.value; - - return { type: 'string', value: (s.value)[index]}; -} - -export function math_comb(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_comb', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_comb', Number(2), args, false)); - } - - const n = args[0]; - const k = args[1]; - - if (n.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); - } else if (k.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, k.type, "int")); - } - - const nVal = BigInt(n.value); - const kVal = BigInt(k.value); - - if (nVal < 0 || kVal < 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_comb")); - } - - if (kVal > nVal) { - return { type: 'bigint', value: BigInt(0) }; - } - - let result: bigint = BigInt(1); - let kk = kVal > nVal - kVal ? nVal - kVal : kVal; - - for (let i: bigint = BigInt(0); i < kk; i++) { - result = result * (nVal - i) / (i + BigInt(1)); - } - - return { type: 'bigint', value: result }; -} - -export function math_factorial(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_factorial', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_factorial', Number(1), args, false)); - } - - const n = args[0]; - - if (n.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); - } - - const nVal = BigInt(n.value); - - if (nVal < 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_factorial")); - } - - // 0! = 1 - if (nVal === BigInt(0)) { - return { type: 'bigint', value: BigInt(1) }; - } - - let result: bigint = BigInt(1); - for (let i: bigint = BigInt(1); i <= nVal; i++) { - result *= i; - } - - return { type: 'bigint', value: result }; -} - -export function math_gcd(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length === 0) { - return { type: 'bigint', value: BigInt(0) }; + return { type: 'bigint', value: result }; } - - const values = args.map((v, idx) => { - if (v.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, v.type, "int")); + + @Validate(1, 1, 'math_factorial', false) + static math_factorial(args: Value[], source: string, command: ControlItem, context: Context): Value { + const n = args[0]; + + if (n.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, n.type, "int")); } - return BigInt(v.value); - }); - - const allZero = values.every(val => val === BigInt(0)); - if (allZero) { - return { type: 'bigint', value: BigInt(0) }; - } - - let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0]; - for (let i = 1; i < values.length; i++) { - currentGcd = gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]); - if (currentGcd === BigInt(1)) { - break; + + const nVal = BigInt(n.value); + + if (nVal < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_factorial")); } - } - - return { type: 'bigint', value: currentGcd }; -} - -function gcdOfTwo(a: bigint, b: bigint): bigint { - let x: bigint = a; - let y: bigint = b; - while (y !== BigInt(0)) { - const temp = x % y; - x = y; - y = temp; - } - return x < 0 ? -x : x; -} - -export function math_isqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isqrt', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isqrt', Number(1), args, false)); - } - - const nValObj = args[0]; - if (nValObj.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); - } - - const n: bigint = nValObj.value; - - if (n < 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_isqrt")); - } - - if (n < 2) { - return { type: 'bigint', value: n }; - } - - let low: bigint = BigInt(1); - let high: bigint = n; - - while (low < high) { - const mid = (low + high + BigInt(1)) >> BigInt(1); - const sq = mid * mid; - if (sq <= n) { - low = mid; - } else { - high = mid - BigInt(1); + + // 0! = 1 + if (nVal === BigInt(0)) { + return { type: 'bigint', value: BigInt(1) }; } - } - - return { type: 'bigint', value: low }; -} - -export function math_lcm(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length === 0) { - return { type: 'bigint', value: BigInt(1) }; - } - - const values = args.map((val, idx) => { - if (val.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "int")); + + let result: bigint = BigInt(1); + for (let i: bigint = BigInt(1); i <= nVal; i++) { + result *= i; } - return BigInt(val.value); - }); - - if (values.some(v => v === BigInt(0))) { - return { type: 'bigint', value: BigInt(0) }; + + return { type: 'bigint', value: result }; } - - let currentLcm: bigint = absBigInt(values[0]); - for (let i = 1; i < values.length; i++) { - currentLcm = lcmOfTwo(currentLcm, absBigInt(values[i])); - if (currentLcm === BigInt(0)) { - break; + + static math_gcd(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: BigInt(0) }; } - } - - return { type: 'bigint', value: currentLcm }; -} - -function lcmOfTwo(a: bigint, b: bigint): bigint { - const gcdVal: bigint = gcdOfTwo(a, b); - return BigInt((a / gcdVal) * b); -} - -function absBigInt(x: bigint): bigint { - return x < 0 ? -x : x; -} - -export function math_perm(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_perm', Number(1), args, true)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_perm', Number(2), args, true)); - } - - const nValObj = args[0]; - if (nValObj.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); - } - const n = BigInt(nValObj.value); - - let k = n; - if (args.length === 2) { - const kValObj = args[1]; - if (kValObj.type === 'null' || kValObj.type === 'undefined') { - k = n; - } else if (kValObj.type === 'bigint') { - k = BigInt(kValObj.value); - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, kValObj.type, "int' or 'None")); + + const values = args.map((v, idx) => { + if (v.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, v.type, "int")); + } + return BigInt(v.value); + }); + + const allZero = values.every(val => val === BigInt(0)); + if (allZero) { + return { type: 'bigint', value: BigInt(0) }; } - } - - if (n < 0 || k < 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_perm")); - } - - if (k > n) { - return { type: 'bigint', value: BigInt(0) }; - } - - let result: bigint = BigInt(1); - for (let i: bigint = BigInt(0); i < k; i++) { - result *= (n - i); - } - - return { type: 'bigint', value: result }; -} - -export function math_ceil(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_ceil', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_ceil', Number(1), args, false)); - } - - const x = args[0]; - - if (x.type === 'bigint') { - return x; - } - - if (x.type === 'number') { - const numVal = x.value as number; - if (typeof numVal !== 'number') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0]; + for (let i = 1; i < values.length; i++) { + currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]); + if (currentGcd === BigInt(1)) { + break; + } } - const ceiled: bigint = BigInt(Math.ceil(numVal)); - return { type: 'bigint', value: ceiled }; + + return { type: 'bigint', value: currentGcd }; } - - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); -} - -export function math_fabs(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fabs', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fabs', Number(1), args, false)); - } - - const x = args[0]; - - if (x.type === 'bigint') { - const bigVal: bigint = BigInt(x.value); - const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal); - return { type: 'number', value: absVal }; - } - - if (x.type === 'number') { - const numVal: number = x.value as number; - if (typeof numVal !== 'number') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + static gcdOfTwo(a: bigint, b: bigint): bigint { + let x: bigint = a; + let y: bigint = b; + while (y !== BigInt(0)) { + const temp = x % y; + x = y; + y = temp; } - const absVal: number = Math.abs(numVal); - return { type: 'number', value: absVal }; + return x < 0 ? -x : x; } - - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); -} - -export function math_floor(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_floor', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_floor', Number(1), args, false)); - } - - const x = args[0]; - - if (x.type === 'bigint') { - return x; + + @Validate(1, 1, 'math_isqrt', false) + static math_isqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + const nValObj = args[0]; + if (nValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); + } + + const n: bigint = nValObj.value; + + if (n < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_isqrt")); + } + + if (n < 2) { + return { type: 'bigint', value: n }; + } + + let low: bigint = BigInt(1); + let high: bigint = n; + + while (low < high) { + const mid = (low + high + BigInt(1)) >> BigInt(1); + const sq = mid * mid; + if (sq <= n) { + low = mid; + } else { + high = mid - BigInt(1); + } + } + + return { type: 'bigint', value: low }; } - - if (x.type === 'number') { - const numVal: number = x.value as number; - if (typeof numVal !== 'number') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + static math_lcm(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'bigint', value: BigInt(1) }; + } + + const values = args.map((val, idx) => { + if (val.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "int")); + } + return BigInt(val.value); + }); + + if (values.some(v => v === BigInt(0))) { + return { type: 'bigint', value: BigInt(0) }; } - const floored: bigint = BigInt(Math.floor(numVal)); - return { type: 'bigint', value: floored }; + + let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]); + for (let i = 1; i < values.length; i++) { + currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i])); + if (currentLcm === BigInt(0)) { + break; + } + } + + return { type: 'bigint', value: currentLcm }; } - - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); -} - -// Computes the product of a and b along with the rounding error using Dekker's algorithm. -function twoProd(a: number, b: number): { prod: number; err: number } { - const prod = a * b; - const c = 134217729; // 2^27 + 1 - const a_hi = (a * c) - ((a * c) - a); - const a_lo = a - a_hi; - const b_hi = (b * c) - ((b * c) - b); - const b_lo = b - b_hi; - const err = a_lo * b_lo - (((prod - a_hi * b_hi) - a_lo * b_hi) - a_hi * b_lo); - return { prod, err }; -} - -// Computes the sum of a and b along with the rounding error using Fast TwoSum. -function twoSum(a: number, b: number): { sum: number; err: number } { - const sum = a + b; - const v = sum - a; - const err = (a - (sum - v)) + (b - v); - return { sum, err }; -} - -// Performs a fused multiply-add operation: computes (x * y) + z with a single rounding. -function fusedMultiplyAdd(x: number, y: number, z: number): number { - const { prod, err: prodErr } = twoProd(x, y); - const { sum, err: sumErr } = twoSum(prod, z); - const result = sum + (prodErr + sumErr); - return result; -} - -function toNumber(val: Value, source: string, command: ControlItem, context: Context): number { - if (val.type === 'bigint') { - return Number(val.value); - } else if (val.type === 'number') { - return val.value as number; - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "float' or 'int")); - return 0; + + static lcmOfTwo(a: bigint, b: bigint): bigint { + const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b); + return BigInt((a / gcdVal) * b); } -} - -export function math_fma(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 3) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fma', Number(3), args, false)); - } else if (args.length > 3) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fma', Number(3), args, false)); + + static absBigInt(x: bigint): bigint { + return x < 0 ? -x : x; } - const xVal = toNumber(args[0], source, command, context); - const yVal = toNumber(args[1], source, command, context); - const zVal = toNumber(args[2], source, command, context); - - // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan) - // and fma(inf, 0, nan) should return NaN. - if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) { - return { type: 'number', value: NaN }; + @Validate(1, 2, 'math_perm', true) + static math_perm(args: Value[], source: string, command: ControlItem, context: Context): Value { + const nValObj = args[0]; + if (nValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, nValObj.type, "int")); + } + const n = BigInt(nValObj.value); + + let k = n; + if (args.length === 2) { + const kValObj = args[1]; + if (kValObj.type === 'null' || kValObj.type === 'undefined') { + k = n; + } else if (kValObj.type === 'bigint') { + k = BigInt(kValObj.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, kValObj.type, "int' or 'None")); + } + } + + if (n < 0 || k < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_perm")); + } + + if (k > n) { + return { type: 'bigint', value: BigInt(0) }; + } + + let result: bigint = BigInt(1); + for (let i: bigint = BigInt(0); i < k; i++) { + result *= (n - i); + } + + return { type: 'bigint', value: result }; } - if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) { - return { type: 'number', value: NaN }; + + @Validate(1, 1, 'math_ceil', false) + static math_ceil(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const ceiled: bigint = BigInt(Math.ceil(numVal)); + return { type: 'bigint', value: ceiled }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); } - if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) { - return { type: 'number', value: NaN }; + + @Validate(1, 1, 'math_fabs', false) + static math_fabs(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + + if (x.type === 'bigint') { + const bigVal: bigint = BigInt(x.value); + const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal); + return { type: 'number', value: absVal }; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const absVal: number = Math.abs(numVal); + return { type: 'number', value: absVal }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); } - const result = fusedMultiplyAdd(xVal, yVal, zVal); - return { type: 'number', value: result }; -} - -export function math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_fmod', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_fmod', Number(2), args, false)); + @Validate(1, 1, 'math_floor', false) + static math_floor(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + const floored: bigint = BigInt(Math.floor(numVal)); + return { type: 'bigint', value: floored }; + } + + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); } - - // Convert inputs to numbers - const xVal = toNumber(args[0], source, command, context); - const yVal = toNumber(args[1], source, command, context); - - // Divisor cannot be zero - if (yVal === 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_fmod")); + + // Computes the product of a and b along with the rounding error using Dekker's algorithm. + static twoProd(a: number, b: number): { prod: number; err: number } { + const prod = a * b; + const c = 134217729; // 2^27 + 1 + const a_hi = (a * c) - ((a * c) - a); + const a_lo = a - a_hi; + const b_hi = (b * c) - ((b * c) - b); + const b_lo = b - b_hi; + const err = a_lo * b_lo - (((prod - a_hi * b_hi) - a_lo * b_hi) - a_hi * b_lo); + return { prod, err }; + } + + // Computes the sum of a and b along with the rounding error using Fast TwoSum. + static twoSum(a: number, b: number): { sum: number; err: number } { + const sum = a + b; + const v = sum - a; + const err = (a - (sum - v)) + (b - v); + return { sum, err }; } - - // JavaScript's % operator behaves similarly to C's fmod - // in that the sign of the result is the same as the sign of x. - // For corner cases (NaN, Infinity), JavaScript remainder - // yields results consistent with typical C library fmod behavior. - const remainder = xVal % yVal; - - return { type: 'number', value: remainder }; -} - -function roundToEven(num: number): number { - const floorVal = Math.floor(num); - const ceilVal = Math.ceil(num); - const diffFloor = num - floorVal; - const diffCeil = ceilVal - num; - if (diffFloor < diffCeil) { - return floorVal; - } else if (diffCeil < diffFloor) { - return ceilVal; - } else { - return (floorVal % 2 === 0) ? floorVal : ceilVal; + + // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding. + static fusedMultiplyAdd(x: number, y: number, z: number): number { + const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y); + const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z); + const result = sum + (prodErr + sumErr); + return result; } -} - -export function math_remainder(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_remainder', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_remainder', Number(2), args, false)); + + static toNumber(val: Value, source: string, command: ControlItem, context: Context): number { + if (val.type === 'bigint') { + return Number(val.value); + } else if (val.type === 'number') { + return val.value as number; + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, val.type, "float' or 'int")); + return 0; + } } - const x = args[0]; - const y = args[1]; - - let xValue: number; - if (x.type === 'bigint') { - xValue = Number(x.value); - } else if (x.type === 'number') { - xValue = x.value as number; - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - return; + @Validate(3, 3, 'math_fma', false) + static math_fma(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xVal = BuiltInFunctions.toNumber(args[0], source, command, context); + const yVal = BuiltInFunctions.toNumber(args[1], source, command, context); + const zVal = BuiltInFunctions.toNumber(args[2], source, command, context); + + // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan) + // and fma(inf, 0, nan) should return NaN. + if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) { + return { type: 'number', value: NaN }; + } + + const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal); + return { type: 'number', value: result }; } - - let yValue: number; - if (y.type === 'bigint') { - yValue = Number(y.value); - } else if (y.type === 'number') { - yValue = y.value as number; - } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); - return; + + @Validate(2, 2, 'math_fmod', false) + static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value { + // Convert inputs to numbers + const xVal = BuiltInFunctions.toNumber(args[0], source, command, context); + const yVal = BuiltInFunctions.toNumber(args[1], source, command, context); + + // Divisor cannot be zero + if (yVal === 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_fmod")); + } + + // JavaScript's % operator behaves similarly to C's fmod + // in that the sign of the result is the same as the sign of x. + // For corner cases (NaN, Infinity), JavaScript remainder + // yields results consistent with typical C library fmod behavior. + const remainder = xVal % yVal; + + return { type: 'number', value: remainder }; } - - if (yValue === 0) { - - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_remainder")); + + static roundToEven(num: number): number { + const floorVal = Math.floor(num); + const ceilVal = Math.ceil(num); + const diffFloor = num - floorVal; + const diffCeil = ceilVal - num; + if (diffFloor < diffCeil) { + return floorVal; + } else if (diffCeil < diffFloor) { + return ceilVal; + } else { + return (floorVal % 2 === 0) ? floorVal : ceilVal; + } } - - const quotient = xValue / yValue; - const n = roundToEven(quotient); - const remainder = xValue - n * yValue; - - return { type: 'number', value: remainder }; -} - -export function math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_trunc', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_trunc', Number(1), args, false)); - } - - const x = args[0]; - - if (x.type === 'bigint') { - return x; - } - - if (x.type === 'number') { - const numVal: number = x.value as number; - if (typeof numVal !== 'number') { + + @Validate(2, 2, 'math_remainder', false) + static math_remainder(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + const y = args[1]; + + let xValue: number; + if (x.type === 'bigint') { + xValue = Number(x.value); + } else if (x.type === 'number') { + xValue = x.value as number; + } else { handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + return; } - let truncated: number; - if (numVal === 0) { - truncated = 0; - } else if (numVal < 0) { - truncated = Math.ceil(numVal); + + let yValue: number; + if (y.type === 'bigint') { + yValue = Number(y.value); + } else if (y.type === 'number') { + yValue = y.value as number; } else { - truncated = Math.floor(numVal); + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + return; } - return { type: 'bigint', value: BigInt(truncated) }; + + if (yValue === 0) { + + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_remainder")); + } + + const quotient = xValue / yValue; + const n = BuiltInFunctions.roundToEven(quotient); + const remainder = xValue - n * yValue; + + return { type: 'number', value: remainder }; } - - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); -} - -export function math_copysign(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_copysign', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_copysign', Number(2), args, false)); - } - - const [x, y] = args; - - if (x.type !== 'number' && x.type !== 'bigint') { + + @Validate(1, 1, 'math_trunc', false) + static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + + if (x.type === 'bigint') { + return x; + } + + if (x.type === 'number') { + const numVal: number = x.value as number; + if (typeof numVal !== 'number') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + let truncated: number; + if (numVal === 0) { + truncated = 0; + } else if (numVal < 0) { + truncated = Math.ceil(numVal); + } else { + truncated = Math.floor(numVal); + } + return { type: 'bigint', value: BigInt(truncated) }; + } + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } else if (y.type !== 'number' && y.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); } - const xVal = Number(x.value) as number; - const yVal = Number(y.value) as number; - - const absVal = Math.abs(xVal); - const isNegative = yVal < 0 || (Object.is(yVal, -0)); - const result = isNegative ? -absVal : absVal; + @Validate(2, 2, 'math_copysign', false) + static math_copysign(args: Value[], source: string, command: ControlItem, context: Context): Value { + const [x, y] = args; - return { type: 'number', value: Number(result) }; -} - -export function math_isfinite(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isfinite', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isfinite', Number(1), args, false)); - } - - const xValObj = args[0]; - if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); - } - - const x = Number(xValObj.value) as number; - const result: boolean = Number.isFinite(x); - - return { type: 'bool', value: result }; -} - -export function math_isinf(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isinf', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isinf', Number(1), args, false)); - } - - const xValObj = args[0]; - if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } else if (y.type !== 'number' && y.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, y.type, "float' or 'int")); + } + + const xVal = Number(x.value) as number; + const yVal = Number(y.value) as number; + + const absVal = Math.abs(xVal); + const isNegative = yVal < 0 || (Object.is(yVal, -0)); + const result = isNegative ? -absVal : absVal; + + return { type: 'number', value: Number(result) }; } - - const x = Number(xValObj.value) as number; - const result: boolean = (x === Infinity || x === -Infinity); - - return { type: 'bool', value: result }; -} - -export function math_isnan(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_isnan', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_isnan', Number(1), args, false)); + + @Validate(1, 1, 'math_isfinite', false) + static math_isfinite(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = Number.isFinite(x); + + return { type: 'bool', value: result }; } - - const xValObj = args[0]; - if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + + @Validate(1, 1, 'math_isinf', false) + static math_isinf(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = (x === Infinity || x === -Infinity); + + return { type: 'bool', value: result }; } - - const x = Number(xValObj.value) as number; - const result: boolean = Number.isNaN(x); - - return { type: 'bool', value: result }; -} - -export function math_ldexp(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_ldexp', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_ldexp', Number(2), args, false)); + + @Validate(1, 1, 'math_isnan', false) + static math_isnan(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xValObj = args[0]; + if (xValObj.type !== 'number' && xValObj.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xValObj.type, "float' or 'int")); + } + + const x = Number(xValObj.value) as number; + const result: boolean = Number.isNaN(x); + + return { type: 'bool', value: result }; } - - const xVal = toNumber(args[0], source, command, context); - - if (args[1].type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "int")); + + @Validate(2, 2, 'math_ldexp', false) + static math_ldexp(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xVal = BuiltInFunctions.toNumber(args[0], source, command, context); + + if (args[1].type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[1].type, "int")); + } + const expVal = args[1].value; + + // Perform x * 2^expVal + // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively. + // That behavior parallels typical C library rules for ldexp. + const result = xVal * Math.pow(2, Number(expVal)); + + return { type: 'number', value: result }; + } + + static math_nextafter(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.) + throw new Error("math_nextafter not implemented"); } - const expVal = args[1].value; - - // Perform x * 2^expVal - // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively. - // That behavior parallels typical C library rules for ldexp. - const result = xVal * Math.pow(2, Number(expVal)); - - return { type: 'number', value: result }; -} - -export function math_nextafter(args: Value[], source: string, command: ControlItem, context: Context): Value { - // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.) - throw new Error("math_nextafter not implemented"); -} - -export function math_ulp(args: Value[], source: string, command: ControlItem, context: Context): Value { - // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number. - throw new Error("math_ulp not implemented"); -} - -export function math_cbrt(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_cbrt', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_cbrt', Number(1), args, false)); + + static math_ulp(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number. + throw new Error("math_ulp not implemented"); } - - const xVal = args[0]; - let x: number; - - if (xVal.type !== 'number') { - if (xVal.type === 'bigint') { - x = Number(xVal.value); + + @Validate(1, 1, 'math_cbrt', false) + static math_cbrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); - return; + x = xVal.value as number; } - } else { - x = xVal.value as number; + + const result = Math.cbrt(x); + + return { type: 'number', value: result }; } - - const result = Math.cbrt(x); - - return { type: 'number', value: result }; -} - -export function math_exp(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_exp', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_exp', Number(1), args, false)); - } - - const xVal = args[0]; - let x: number; - - if (xVal.type !== 'number') { - if (xVal.type === 'bigint') { - x = Number(xVal.value); + + @Validate(1, 1, 'math_exp', false) + static math_exp(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); - return; + x = xVal.value as number; } - } else { - x = xVal.value as number; + + const result = Math.exp(x); + return { type: 'number', value: result }; } - - const result = Math.exp(x); - return { type: 'number', value: result }; -} - -export function math_exp2(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_exp2', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_exp2', Number(1), args, false)); - } - - const xVal = args[0]; - let x: number; - - if (xVal.type !== 'number') { - if (xVal.type === 'bigint') { - x = Number(xVal.value); + + @Validate(1, 1, 'math_exps', false) + static math_exp2(args: Value[], source: string, command: ControlItem, context: Context): Value { + const xVal = args[0]; + let x: number; + + if (xVal.type !== 'number') { + if (xVal.type === 'bigint') { + x = Number(xVal.value); + } else { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); + return; + } } else { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, xVal.type, "float' or 'int")); - return; + x = xVal.value as number; } - } else { - x = xVal.value as number; + + const result = Math.pow(2, x); + return { type: 'number', value: result }; } - const result = Math.pow(2, x); - return { type: 'number', value: result }; -} - -export function math_expm1(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_expm1', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_expm1', Number(1), args, false)); + @Validate(1, 1, 'math_expm1', false) + static math_expm1(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.expm1(num); + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } + @Validate(1, 1, 'math_gamma', false) + static math_gamma(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } + const z = BuiltInFunctions.toNumber(x, source, command, context); + const result = gamma(z); - const result = Math.expm1(num); - return { type: 'number', value: result }; -} - -export function math_gamma(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_gamma', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_gamma', Number(1), args, false)); + return { type: 'number', value: result }; } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - const z = toNumber(x, source, command, context); - const result = gamma(z); - - return { type: 'number', value: result }; -} - -export function math_lgamma(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_lgamma', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_lgamma', Number(1), args, false)); - } + @Validate(1, 1, 'math_lgamma', false) + static math_lgamma(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - const z = toNumber(x, source, command, context); - const result = lgamma(z); - - return { type: 'number', value: result }; -} - -export function math_log(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_gamma', Number(1), args, true)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_gamma', Number(2), args, true)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } + const z = BuiltInFunctions.toNumber(x, source, command, context); + const result = lgamma(z); - if (num <= 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); - } - - if (args.length === 1) { - return { type: 'number', value: Math.log(num) }; - } - - const baseArg = args[1]; - if (baseArg.type !== 'number' && baseArg.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, baseArg.type, "float' or 'int")); - } - let baseNum: number; - if (baseArg.type === 'number') { - baseNum = baseArg.value; - } else { - baseNum = Number(baseArg.value); - } - if (baseNum <= 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); - } - - const result = Math.log(num) / Math.log(baseNum); - return { type: 'number', value: result }; -} - -export function math_log10(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log10', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log10', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); - } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - if (num <= 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log10")); - } - - const result = Math.log10(num); - return { type: 'number', value: result }; -} - -export function math_log1p(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log1p', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log1p', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); - } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - if (1 + num <= 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log1p")); + return { type: 'number', value: result }; } - - const result = Math.log1p(num); - return { type: 'number', value: result }; -} - -export function math_log2(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_log2', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_log2', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); - } - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - if (num <= 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log2")); - } - - const result = Math.log2(num); - return { type: 'number', value: result }; -} - -export function math_pow(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_pow', Number(2), args, false)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_pow', Number(2), args, false)); - } - - const base = args[0]; - const exp = args[1]; - - if (base.type !== 'number' && base.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, base.type, "float' or 'int")); - } else if (exp.type !== 'number' && exp.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, exp.type, "float' or 'int")); - } - - let baseNum: number; - if (base.type === 'number') { - baseNum = base.value; - } else { - baseNum = Number(base.value); - } - - let expNum: number; - if (exp.type === 'number') { - expNum = exp.value; - } else { - expNum = Number(exp.value); - } - - const result = Math.pow(baseNum, expNum); - return { type: 'number', value: result }; -} - -export function math_radians(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_radians', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_radians', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let deg: number; - if (x.type === 'number') { - deg = x.value; - } else { - deg = Number(x.value); - } - - const radians = deg * Math.PI / 180; - return { type: 'number', value: radians }; -} - -export function math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sin', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sin', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); - } - - const result = Math.sin(num); - return { type: 'number', value: result }; -} - -export function math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sinh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sinh', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); - } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + + @Validate(1, 2, 'math_log', true) + static math_log(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num <= 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); + } + + if (args.length === 1) { + return { type: 'number', value: Math.log(num) }; + } + + const baseArg = args[1]; + if (baseArg.type !== 'number' && baseArg.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, baseArg.type, "float' or 'int")); + } + let baseNum: number; + if (baseArg.type === 'number') { + baseNum = baseArg.value; + } else { + baseNum = Number(baseArg.value); + } + if (baseNum <= 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log")); + } + + const result = Math.log(num) / Math.log(baseNum); + return { type: 'number', value: result }; } - - const result = Math.sinh(num); - return { type: 'number', value: result }; -} - -export function math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_tan', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_tan', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + @Validate(1, 1, 'math_log10', false) + static math_log10(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (num <= 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log10")); + } + + const result = Math.log10(num); + return { type: 'number', value: result }; + } + + @Validate(1, 1, 'math_log1p', false) + static math_log1p(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (1 + num <= 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log1p")); + } + + const result = Math.log1p(num); + return { type: 'number', value: result }; + } + + @Validate(1, 1, 'math_log2', false) + static math_log2(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[0].type, "float' or 'int")); + } + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + if (num <= 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_log2")); + } + + const result = Math.log2(num); + return { type: 'number', value: result }; } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + + @Validate(2, 2, 'math_pow', false) + static math_pow(args: Value[], source: string, command: ControlItem, context: Context): Value { + const base = args[0]; + const exp = args[1]; + + if (base.type !== 'number' && base.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, base.type, "float' or 'int")); + } else if (exp.type !== 'number' && exp.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, exp.type, "float' or 'int")); + } + + let baseNum: number; + if (base.type === 'number') { + baseNum = base.value; + } else { + baseNum = Number(base.value); + } + + let expNum: number; + if (exp.type === 'number') { + expNum = exp.value; + } else { + expNum = Number(exp.value); + } + + const result = Math.pow(baseNum, expNum); + return { type: 'number', value: result }; } - - const result = Math.tan(num); - return { type: 'number', value: result }; -} - -export function math_tanh(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_tanh', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_tanh', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + @Validate(1, 1, 'math_radians', false) + static math_radians(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let deg: number; + if (x.type === 'number') { + deg = x.value; + } else { + deg = Number(x.value); + } + + const radians = deg * Math.PI / 180; + return { type: 'number', value: radians }; } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + + @Validate(1, 1, 'math_sin', false) + static math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.sin(num); + return { type: 'number', value: result }; } - - const result = Math.tanh(num); - return { type: 'number', value: result }; -} - -export function math_sqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'math_sqrt', Number(1), args, false)); - } else if (args.length > 1) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'math_sqrt', Number(1), args, false)); - } - - const x = args[0]; - if (x.type !== 'number' && x.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + + @Validate(1, 1, 'math_sinh', false) + static math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.sinh(num); + return { type: 'number', value: result }; } - - let num: number; - if (x.type === 'number') { - num = x.value; - } else { - num = Number(x.value); + + @Validate(1, 1, 'math_tan', false) + static math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.tan(num); + return { type: 'number', value: result }; } - - if (num < 0) { - handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_sqrt")); + + @Validate(1, 1, 'math_tanh', false) + static math_tanh(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + const result = Math.tanh(num); + return { type: 'number', value: result }; } - - const result = Math.sqrt(num); - return { type: 'number', value: result }; -} - -export function max(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'max', Number(2), args, true)); + + @Validate(1, 1, 'math_sqrt', false) + static math_sqrt(args: Value[], source: string, command: ControlItem, context: Context): Value { + const x = args[0]; + if (x.type !== 'number' && x.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, x.type, "float' or 'int")); + } + + let num: number; + if (x.type === 'number') { + num = x.value; + } else { + num = Number(x.value); + } + + if (num < 0) { + handleRuntimeError(context, new ValueError(source, command as es.Node, context, "math_sqrt")); + } + + const result = Math.sqrt(num); + return { type: 'number', value: result }; } - - const numericTypes = ['bigint', 'number']; - const firstType = args[0].type; - let isNumeric = numericTypes.includes(firstType); - let isString = firstType === 'string'; - - for (let i = 1; i < args.length; i++) { - const t = args[i].type; - if (isNumeric && !numericTypes.includes(t)) { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); - } - if (isString && t !== 'string') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); - } - } - - let useFloat = false; - if (isNumeric) { - for (const arg of args) { - if (arg.type === 'number') { - useFloat = true; - break; + + @Validate(2, null, 'max', true) + static max(args: Value[], source: string, command: ControlItem, context: Context): Value { + const numericTypes = ['bigint', 'number']; + const firstType = args[0].type; + let isNumeric = numericTypes.includes(firstType); + let isString = firstType === 'string'; + + for (let i = 1; i < args.length; i++) { + const t = args[i].type; + if (isNumeric && !numericTypes.includes(t)) { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); + } + if (isString && t !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); } } - } - - let maxIndex = 0; - if (isNumeric) { - if (useFloat) { - let maxVal: number = Number(args[0].value); - for (let i = 1; i < args.length; i++) { - const curr: number = Number(args[i].value); - if (curr > maxVal) { - maxVal = curr; - maxIndex = i; + + let useFloat = false; + if (isNumeric) { + for (const arg of args) { + if (arg.type === 'number') { + useFloat = true; + break; } } - } else { - let maxVal: bigint = args[0].value; + } + + let maxIndex = 0; + if (isNumeric) { + if (useFloat) { + let maxVal: number = Number(args[0].value); + for (let i = 1; i < args.length; i++) { + const curr: number = Number(args[i].value); + if (curr > maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + let maxVal: bigint = args[0].value; + for (let i = 1; i < args.length; i++) { + const curr: bigint = args[i].value; + if (curr > maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } + } else if (isString) { + let maxVal = args[0].value as string; for (let i = 1; i < args.length; i++) { - const curr: bigint = args[i].value; + const curr = args[i].value as string; if (curr > maxVal) { maxVal = curr; maxIndex = i; } } + } else { + // Won't happen + throw new Error(`max: unsupported type ${firstType}`); } - } else if (isString) { - let maxVal = args[0].value as string; - for (let i = 1; i < args.length; i++) { - const curr = args[i].value as string; - if (curr > maxVal) { - maxVal = curr; - maxIndex = i; - } - } - } else { - // Won't happen - throw new Error(`max: unsupported type ${firstType}`); + + return args[maxIndex]; } - - return args[maxIndex]; -} - -export function min(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 2) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'min', Number(2), args, true)); - } - - const numericTypes = ['bigint', 'number']; - const firstType = args[0].type; - let isNumeric = numericTypes.includes(firstType); - let isString = firstType === 'string'; - - for (let i = 1; i < args.length; i++) { - const t = args[i].type; - if (isNumeric && !numericTypes.includes(t)) { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); + + @Validate(2, null, 'min', true) + static min(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length < 2) { + handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'min', Number(2), args, true)); } - if (isString && t !== 'string') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); + + const numericTypes = ['bigint', 'number']; + const firstType = args[0].type; + let isNumeric = numericTypes.includes(firstType); + let isString = firstType === 'string'; + + for (let i = 1; i < args.length; i++) { + const t = args[i].type; + if (isNumeric && !numericTypes.includes(t)) { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "float' or 'int")); + } + if (isString && t !== 'string') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, args[i].type, "string")); + } } - } - - let useFloat = false; - if (isNumeric) { - for (const arg of args) { - if (arg.type === 'number') { - useFloat = true; - break; + + let useFloat = false; + if (isNumeric) { + for (const arg of args) { + if (arg.type === 'number') { + useFloat = true; + break; + } } } - } - - let maxIndex = 0; - if (isNumeric) { - if (useFloat) { - let maxVal: number = Number(args[0].value); - for (let i = 1; i < args.length; i++) { - const curr: number = Number(args[i].value); - if (curr < maxVal) { - maxVal = curr; - maxIndex = i; + + let maxIndex = 0; + if (isNumeric) { + if (useFloat) { + let maxVal: number = Number(args[0].value); + for (let i = 1; i < args.length; i++) { + const curr: number = Number(args[i].value); + if (curr < maxVal) { + maxVal = curr; + maxIndex = i; + } + } + } else { + let maxVal: bigint = args[0].value; + for (let i = 1; i < args.length; i++) { + const curr: bigint = args[i].value; + if (curr < maxVal) { + maxVal = curr; + maxIndex = i; + } } } - } else { - let maxVal: bigint = args[0].value; + } else if (isString) { + let maxVal = args[0].value as string; for (let i = 1; i < args.length; i++) { - const curr: bigint = args[i].value; + const curr = args[i].value as string; if (curr < maxVal) { maxVal = curr; maxIndex = i; } } + } else { + // Won't happen + throw new Error(`min: unsupported type ${firstType}`); } - } else if (isString) { - let maxVal = args[0].value as string; - for (let i = 1; i < args.length; i++) { - const curr = args[i].value as string; - if (curr < maxVal) { - maxVal = curr; - maxIndex = i; + + return args[maxIndex]; + } + + @Validate(null, 0, 'random_random', true) + static random_random(args: Value[], source: string, command: ControlItem, context: Context): Value { + const result = Math.random(); + return { type: 'number', value: result }; + } + + @Validate(1, 2, 'round', true) + static round(args: Value[], source: string, command: ControlItem, context: Context): Value { + const numArg = args[0]; + if (numArg.type !== 'number' && numArg.type !== 'bigint') { + handleRuntimeError(context, new TypeError(source, command as es.Node, context, numArg.type, "float' or 'int")); + } + + let ndigitsArg = { type: 'bigint', value: BigInt(0) }; + if (args.length === 2 && args[1].type !== 'NoneType') { + ndigitsArg = args[1]; + } + + if (numArg.type === 'number') { + let numberValue: number = numArg.value; + if (ndigitsArg.value > 0) { + const shifted = Number(numberValue.toFixed(Number(ndigitsArg.value))); + return { type: 'number', value: shifted }; + } else if (ndigitsArg.value === BigInt(0)) { + const shifted = Math.round(numArg.value); + return { type: 'bigint', value: BigInt(shifted) }; + } else { + const shifted = Math.round(numArg.value / (10 ** (-Number(ndigitsArg.value)))) * (10 ** (-Number(ndigitsArg.value))); + return { type: 'number', value: shifted }; + } + } else { + if (ndigitsArg.value >= 0) { + return numArg; + } else { + const shifted: bigint = numArg.value / (BigInt(10) ** (-ndigitsArg.value)) * (BigInt(10) ** (-ndigitsArg.value)); + return { type: 'bigint', value: shifted }; } } - } else { - // Won't happen - throw new Error(`min: unsupported type ${firstType}`); - } - - return args[maxIndex]; -} - -export function random_random(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length > 0) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'random_random', Number(0), args, false)); } - const result = Math.random(); - return { type: 'number', value: result }; -} - -export function round(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length < 1) { - handleRuntimeError(context, new MissingRequiredPositionalError(source, (command as es.Node), 'round', Number(1), args, true)); - } else if (args.length > 2) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'round', Number(2), args, true)); + + @Validate(null, 0, 'time_time', true) + static time_time(args: Value[], source: string, command: ControlItem, context: Context): Value { + const currentTime = Date.now(); + return { type: 'number', value: currentTime }; } - - const numArg = args[0]; - if (numArg.type !== 'number' && numArg.type !== 'bigint') { - handleRuntimeError(context, new TypeError(source, command as es.Node, context, numArg.type, "float' or 'int")); + + + + static input(args: Value[], source: string, command: ControlItem, context: Context): Value { + // TODO: : call conductor to receive user input } - let ndigitsArg = { type: 'bigint', value: BigInt(0) }; - if (args.length === 2 && args[1].type !== 'NoneType') { - ndigitsArg = args[1]; + static print(args: Value[], source: string, command: ControlItem, context: Context) { + const pieces = args.map(arg => toPythonString(arg)); + const output = pieces.join(' '); + addPrint(output); + //return { type: 'string', value: output }; } - if (numArg.type === 'number') { - let numberValue: number = numArg.value; - if (ndigitsArg.value > 0) { - const shifted = Number(numberValue.toFixed(Number(ndigitsArg.value))); - return { type: 'number', value: shifted }; - } else if (ndigitsArg.value === BigInt(0)) { - const shifted = Math.round(numArg.value); - return { type: 'bigint', value: BigInt(shifted) }; - } else { - const shifted = Math.round(numArg.value / (10 ** (-Number(ndigitsArg.value)))) * (10 ** (-Number(ndigitsArg.value))); - return { type: 'number', value: shifted }; - } - } else { - if (ndigitsArg.value >= 0) { - return numArg; - } else { - const shifted: bigint = numArg.value / (BigInt(10) ** (-ndigitsArg.value)) * (BigInt(10) ** (-ndigitsArg.value)); - return { type: 'bigint', value: shifted }; + static str(args: Value[], source: string, command: ControlItem, context: Context): Value { + if (args.length === 0) { + return { type: 'string', value: "" }; } + const obj = args[0]; + const result = toPythonString(obj); + return { type: 'string', value: result }; + } +} + +import py_s1_constants from './stdlib/py_s1_constants.json'; + +// NOTE: If we ever switch to another Python “chapter” (e.g. py_s2_constants), +// just change the variable below to switch to the set. +const constants = py_s1_constants; + +/* + Create a map to hold built-in constants. + Each constant is stored with a string key and its corresponding value object. +*/ +export const builtInConstants = new Map(); + +const constantMap = { + math_e: { type: 'number', value: Math.E }, + math_inf: { type: 'number', value: Infinity }, + math_nan: { type: 'number', value: NaN }, + math_pi: { type: 'number', value: Math.PI }, + math_tau: { type: 'number', value: 2 * Math.PI }, +} as const; + +for (const name of constants.constants as string[]) { + const valueObj = constantMap[name as keyof typeof constantMap]; + if (!valueObj) { + throw new Error(`Constant '${name}' is not implemented`); } + builtInConstants.set(name, valueObj); } -export function time_time(args: Value[], source: string, command: ControlItem, context: Context): Value { - if (args.length > 0) { - handleRuntimeError(context, new TooManyPositionalArgumentsError(source, (command as es.Node), 'time_time', Number(0), args, false)); +/* + Create a map to hold built-in functions. + The keys are strings (function names) and the values are functions that can take any arguments. +*/ +export const builtIns = new Map any>(); +for (const name of constants.builtInFuncs as string[]) { + let impl = (BuiltInFunctions as any)[name]; + if (typeof impl !== 'function') { + throw new Error(`BuiltInFunctions.${name} is not implemented`); } - const currentTime = Date.now(); - return { type: 'number', value: currentTime }; + builtIns.set(name, impl); } /** @@ -1798,7 +1576,7 @@ export function time_time(args: Value[], source: string, command: ControlItem, c * Special cases such as -0, Infinity, and NaN are also handled to ensure that * output matches Python’s display conventions. */ -function toPythonFloat(num: number): string { +export function toPythonFloat(num: number): string { if (Object.is(num, -0)) { return "-0.0"; } @@ -1862,15 +1640,3 @@ export function str(args: Value[], source: string, command: ControlItem, context const result = toPythonString(obj); return { type: 'string', value: result }; } - -export function input(args: Value[], source: string, command: ControlItem, context: Context): Value { - // TODO: : call conductor to receive user input -} - -export function print(args: Value[], source: string, command: ControlItem, context: Context) { - const pieces = args.map(arg => toPythonString(arg)); - const output = pieces.join(' '); - addPrint(output); - //return { type: 'string', value: output }; -} - \ No newline at end of file diff --git a/src/stdlib/py_s1_constants.json b/src/stdlib/py_s1_constants.json new file mode 100644 index 0000000..5bd9ac9 --- /dev/null +++ b/src/stdlib/py_s1_constants.json @@ -0,0 +1,74 @@ +{ + "builtInFuncs": [ + "_int", + "_int_from_string", + "abs", + "error", + "isinstance", + "math_acos", + "math_acosh", + "math_asin", + "math_asinh", + "math_atan", + "math_atan2", + "math_atanh", + "math_cos", + "math_cosh", + "math_degrees", + "math_erf", + "math_erfc", + "char_at", + "math_comb", + "math_factorial", + "math_gcd", + "math_isqrt", + "math_lcm", + "math_perm", + "math_ceil", + "math_fabs", + "math_floor", + "math_fma", + "math_fmod", + "math_remainder", + "math_trunc", + "math_copysign", + "math_isfinite", + "math_isinf", + "math_isnan", + "math_ldexp", + "math_nextafter", + "math_ulp", + "math_cbrt", + "math_exp", + "math_exp2", + "math_expm1", + "math_gamma", + "math_lgamma", + "math_log", + "math_log10", + "math_log1p", + "math_log2", + "math_pow", + "math_radians", + "math_sin", + "math_sinh", + "math_tan", + "math_tanh", + "math_sqrt", + "max", + "min", + "random_random", + "round", + "time_time", + "str", + "print", + "input" + ], + "constants": [ + "math_e", + "math_inf", + "math_nan", + "math_pi", + "math_tau" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 40c22a2..9fd404c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,18 +22,18 @@ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["es6", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + "useDefineForClassFields": false, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ESNext",//"commonjs", /* Specify what module code is generated. */ + "module": "commonjs",//"ESNext",// /* Specify what module code is generated. */ "rootDir": "src", /* Specify the root folder within your source files. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ @@ -43,7 +43,7 @@ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - //"resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */