• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / py-slang / 23841458542

01 Apr 2026 09:19AM UTC coverage: 63.539% (+2.9%) from 60.609%
23841458542

push

github

web-flow
Add new standard library functions (#129)

* Add new standard library functions

* Remove `char_at` + Add indexing for strings with subscript postfix operator + Fix complex `fromString` + Add more tests

* Add `arity` function + Update builtins to store minimum number of arguments

* Enable variadic related tests

* Add `len` function to standard library

* Revert documentation change

* Update error message for `float`

* Update `float` function to support other `inf` and `nan`

* Fix utf-8 bugs with string access

* Fix `int` to support arbitrarily large integers

* Add test-cases for arbitrarily large integers

---------

Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>

794 of 1459 branches covered (54.42%)

Branch coverage included in aggregate %.

79 of 121 new or added lines in 9 files covered. (65.29%)

4 existing lines in 3 files now uncovered.

2599 of 3881 relevant lines covered (66.97%)

3525.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.33
/src/stdlib.ts
1
import {
2
  BigIntValue,
3
  BoolValue,
4
  BuiltinValue,
5
  ComplexValue,
6
  NumberValue,
7
  StringValue,
8
  Value,
9
} from "./cse-machine/stash";
10
// npm install mathjs
11
import { erf, gamma, lgamma } from "mathjs";
6✔
12
import { Context } from "./cse-machine/context";
13
import { ControlItem } from "./cse-machine/control";
14
import { handleRuntimeError } from "./cse-machine/error";
6✔
15
import { displayOutput, receiveInput } from "./cse-machine/streams";
6✔
16
import {
6✔
17
  MissingRequiredPositionalError,
18
  TooManyPositionalArgumentsError,
19
  TypeError,
20
  UserError,
21
  ValueError,
22
} from "./errors/errors";
23

24
export const minArgMap = new Map<string, number>();
6✔
25

26
export function Validate<T extends Value | Promise<Value>>(
6✔
27
  minArgs: number | null,
28
  maxArgs: number | null,
29
  functionName: string,
30
  strict: boolean,
31
) {
32
  return function (
432✔
33
    _target: unknown,
34
    _propertyKey: string,
35
    descriptor: TypedPropertyDescriptor<
36
      (args: Value[], source: string, command: ControlItem, context: Context) => T
37
    >,
38
  ): void {
39
    const originalMethod = descriptor.value!;
432✔
40
    minArgMap.set(functionName, minArgs || 0);
432✔
41
    descriptor.value = function (
432✔
42
      args: Value[],
43
      source: string,
44
      command: ControlItem,
45
      context: Context,
46
    ): T {
47
      if (minArgs !== null && args.length < minArgs) {
2,102✔
48
        handleRuntimeError(
5✔
49
          context,
50
          new MissingRequiredPositionalError(
51
            source,
52
            command as ExprNS.Expr,
53
            functionName,
54
            minArgs,
55
            args,
56
            strict,
57
          ),
58
        );
59
      }
60

61
      if (maxArgs !== null && args.length > maxArgs) {
2,097✔
62
        handleRuntimeError(
2✔
63
          context,
64
          new TooManyPositionalArgumentsError(
65
            source,
66
            command as ExprNS.Expr,
67
            functionName,
68
            maxArgs,
69
            args,
70
            strict,
71
          ),
72
        );
73
      }
74

75
      return originalMethod.call(this, args, source, command, context);
2,095✔
76
    };
77
  };
78
}
79

80
export class BuiltInFunctions {
6✔
81
  @Validate(1, 1, "arity", true)
82
  static arity(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
83
    const func = args[0];
88✔
84
    if (func.type !== "builtin" && func.type !== "closure") {
88✔
85
      handleRuntimeError(
7✔
86
        context,
87
        new TypeError(source, command as ExprNS.Expr, context, func.type, "function"),
88
      );
89
    }
90
    if (func.type === "closure") {
81✔
91
      const variadicInstance = func.closure.node.parameters.findIndex(param => param.isStarred);
10✔
92
      if (variadicInstance !== -1) {
6✔
93
        return { type: "bigint", value: BigInt(variadicInstance) };
4✔
94
      }
95
      return { type: "bigint", value: BigInt(func.closure.node.parameters.length) };
2✔
96
    }
97
    return { type: "bigint", value: BigInt(func.minArgs) };
75✔
98
  }
99

100
  @Validate(null, 2, "int", true)
101
  static int(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
102
    if (args.length === 0) {
15!
103
      return { type: "bigint", value: BigInt(0) };
×
104
    }
105
    const arg = args[0];
15✔
106
    if (!isNumeric(arg) && arg.type !== "string" && arg.type !== "bool") {
15✔
107
      handleRuntimeError(
4✔
108
        context,
109
        new TypeError(source, command as ExprNS.Expr, context, arg.type, "str, int, float or bool"),
110
      );
111
    }
112

113
    if (args.length === 1) {
11✔
114
      if (arg.type === "number") {
11✔
115
        const truncated = Math.trunc(arg.value);
2✔
116
        return { type: "bigint", value: BigInt(truncated) };
2✔
117
      }
118
      if (arg.type === "bigint") {
9✔
119
        return { type: "bigint", value: arg.value };
1✔
120
      }
121
      if (arg.type === "string") {
8✔
122
        const str = arg.value.trim().replace(/_/g, "");
7✔
123
        if (!/^[+-]?\d+$/.test(str)) {
7✔
124
          handleRuntimeError(
3✔
125
            context,
126
            new ValueError(source, command as ExprNS.Expr, context, "int"),
127
          );
128
        }
129
        return { type: "bigint", value: BigInt(str) };
4✔
130
      }
131
      return { type: "bigint", value: arg.value ? BigInt(1) : BigInt(0) };
1!
132
    }
NEW
133
    const baseArg = args[1];
×
NEW
134
    if (arg.type !== "string") {
×
NEW
135
      handleRuntimeError(
×
136
        context,
137
        new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
138
      );
139
    }
NEW
140
    if (baseArg.type !== "bigint") {
×
NEW
141
      handleRuntimeError(
×
142
        context,
143
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "int"),
144
      );
145
    }
146

NEW
147
    let base = Number(baseArg.value);
×
NEW
148
    let str = arg.value.trim().replace(/_/g, "");
×
149

NEW
150
    const sign = str.startsWith("-") ? -1 : 1;
×
NEW
151
    if (str.startsWith("+") || str.startsWith("-")) {
×
NEW
152
      str = str.substring(1);
×
153
    }
154

NEW
155
    if (base === 0) {
×
NEW
156
      if (str.startsWith("0x") || str.startsWith("0X")) {
×
NEW
157
        base = 16;
×
NEW
158
        str = str.substring(2);
×
NEW
159
      } else if (str.startsWith("0o") || str.startsWith("0O")) {
×
NEW
160
        base = 8;
×
NEW
161
        str = str.substring(2);
×
NEW
162
      } else if (str.startsWith("0b") || str.startsWith("0B")) {
×
NEW
163
        base = 2;
×
NEW
164
        str = str.substring(2);
×
165
      } else {
NEW
166
        base = 10;
×
167
      }
168
    }
169

NEW
170
    if (base < 2 || base > 36) {
×
NEW
171
      handleRuntimeError(context, new ValueError(source, command as ExprNS.Expr, context, "int"));
×
172
    }
173

NEW
174
    const validChars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base);
×
NEW
175
    const regex = new RegExp(`^[${validChars}]+$`, "i");
×
NEW
176
    if (!regex.test(str)) {
×
NEW
177
      handleRuntimeError(context, new ValueError(source, command as ExprNS.Expr, context, "int"));
×
178
    }
179

NEW
180
    let res = BigInt(0);
×
NEW
181
    for (const char of str) {
×
NEW
182
      res = res * BigInt(base) + BigInt(validChars.indexOf(char.toLowerCase()));
×
183
    }
NEW
184
    return { type: "bigint", value: BigInt(sign) * res };
×
185
  }
186

187
  @Validate(null, 1, "float", true)
188
  static float(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
189
    if (args.length === 0) {
24!
NEW
190
      return { type: "number", value: 0 };
×
191
    }
192
    const val = args[0];
24✔
193
    if (val.type === "bigint") {
24✔
194
      return { type: "number", value: Number(val.value) };
1✔
195
    } else if (val.type === "number") {
23✔
196
      return { type: "number", value: val.value };
2✔
197
    } else if (val.type === "bool") {
21✔
198
      return { type: "number", value: val.value ? 1 : 0 };
1!
199
    } else if (val.type === "string") {
20✔
200
      const str = val.value.trim().replace(/_/g, "").toLowerCase();
16✔
201
      const mappings = {
16✔
202
        inf: Infinity,
203
        "+inf": Infinity,
204
        "-inf": -Infinity,
205
        infinity: Infinity,
206
        "+infinity": Infinity,
207
        "-infinity": -Infinity,
208
        nan: NaN,
209
        "+nan": NaN,
210
        "-nan": NaN,
211
      };
212
      if (str in mappings) {
16✔
213
        return { type: "number", value: mappings[str as keyof typeof mappings] };
8✔
214
      }
215
      const num = Number(str);
8✔
216
      if (isNaN(num)) {
8✔
217
        handleRuntimeError(
1✔
218
          context,
219
          new ValueError(source, command as ExprNS.Expr, context, "float"),
220
        );
221
      }
222
      return { type: "number", value: num };
7✔
223
    }
224
    handleRuntimeError(
4✔
225
      context,
226
      new TypeError(
227
        source,
228
        command as ExprNS.Expr,
229
        context,
230
        val.type,
231
        "'float', 'int', 'bool' or 'str'",
232
      ),
233
    );
234
  }
235

236
  @Validate(null, 1, "complex", true)
237
  static complex(
6✔
238
    args: Value[],
239
    source: string,
240
    command: ControlItem,
241
    context: Context,
242
  ): ComplexValue {
243
    if (args.length === 0) {
21!
NEW
244
      return { type: "complex", value: new PyComplexNumber(0, 0) };
×
245
    }
246
    const val = args[0];
21✔
247
    if (
21✔
248
      val.type !== "bigint" &&
79✔
249
      val.type !== "number" &&
250
      val.type !== "bool" &&
251
      val.type !== "string" &&
252
      val.type !== "complex"
253
    ) {
254
      handleRuntimeError(
3✔
255
        context,
256
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
257
      );
258
    }
259
    return {
18✔
260
      type: "complex",
261
      value: PyComplexNumber.fromValue(context, source, command as ExprNS.Expr, val.value),
262
    };
263
  }
264

265
  @Validate(1, 1, "real", true)
266
  static real(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
NEW
267
    const val = args[0];
×
NEW
268
    if (val.type !== "complex") {
×
UNCOV
269
      handleRuntimeError(
×
270
        context,
271
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
272
      );
273
    }
NEW
274
    return { type: "number", value: val.value.real };
×
275
  }
276

277
  @Validate(1, 1, "imag", true)
278
  static imag(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
NEW
279
    const val = args[0];
×
NEW
280
    if (val.type !== "complex") {
×
UNCOV
281
      handleRuntimeError(
×
282
        context,
283
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
284
      );
285
    }
NEW
286
    return { type: "number", value: val.value.imag };
×
287
  }
288

289
  @Validate(null, 1, "bool", true)
290
  static bool(args: Value[], _source: string, _command: ControlItem, _context: Context): BoolValue {
6✔
291
    if (args.length === 0) {
12!
NEW
292
      return { type: "bool", value: false };
×
293
    }
294
    const val = args[0];
12✔
295
    return { type: "bool", value: !isFalsy(val) };
12✔
296
  }
297

298
  @Validate(1, 1, "abs", false)
299
  static abs(
6✔
300
    args: Value[],
301
    source: string,
302
    command: ControlItem,
303
    context: Context,
304
  ): BigIntValue | NumberValue {
305
    const x = args[0];
15✔
306
    switch (x.type) {
15!
307
      case "bigint": {
308
        const intVal = x.value;
11✔
309
        const result: bigint = intVal < 0 ? -intVal : intVal;
11✔
310
        return { type: "bigint", value: result };
11✔
311
      }
312
      case "number": {
313
        return { type: "number", value: Math.abs(x.value) };
2✔
314
      }
315
      case "complex": {
316
        // Calculate the modulus (absolute value) of a complex number.
317
        const real = x.value.real;
×
318
        const imag = x.value.imag;
×
319
        const modulus = Math.sqrt(real * real + imag * imag);
×
320
        return { type: "number", value: modulus };
×
321
      }
322
      default:
323
        handleRuntimeError(
2✔
324
          context,
325
          new TypeError(
326
            source,
327
            command as ExprNS.Expr,
328
            context,
329
            args[0].type,
330
            "float', 'int' or 'complex",
331
          ),
332
        );
333
    }
334
  }
335

336
  @Validate(1, 1, "len", true)
337
  static len(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
338
    const val = args[0];
14✔
339
    if (val.type === "string" || val.type === "list") {
14✔
340
      // The spread operator is used to count the number of Unicode code points
341
      // in the string
342
      return { type: "bigint", value: BigInt([...val.value].length) };
7✔
343
    }
344
    handleRuntimeError(
7✔
345
      context,
346
      new TypeError(source, command as ExprNS.Expr, context, val.type, "object with length"),
347
    );
348
  }
349

350
  static toStr(val: Value): string {
351
    return toPythonString(val);
2✔
352
  }
353

354
  static error(args: Value[], _source: string, command: ControlItem, context: Context): Value {
355
    const output = "Error: " + args.map(arg => BuiltInFunctions.toStr(arg)).join(" ") + "\n";
2✔
356
    handleRuntimeError(context, new UserError(output, command as ExprNS.Expr));
2✔
357
  }
358

359
  @Validate(1, 1, "math_acos", false)
360
  static math_acos(
6✔
361
    args: Value[],
362
    source: string,
363
    command: ControlItem,
364
    context: Context,
365
  ): NumberValue {
366
    const x = args[0];
×
367
    if (!isNumeric(x)) {
×
368
      handleRuntimeError(
×
369
        context,
370
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
371
      );
372
    }
373

374
    let num: number;
375
    if (x.type === "number") {
×
376
      num = x.value;
×
377
    } else {
378
      num = Number(x.value);
×
379
    }
380

381
    if (num < -1 || num > 1) {
×
382
      handleRuntimeError(
×
383
        context,
384
        new ValueError(source, command as ExprNS.Expr, context, "math_acos"),
385
      );
386
    }
387

388
    const result = Math.acos(num);
×
389
    return { type: "number", value: result };
×
390
  }
391

392
  @Validate(1, 1, "math_acosh", false)
393
  static math_acosh(
6✔
394
    args: Value[],
395
    source: string,
396
    command: ControlItem,
397
    context: Context,
398
  ): NumberValue {
399
    const x = args[0];
×
400

401
    if (!isNumeric(x)) {
×
402
      handleRuntimeError(
×
403
        context,
404
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
405
      );
406
    }
407

408
    let num: number;
409
    if (x.type === "number") {
×
410
      num = x.value;
×
411
    } else {
412
      num = Number(x.value);
×
413
    }
414

415
    if (num < 1) {
×
416
      handleRuntimeError(
×
417
        context,
418
        new ValueError(source, command as ExprNS.Expr, context, "math_acosh"),
419
      );
420
    }
421

422
    const result = Math.acosh(num);
×
423
    return { type: "number", value: result };
×
424
  }
425

426
  @Validate(1, 1, "math_asin", false)
427
  static math_asin(
6✔
428
    args: Value[],
429
    source: string,
430
    command: ControlItem,
431
    context: Context,
432
  ): NumberValue {
433
    const x = args[0];
×
434
    if (!isNumeric(x)) {
×
435
      handleRuntimeError(
×
436
        context,
437
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
438
      );
439
    }
440

441
    let num: number;
442
    if (x.type === "number") {
×
443
      num = x.value;
×
444
    } else {
445
      num = Number(x.value);
×
446
    }
447

448
    if (num < -1 || num > 1) {
×
449
      handleRuntimeError(
×
450
        context,
451
        new ValueError(source, command as ExprNS.Expr, context, "math_asin"),
452
      );
453
    }
454

455
    const result = Math.asin(num);
×
456
    return { type: "number", value: result };
×
457
  }
458

459
  @Validate(1, 1, "math_asinh", false)
460
  static math_asinh(
6✔
461
    args: Value[],
462
    source: string,
463
    command: ControlItem,
464
    context: Context,
465
  ): NumberValue {
466
    const x = args[0];
×
467
    if (!isNumeric(x)) {
×
468
      handleRuntimeError(
×
469
        context,
470
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
471
      );
472
    }
473

474
    let num: number;
475
    if (x.type === "number") {
×
476
      num = x.value;
×
477
    } else {
478
      num = Number(x.value);
×
479
    }
480

481
    const result = Math.asinh(num);
×
482
    return { type: "number", value: result };
×
483
  }
484

485
  @Validate(1, 1, "math_atan", false)
486
  static math_atan(
6✔
487
    args: Value[],
488
    source: string,
489
    command: ControlItem,
490
    context: Context,
491
  ): NumberValue {
492
    const x = args[0];
×
493
    if (!isNumeric(x)) {
×
494
      handleRuntimeError(
×
495
        context,
496
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
497
      );
498
    }
499

500
    let num: number;
501
    if (x.type === "number") {
×
502
      num = x.value;
×
503
    } else {
504
      num = Number(x.value);
×
505
    }
506

507
    const result = Math.atan(num);
×
508
    return { type: "number", value: result };
×
509
  }
510

511
  @Validate(2, 2, "math_atan2", false)
512
  static math_atan2(
6✔
513
    args: Value[],
514
    source: string,
515
    command: ControlItem,
516
    context: Context,
517
  ): NumberValue {
518
    const y = args[0];
×
519
    const x = args[1];
×
520
    if (!isNumeric(x)) {
×
521
      handleRuntimeError(
×
522
        context,
523
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
524
      );
525
    } else if (!isNumeric(y)) {
×
526
      handleRuntimeError(
×
527
        context,
528
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
529
      );
530
    }
531

532
    let yNum: number, xNum: number;
533
    if (y.type === "number") {
×
534
      yNum = y.value;
×
535
    } else {
536
      yNum = Number(y.value);
×
537
    }
538

539
    if (x.type === "number") {
×
540
      xNum = x.value;
×
541
    } else {
542
      xNum = Number(x.value);
×
543
    }
544

545
    const result = Math.atan2(yNum, xNum);
×
546
    return { type: "number", value: result };
×
547
  }
548

549
  @Validate(1, 1, "math_atanh", false)
550
  static math_atanh(
6✔
551
    args: Value[],
552
    source: string,
553
    command: ControlItem,
554
    context: Context,
555
  ): NumberValue {
556
    const x = args[0];
×
557
    if (!isNumeric(x)) {
×
558
      handleRuntimeError(
×
559
        context,
560
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
561
      );
562
    }
563

564
    let num: number;
565
    if (x.type === "number") {
×
566
      num = x.value;
×
567
    } else {
568
      num = Number(x.value);
×
569
    }
570

571
    if (num <= -1 || num >= 1) {
×
572
      handleRuntimeError(
×
573
        context,
574
        new ValueError(source, command as ExprNS.Expr, context, "math_atanh"),
575
      );
576
    }
577

578
    const result = Math.atanh(num);
×
579
    return { type: "number", value: result };
×
580
  }
581

582
  @Validate(1, 1, "math_cos", false)
583
  static math_cos(
6✔
584
    args: Value[],
585
    source: string,
586
    command: ControlItem,
587
    context: Context,
588
  ): NumberValue {
589
    const x = args[0];
7✔
590
    if (!isNumeric(x)) {
7✔
591
      handleRuntimeError(
3✔
592
        context,
593
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
594
      );
595
    }
596

597
    let num: number;
598
    if (x.type === "number") {
4✔
599
      num = x.value;
3✔
600
    } else {
601
      num = Number(x.value);
1✔
602
    }
603

604
    const result = Math.cos(num);
4✔
605
    return { type: "number", value: result };
4✔
606
  }
607

608
  @Validate(1, 1, "math_cosh", false)
609
  static math_cosh(
6✔
610
    args: Value[],
611
    source: string,
612
    command: ControlItem,
613
    context: Context,
614
  ): NumberValue {
615
    const x = args[0];
×
616
    if (!isNumeric(x)) {
×
617
      handleRuntimeError(
×
618
        context,
619
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
620
      );
621
    }
622

623
    let num: number;
624
    if (x.type === "number") {
×
625
      num = x.value;
×
626
    } else {
627
      num = Number(x.value);
×
628
    }
629

630
    const result = Math.cosh(num);
×
631
    return { type: "number", value: result };
×
632
  }
633

634
  @Validate(1, 1, "math_degrees", false)
635
  static math_degrees(
6✔
636
    args: Value[],
637
    source: string,
638
    command: ControlItem,
639
    context: Context,
640
  ): NumberValue {
641
    const x = args[0];
×
642
    if (!isNumeric(x)) {
×
643
      handleRuntimeError(
×
644
        context,
645
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
646
      );
647
    }
648

649
    let num: number;
650
    if (x.type === "number") {
×
651
      num = x.value;
×
652
    } else {
653
      num = Number(x.value);
×
654
    }
655

656
    const result = (num * 180) / Math.PI;
×
657
    return { type: "number", value: result };
×
658
  }
659

660
  @Validate(1, 1, "math_erf", false)
661
  static math_erf(
6✔
662
    args: Value[],
663
    source: string,
664
    command: ControlItem,
665
    context: Context,
666
  ): NumberValue {
667
    const x = args[0];
×
668
    if (!isNumeric(x)) {
×
669
      handleRuntimeError(
×
670
        context,
671
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
672
      );
673
    }
674

675
    let num: number;
676
    if (x.type === "number") {
×
677
      num = x.value;
×
678
    } else {
679
      num = Number(x.value);
×
680
    }
681

682
    const erfnum = erf(num);
×
683

684
    return { type: "number", value: erfnum };
×
685
  }
686

687
  @Validate(1, 1, "math_erfc", false)
688
  static math_erfc(
6✔
689
    args: Value[],
690
    source: string,
691
    command: ControlItem,
692
    context: Context,
693
  ): NumberValue {
694
    const x = args[0];
×
695
    if (!isNumeric(x)) {
×
696
      handleRuntimeError(
×
697
        context,
698
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
699
      );
700
    }
701

702
    const erfc = 1 - BuiltInFunctions.math_erf([args[0]], source, command, context).value;
×
703

704
    return { type: "number", value: erfc };
×
705
  }
706

707
  @Validate(2, 2, "math_comb", false)
708
  static math_comb(
6✔
709
    args: Value[],
710
    source: string,
711
    command: ControlItem,
712
    context: Context,
713
  ): BigIntValue {
714
    const n = args[0];
×
715
    const k = args[1];
×
716

717
    if (n.type !== "bigint") {
×
718
      handleRuntimeError(
×
719
        context,
720
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
721
      );
722
    } else if (k.type !== "bigint") {
×
723
      handleRuntimeError(
×
724
        context,
725
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
726
      );
727
    }
728

729
    const nVal = BigInt(n.value);
×
730
    const kVal = BigInt(k.value);
×
731

732
    if (nVal < 0 || kVal < 0) {
×
733
      handleRuntimeError(
×
734
        context,
735
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
736
      );
737
    }
738

739
    if (kVal > nVal) {
×
740
      return { type: "bigint", value: BigInt(0) };
×
741
    }
742

743
    let result: bigint = BigInt(1);
×
744
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
745

746
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
747
      result = (result * (nVal - i)) / (i + BigInt(1));
×
748
    }
749

750
    return { type: "bigint", value: result };
×
751
  }
752

753
  @Validate(1, 1, "math_factorial", false)
754
  static math_factorial(
6✔
755
    args: Value[],
756
    source: string,
757
    command: ControlItem,
758
    context: Context,
759
  ): BigIntValue {
760
    const n = args[0];
×
761

762
    if (n.type !== "bigint") {
×
763
      handleRuntimeError(
×
764
        context,
765
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
766
      );
767
    }
768

769
    const nVal = BigInt(n.value);
×
770

771
    if (nVal < 0) {
×
772
      handleRuntimeError(
×
773
        context,
774
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
775
      );
776
    }
777

778
    // 0! = 1
779
    if (nVal === BigInt(0)) {
×
780
      return { type: "bigint", value: BigInt(1) };
×
781
    }
782

783
    let result: bigint = BigInt(1);
×
784
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
785
      result *= i;
×
786
    }
787

788
    return { type: "bigint", value: result };
×
789
  }
790

791
  static math_gcd(
792
    args: Value[],
793
    source: string,
794
    command: ControlItem,
795
    context: Context,
796
  ): BigIntValue {
797
    if (args.length === 0) {
×
798
      return { type: "bigint", value: BigInt(0) };
×
799
    }
800

801
    const values = args.map(v => {
×
802
      if (v.type !== "bigint") {
×
803
        handleRuntimeError(
×
804
          context,
805
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
806
        );
807
      }
808
      return BigInt(v.value);
×
809
    });
810

811
    const allZero = values.every(val => val === BigInt(0));
×
812
    if (allZero) {
×
813
      return { type: "bigint", value: BigInt(0) };
×
814
    }
815

816
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
817
    for (let i = 1; i < values.length; i++) {
×
818
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
819
      if (currentGcd === BigInt(1)) {
×
820
        break;
×
821
      }
822
    }
823

824
    return { type: "bigint", value: currentGcd };
×
825
  }
826

827
  static gcdOfTwo(a: bigint, b: bigint): bigint {
828
    let x: bigint = a;
×
829
    let y: bigint = b;
×
830
    while (y !== BigInt(0)) {
×
831
      const temp = x % y;
×
832
      x = y;
×
833
      y = temp;
×
834
    }
835
    return x < 0 ? -x : x;
×
836
  }
837

838
  @Validate(1, 1, "math_isqrt", false)
839
  static math_isqrt(
6✔
840
    args: Value[],
841
    source: string,
842
    command: ControlItem,
843
    context: Context,
844
  ): BigIntValue {
845
    const nValObj = args[0];
×
846
    if (nValObj.type !== "bigint") {
×
847
      handleRuntimeError(
×
848
        context,
849
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
850
      );
851
    }
852

853
    const n: bigint = nValObj.value;
×
854

855
    if (n < 0) {
×
856
      handleRuntimeError(
×
857
        context,
858
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
859
      );
860
    }
861

862
    if (n < 2) {
×
863
      return { type: "bigint", value: n };
×
864
    }
865

866
    let low: bigint = BigInt(1);
×
867
    let high: bigint = n;
×
868

869
    while (low < high) {
×
870
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
871
      const sq = mid * mid;
×
872
      if (sq <= n) {
×
873
        low = mid;
×
874
      } else {
875
        high = mid - BigInt(1);
×
876
      }
877
    }
878

879
    return { type: "bigint", value: low };
×
880
  }
881

882
  static math_lcm(
883
    args: Value[],
884
    source: string,
885
    command: ControlItem,
886
    context: Context,
887
  ): BigIntValue {
888
    if (args.length === 0) {
×
889
      return { type: "bigint", value: BigInt(1) };
×
890
    }
891

892
    const values = args.map(val => {
×
893
      if (val.type !== "bigint") {
×
894
        handleRuntimeError(
×
895
          context,
896
          new TypeError(source, command as ExprNS.Expr, context, val.type, "int"),
897
        );
898
      }
899
      return BigInt(val.value);
×
900
    });
901

902
    if (values.some(v => v === BigInt(0))) {
×
903
      return { type: "bigint", value: BigInt(0) };
×
904
    }
905

906
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
907
    for (let i = 1; i < values.length; i++) {
×
908
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
909
      if (currentLcm === BigInt(0)) {
×
910
        break;
×
911
      }
912
    }
913

914
    return { type: "bigint", value: currentLcm };
×
915
  }
916

917
  static lcmOfTwo(a: bigint, b: bigint): bigint {
918
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
919
    return BigInt((a / gcdVal) * b);
×
920
  }
921

922
  static absBigInt(x: bigint): bigint {
923
    return x < 0 ? -x : x;
×
924
  }
925

926
  @Validate(1, 2, "math_perm", true)
927
  static math_perm(
6✔
928
    args: Value[],
929
    source: string,
930
    command: ControlItem,
931
    context: Context,
932
  ): BigIntValue {
933
    const nValObj = args[0];
×
934
    if (nValObj.type !== "bigint") {
×
935
      handleRuntimeError(
×
936
        context,
937
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
938
      );
939
    }
940
    const n = BigInt(nValObj.value);
×
941

942
    let k = n;
×
943
    if (args.length === 2) {
×
944
      const kValObj = args[1];
×
945
      if (kValObj.type === "none") {
×
946
        k = n;
×
947
      } else if (kValObj.type === "bigint") {
×
948
        k = BigInt(kValObj.value);
×
949
      } else {
950
        handleRuntimeError(
×
951
          context,
952
          new TypeError(source, command as ExprNS.Expr, context, kValObj.type, "int' or 'None"),
953
        );
954
      }
955
    }
956

957
    if (n < 0 || k < 0) {
×
958
      handleRuntimeError(
×
959
        context,
960
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
961
      );
962
    }
963

964
    if (k > n) {
×
965
      return { type: "bigint", value: BigInt(0) };
×
966
    }
967

968
    let result: bigint = BigInt(1);
×
969
    for (let i: bigint = BigInt(0); i < k; i++) {
×
970
      result *= n - i;
×
971
    }
972

973
    return { type: "bigint", value: result };
×
974
  }
975

976
  @Validate(1, 1, "math_ceil", false)
977
  static math_ceil(
6✔
978
    args: Value[],
979
    source: string,
980
    command: ControlItem,
981
    context: Context,
982
  ): BigIntValue {
983
    const x = args[0];
×
984

985
    if (x.type === "bigint") {
×
986
      return x;
×
987
    }
988

989
    if (x.type === "number") {
×
990
      const numVal = x.value;
×
991
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
992
      return { type: "bigint", value: ceiled };
×
993
    }
994

995
    handleRuntimeError(
×
996
      context,
997
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
998
    );
999
  }
1000

1001
  @Validate(1, 1, "math_fabs", false)
1002
  static math_fabs(
6✔
1003
    args: Value[],
1004
    source: string,
1005
    command: ControlItem,
1006
    context: Context,
1007
  ): NumberValue {
1008
    const x = args[0];
×
1009

1010
    if (x.type === "bigint") {
×
1011
      const bigVal: bigint = BigInt(x.value);
×
1012
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
1013
      return { type: "number", value: absVal };
×
1014
    }
1015

1016
    if (x.type === "number") {
×
1017
      const numVal: number = x.value;
×
1018
      if (typeof numVal !== "number") {
×
1019
        handleRuntimeError(
×
1020
          context,
1021
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1022
        );
1023
      }
1024
      const absVal: number = Math.abs(numVal);
×
1025
      return { type: "number", value: absVal };
×
1026
    }
1027

1028
    handleRuntimeError(
×
1029
      context,
1030
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1031
    );
1032
  }
1033

1034
  @Validate(1, 1, "math_floor", false)
1035
  static math_floor(
6✔
1036
    args: Value[],
1037
    source: string,
1038
    command: ControlItem,
1039
    context: Context,
1040
  ): BigIntValue {
1041
    const x = args[0];
×
1042

1043
    if (x.type === "bigint") {
×
1044
      return x;
×
1045
    }
1046

1047
    if (x.type === "number") {
×
1048
      const numVal: number = x.value;
×
1049
      if (typeof numVal !== "number") {
×
1050
        handleRuntimeError(
×
1051
          context,
1052
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1053
        );
1054
      }
1055
      const floored: bigint = BigInt(Math.floor(numVal));
×
1056
      return { type: "bigint", value: floored };
×
1057
    }
1058

1059
    handleRuntimeError(
×
1060
      context,
1061
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1062
    );
1063
  }
1064

1065
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
1066
  static twoProd(a: number, b: number): { prod: number; err: number } {
1067
    const prod = a * b;
×
1068
    const c = 134217729; // 2^27 + 1
×
1069
    const a_hi = a * c - (a * c - a);
×
1070
    const a_lo = a - a_hi;
×
1071
    const b_hi = b * c - (b * c - b);
×
1072
    const b_lo = b - b_hi;
×
1073
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
1074
    return { prod, err };
×
1075
  }
1076

1077
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
1078
  static twoSum(a: number, b: number): { sum: number; err: number } {
1079
    const sum = a + b;
×
1080
    const v = sum - a;
×
1081
    const err = a - (sum - v) + (b - v);
×
1082
    return { sum, err };
×
1083
  }
1084

1085
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1086
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1087
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1088
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1089
    const result = sum + (prodErr + sumErr);
×
1090
    return result;
×
1091
  }
1092

1093
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
1094
    if (val.type === "bigint") {
×
1095
      return Number(val.value);
×
1096
    } else if (val.type === "number") {
×
1097
      return val.value;
×
1098
    } else {
1099
      handleRuntimeError(
×
1100
        context,
1101
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
1102
      );
1103
    }
1104
  }
1105

1106
  @Validate(3, 3, "math_fma", false)
1107
  static math_fma(
6✔
1108
    args: Value[],
1109
    source: string,
1110
    command: ControlItem,
1111
    context: Context,
1112
  ): NumberValue {
1113
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1114
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1115
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1116

1117
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1118
    // and fma(inf, 0, nan) should return NaN.
1119
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1120
      return { type: "number", value: NaN };
×
1121
    }
1122
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1123
      return { type: "number", value: NaN };
×
1124
    }
1125
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1126
      return { type: "number", value: NaN };
×
1127
    }
1128

1129
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1130
    return { type: "number", value: result };
×
1131
  }
1132

1133
  @Validate(2, 2, "math_fmod", false)
1134
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1135
    // Convert inputs to numbers
1136
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1137
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1138

1139
    // Divisor cannot be zero
1140
    if (yVal === 0) {
×
1141
      handleRuntimeError(
×
1142
        context,
1143
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1144
      );
1145
    }
1146

1147
    // JavaScript's % operator behaves similarly to C's fmod
1148
    // in that the sign of the result is the same as the sign of x.
1149
    // For corner cases (NaN, Infinity), JavaScript remainder
1150
    // yields results consistent with typical C library fmod behavior.
1151
    const remainder = xVal % yVal;
×
1152

1153
    return { type: "number", value: remainder };
×
1154
  }
1155

1156
  static roundToEven(num: number): number {
1157
    //uses Banker's Rounding as per Python's Round() function
1158
    const floorVal = Math.floor(num);
×
1159
    const ceilVal = Math.ceil(num);
×
1160
    const diffFloor = num - floorVal;
×
1161
    const diffCeil = ceilVal - num;
×
1162
    if (diffFloor < diffCeil) {
×
1163
      return floorVal;
×
1164
    } else if (diffCeil < diffFloor) {
×
1165
      return ceilVal;
×
1166
    } else {
1167
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1168
    }
1169
  }
1170

1171
  @Validate(2, 2, "math_remainder", false)
1172
  static math_remainder(
6✔
1173
    args: Value[],
1174
    source: string,
1175
    command: ControlItem,
1176
    context: Context,
1177
  ): NumberValue {
1178
    const x = args[0];
×
1179
    const y = args[1];
×
1180

1181
    let xValue: number;
1182
    if (x.type === "bigint") {
×
1183
      xValue = Number(x.value);
×
1184
    } else if (x.type === "number") {
×
1185
      xValue = x.value;
×
1186
    } else {
1187
      handleRuntimeError(
×
1188
        context,
1189
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1190
      );
1191
    }
1192

1193
    let yValue: number;
1194
    if (y.type === "bigint") {
×
1195
      yValue = Number(y.value);
×
1196
    } else if (y.type === "number") {
×
1197
      yValue = y.value;
×
1198
    } else {
1199
      handleRuntimeError(
×
1200
        context,
1201
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1202
      );
1203
    }
1204

1205
    if (yValue === 0) {
×
1206
      handleRuntimeError(
×
1207
        context,
1208
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1209
      );
1210
    }
1211

1212
    const quotient = xValue / yValue;
×
1213
    const n = BuiltInFunctions.roundToEven(quotient);
×
1214
    const remainder = xValue - n * yValue;
×
1215

1216
    return { type: "number", value: remainder };
×
1217
  }
1218

1219
  @Validate(1, 1, "math_trunc", false)
1220
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1221
    const x = args[0];
×
1222

1223
    if (x.type === "bigint") {
×
1224
      return x;
×
1225
    }
1226

1227
    if (x.type === "number") {
×
1228
      const numVal: number = x.value;
×
1229
      if (typeof numVal !== "number") {
×
1230
        handleRuntimeError(
×
1231
          context,
1232
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1233
        );
1234
      }
1235
      let truncated: number;
1236
      if (numVal === 0) {
×
1237
        truncated = 0;
×
1238
      } else if (numVal < 0) {
×
1239
        truncated = Math.ceil(numVal);
×
1240
      } else {
1241
        truncated = Math.floor(numVal);
×
1242
      }
1243
      return { type: "bigint", value: BigInt(truncated) };
×
1244
    }
1245

1246
    handleRuntimeError(
×
1247
      context,
1248
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1249
    );
1250
  }
1251

1252
  @Validate(2, 2, "math_copysign", false)
1253
  static math_copysign(
6✔
1254
    args: Value[],
1255
    source: string,
1256
    command: ControlItem,
1257
    context: Context,
1258
  ): NumberValue {
1259
    const [x, y] = args;
×
1260

1261
    if (!isNumeric(x)) {
×
1262
      handleRuntimeError(
×
1263
        context,
1264
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1265
      );
1266
    } else if (!isNumeric(y)) {
×
1267
      handleRuntimeError(
×
1268
        context,
1269
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1270
      );
1271
    }
1272

1273
    const xVal = Number(x.value);
×
1274
    const yVal = Number(y.value);
×
1275

1276
    const absVal = Math.abs(xVal);
×
1277
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1278
    const result = isNegative ? -absVal : absVal;
×
1279

1280
    return { type: "number", value: Number(result) };
×
1281
  }
1282

1283
  @Validate(1, 1, "math_isfinite", false)
1284
  static math_isfinite(
6✔
1285
    args: Value[],
1286
    source: string,
1287
    command: ControlItem,
1288
    context: Context,
1289
  ): BoolValue {
1290
    const xValObj = args[0];
×
1291
    if (!isNumeric(xValObj)) {
×
1292
      handleRuntimeError(
×
1293
        context,
1294
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1295
      );
1296
    }
1297

1298
    const x = Number(xValObj.value);
×
1299
    const result: boolean = Number.isFinite(x);
×
1300

1301
    return { type: "bool", value: result };
×
1302
  }
1303

1304
  @Validate(1, 1, "math_isinf", false)
1305
  static math_isinf(
6✔
1306
    args: Value[],
1307
    source: string,
1308
    command: ControlItem,
1309
    context: Context,
1310
  ): BoolValue {
1311
    const xValObj = args[0];
×
1312
    if (!isNumeric(xValObj)) {
×
1313
      handleRuntimeError(
×
1314
        context,
1315
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1316
      );
1317
    }
1318

1319
    const x = Number(xValObj.value);
×
1320
    const result: boolean = x === Infinity || x === -Infinity;
×
1321

1322
    return { type: "bool", value: result };
×
1323
  }
1324

1325
  @Validate(1, 1, "math_isnan", false)
1326
  static math_isnan(
6✔
1327
    args: Value[],
1328
    source: string,
1329
    command: ControlItem,
1330
    context: Context,
1331
  ): BoolValue {
1332
    const xValObj = args[0];
×
1333
    if (!isNumeric(xValObj)) {
×
1334
      handleRuntimeError(
×
1335
        context,
1336
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1337
      );
1338
    }
1339

1340
    const x = Number(xValObj.value);
×
1341
    const result: boolean = Number.isNaN(x);
×
1342

1343
    return { type: "bool", value: result };
×
1344
  }
1345

1346
  @Validate(2, 2, "math_ldexp", false)
1347
  static math_ldexp(
6✔
1348
    args: Value[],
1349
    source: string,
1350
    command: ControlItem,
1351
    context: Context,
1352
  ): NumberValue {
1353
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1354

1355
    if (args[1].type !== "bigint") {
×
1356
      handleRuntimeError(
×
1357
        context,
1358
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1359
      );
1360
    }
1361
    const expVal = args[1].value;
×
1362

1363
    // Perform x * 2^expVal
1364
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1365
    // That behavior parallels typical C library rules for ldexp.
1366
    const result = xVal * Math.pow(2, Number(expVal));
×
1367

1368
    return { type: "number", value: result };
×
1369
  }
1370

1371
  @Validate(2, 2, "math_nextafter", false)
1372
  static math_nextafter(
6✔
1373
    _args: Value[],
1374
    _source: string,
1375
    _command: ControlItem,
1376
    _context: Context,
1377
  ): Value {
1378
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1379
    throw new Error("math_nextafter not implemented");
×
1380
  }
1381

1382
  @Validate(1, 1, "math_ulp", false)
1383
  static math_ulp(
6✔
1384
    _args: Value[],
1385
    _source: string,
1386
    _command: ControlItem,
1387
    _context: Context,
1388
  ): Value {
1389
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1390
    throw new Error("math_ulp not implemented");
×
1391
  }
1392

1393
  @Validate(1, 1, "math_cbrt", false)
1394
  static math_cbrt(
6✔
1395
    args: Value[],
1396
    source: string,
1397
    command: ControlItem,
1398
    context: Context,
1399
  ): NumberValue {
1400
    const xVal = args[0];
×
1401
    let x: number;
1402

1403
    if (xVal.type !== "number") {
×
1404
      if (xVal.type === "bigint") {
×
1405
        x = Number(xVal.value);
×
1406
      } else {
1407
        handleRuntimeError(
×
1408
          context,
1409
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1410
        );
1411
      }
1412
    } else {
1413
      x = xVal.value;
×
1414
    }
1415

1416
    const result = Math.cbrt(x);
×
1417

1418
    return { type: "number", value: result };
×
1419
  }
1420

1421
  @Validate(1, 1, "math_exp", false)
1422
  static math_exp(
6✔
1423
    args: Value[],
1424
    source: string,
1425
    command: ControlItem,
1426
    context: Context,
1427
  ): NumberValue {
1428
    const xVal = args[0];
×
1429
    let x: number;
1430

1431
    if (xVal.type !== "number") {
×
1432
      if (xVal.type === "bigint") {
×
1433
        x = Number(xVal.value);
×
1434
      } else {
1435
        handleRuntimeError(
×
1436
          context,
1437
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1438
        );
1439
      }
1440
    } else {
1441
      x = xVal.value;
×
1442
    }
1443

1444
    const result = Math.exp(x);
×
1445
    return { type: "number", value: result };
×
1446
  }
1447

1448
  @Validate(1, 1, "math_exp2", false)
1449
  static math_exp2(
6✔
1450
    args: Value[],
1451
    source: string,
1452
    command: ControlItem,
1453
    context: Context,
1454
  ): NumberValue {
1455
    const xVal = args[0];
×
1456
    let x: number;
1457

1458
    if (xVal.type !== "number") {
×
1459
      if (xVal.type === "bigint") {
×
1460
        x = Number(xVal.value);
×
1461
      } else {
1462
        handleRuntimeError(
×
1463
          context,
1464
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1465
        );
1466
      }
1467
    } else {
1468
      x = xVal.value;
×
1469
    }
1470

1471
    const result = Math.pow(2, x);
×
1472
    return { type: "number", value: result };
×
1473
  }
1474

1475
  @Validate(1, 1, "math_expm1", false)
1476
  static math_expm1(
6✔
1477
    args: Value[],
1478
    source: string,
1479
    command: ControlItem,
1480
    context: Context,
1481
  ): NumberValue {
1482
    const x = args[0];
×
1483
    if (!isNumeric(x)) {
×
1484
      handleRuntimeError(
×
1485
        context,
1486
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1487
      );
1488
    }
1489

1490
    let num: number;
1491
    if (x.type === "number") {
×
1492
      num = x.value;
×
1493
    } else {
1494
      num = Number(x.value);
×
1495
    }
1496

1497
    const result = Math.expm1(num);
×
1498
    return { type: "number", value: result };
×
1499
  }
1500

1501
  @Validate(1, 1, "math_gamma", false)
1502
  static math_gamma(
6✔
1503
    args: Value[],
1504
    source: string,
1505
    command: ControlItem,
1506
    context: Context,
1507
  ): NumberValue {
1508
    const x = args[0];
×
1509
    if (!isNumeric(x)) {
×
1510
      handleRuntimeError(
×
1511
        context,
1512
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1513
      );
1514
    }
1515

1516
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1517
    const result = gamma(z);
×
1518

1519
    return { type: "number", value: result };
×
1520
  }
1521

1522
  @Validate(1, 1, "math_lgamma", false)
1523
  static math_lgamma(
6✔
1524
    args: Value[],
1525
    source: string,
1526
    command: ControlItem,
1527
    context: Context,
1528
  ): NumberValue {
1529
    const x = args[0];
×
1530
    if (!isNumeric(x)) {
×
1531
      handleRuntimeError(
×
1532
        context,
1533
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1534
      );
1535
    }
1536

1537
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1538
    const result = lgamma(z);
×
1539

1540
    return { type: "number", value: result };
×
1541
  }
1542

1543
  @Validate(1, 2, "math_log", true)
1544
  static math_log(
6✔
1545
    args: Value[],
1546
    source: string,
1547
    command: ControlItem,
1548
    context: Context,
1549
  ): NumberValue {
1550
    const x = args[0];
×
1551
    if (!isNumeric(x)) {
×
1552
      handleRuntimeError(
×
1553
        context,
1554
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1555
      );
1556
    }
1557
    let num: number;
1558
    if (x.type === "number") {
×
1559
      num = x.value;
×
1560
    } else {
1561
      num = Number(x.value);
×
1562
    }
1563

1564
    if (num <= 0) {
×
1565
      handleRuntimeError(
×
1566
        context,
1567
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1568
      );
1569
    }
1570

1571
    if (args.length === 1) {
×
1572
      return { type: "number", value: Math.log(num) };
×
1573
    }
1574

1575
    const baseArg = args[1];
×
1576
    if (!isNumeric(baseArg)) {
×
1577
      handleRuntimeError(
×
1578
        context,
1579
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
1580
      );
1581
    }
1582
    let baseNum: number;
1583
    if (baseArg.type === "number") {
×
1584
      baseNum = baseArg.value;
×
1585
    } else {
1586
      baseNum = Number(baseArg.value);
×
1587
    }
1588
    if (baseNum <= 0) {
×
1589
      handleRuntimeError(
×
1590
        context,
1591
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1592
      );
1593
    }
1594

1595
    const result = Math.log(num) / Math.log(baseNum);
×
1596
    return { type: "number", value: result };
×
1597
  }
1598

1599
  @Validate(1, 1, "math_log10", false)
1600
  static math_log10(
6✔
1601
    args: Value[],
1602
    source: string,
1603
    command: ControlItem,
1604
    context: Context,
1605
  ): NumberValue {
1606
    const x = args[0];
×
1607
    if (!isNumeric(x)) {
×
1608
      handleRuntimeError(
×
1609
        context,
1610
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1611
      );
1612
    }
1613
    let num: number;
1614
    if (x.type === "number") {
×
1615
      num = x.value;
×
1616
    } else {
1617
      num = Number(x.value);
×
1618
    }
1619
    if (num <= 0) {
×
1620
      handleRuntimeError(
×
1621
        context,
1622
        new ValueError(source, command as ExprNS.Expr, context, "math_log10"),
1623
      );
1624
    }
1625

1626
    const result = Math.log10(num);
×
1627
    return { type: "number", value: result };
×
1628
  }
1629

1630
  @Validate(1, 1, "math_log1p", false)
1631
  static math_log1p(
6✔
1632
    args: Value[],
1633
    source: string,
1634
    command: ControlItem,
1635
    context: Context,
1636
  ): NumberValue {
1637
    const x = args[0];
×
1638
    if (!isNumeric(x)) {
×
1639
      handleRuntimeError(
×
1640
        context,
1641
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1642
      );
1643
    }
1644
    let num: number;
1645
    if (x.type === "number") {
×
1646
      num = x.value;
×
1647
    } else {
1648
      num = Number(x.value);
×
1649
    }
1650
    if (1 + num <= 0) {
×
1651
      handleRuntimeError(
×
1652
        context,
1653
        new ValueError(source, command as ExprNS.Expr, context, "math_log1p"),
1654
      );
1655
    }
1656

1657
    const result = Math.log1p(num);
×
1658
    return { type: "number", value: result };
×
1659
  }
1660

1661
  @Validate(1, 1, "math_log2", false)
1662
  static math_log2(
6✔
1663
    args: Value[],
1664
    source: string,
1665
    command: ControlItem,
1666
    context: Context,
1667
  ): NumberValue {
1668
    const x = args[0];
×
1669
    if (!isNumeric(x)) {
×
1670
      handleRuntimeError(
×
1671
        context,
1672
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1673
      );
1674
    }
1675
    let num: number;
1676
    if (x.type === "number") {
×
1677
      num = x.value;
×
1678
    } else {
1679
      num = Number(x.value);
×
1680
    }
1681
    if (num <= 0) {
×
1682
      handleRuntimeError(
×
1683
        context,
1684
        new ValueError(source, command as ExprNS.Expr, context, "math_log2"),
1685
      );
1686
    }
1687

1688
    const result = Math.log2(num);
×
1689
    return { type: "number", value: result };
×
1690
  }
1691

1692
  @Validate(2, 2, "math_pow", false)
1693
  static math_pow(
6✔
1694
    args: Value[],
1695
    source: string,
1696
    command: ControlItem,
1697
    context: Context,
1698
  ): NumberValue {
1699
    const base = args[0];
×
1700
    const exp = args[1];
×
1701

1702
    if (!isNumeric(base)) {
×
1703
      handleRuntimeError(
×
1704
        context,
1705
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1706
      );
1707
    } else if (!isNumeric(exp)) {
×
1708
      handleRuntimeError(
×
1709
        context,
1710
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1711
      );
1712
    }
1713

1714
    let baseNum: number;
1715
    if (base.type === "number") {
×
1716
      baseNum = base.value;
×
1717
    } else {
1718
      baseNum = Number(base.value);
×
1719
    }
1720

1721
    let expNum: number;
1722
    if (exp.type === "number") {
×
1723
      expNum = exp.value;
×
1724
    } else {
1725
      expNum = Number(exp.value);
×
1726
    }
1727

1728
    const result = Math.pow(baseNum, expNum);
×
1729
    return { type: "number", value: result };
×
1730
  }
1731

1732
  @Validate(1, 1, "math_radians", false)
1733
  static math_radians(
6✔
1734
    args: Value[],
1735
    source: string,
1736
    command: ControlItem,
1737
    context: Context,
1738
  ): NumberValue {
1739
    const x = args[0];
×
1740
    if (!isNumeric(x)) {
×
1741
      handleRuntimeError(
×
1742
        context,
1743
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1744
      );
1745
    }
1746

1747
    let deg: number;
1748
    if (x.type === "number") {
×
1749
      deg = x.value;
×
1750
    } else {
1751
      deg = Number(x.value);
×
1752
    }
1753

1754
    const radians = (deg * Math.PI) / 180;
×
1755
    return { type: "number", value: radians };
×
1756
  }
1757

1758
  @Validate(1, 1, "math_sin", false)
1759
  static math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1760
    const x = args[0];
7✔
1761
    if (!isNumeric(x)) {
7✔
1762
      handleRuntimeError(
3✔
1763
        context,
1764
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1765
      );
1766
    }
1767

1768
    let num: number;
1769
    if (x.type === "number") {
4✔
1770
      num = x.value;
3✔
1771
    } else {
1772
      num = Number(x.value);
1✔
1773
    }
1774

1775
    const result = Math.sin(num);
4✔
1776
    return { type: "number", value: result };
4✔
1777
  }
1778

1779
  @Validate(1, 1, "math_sinh", false)
1780
  static math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1781
    const x = args[0];
×
1782
    if (!isNumeric(x)) {
×
1783
      handleRuntimeError(
×
1784
        context,
1785
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1786
      );
1787
    }
1788

1789
    let num: number;
1790
    if (x.type === "number") {
×
1791
      num = x.value;
×
1792
    } else {
1793
      num = Number(x.value);
×
1794
    }
1795

1796
    const result = Math.sinh(num);
×
1797
    return { type: "number", value: result };
×
1798
  }
1799

1800
  @Validate(1, 1, "math_tan", false)
1801
  static math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1802
    const x = args[0];
×
1803
    if (!isNumeric(x)) {
×
1804
      handleRuntimeError(
×
1805
        context,
1806
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1807
      );
1808
    }
1809

1810
    let num: number;
1811
    if (x.type === "number") {
×
1812
      num = x.value;
×
1813
    } else {
1814
      num = Number(x.value);
×
1815
    }
1816

1817
    const result = Math.tan(num);
×
1818
    return { type: "number", value: result };
×
1819
  }
1820

1821
  @Validate(1, 1, "math_tanh", false)
1822
  static math_tanh(
6✔
1823
    args: Value[],
1824
    source: string,
1825
    command: ControlItem,
1826
    context: Context,
1827
  ): NumberValue {
1828
    const x = args[0];
×
1829
    if (!isNumeric(x)) {
×
1830
      handleRuntimeError(
×
1831
        context,
1832
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1833
      );
1834
    }
1835

1836
    let num: number;
1837
    if (x.type === "number") {
×
1838
      num = x.value;
×
1839
    } else {
1840
      num = Number(x.value);
×
1841
    }
1842

1843
    const result = Math.tanh(num);
×
1844
    return { type: "number", value: result };
×
1845
  }
1846

1847
  @Validate(1, 1, "math_sqrt", false)
1848
  static math_sqrt(
6✔
1849
    args: Value[],
1850
    source: string,
1851
    command: ControlItem,
1852
    context: Context,
1853
  ): NumberValue {
1854
    const x = args[0];
×
1855
    if (!isNumeric(x)) {
×
1856
      handleRuntimeError(
×
1857
        context,
1858
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1859
      );
1860
    }
1861

1862
    let num: number;
1863
    if (x.type === "number") {
×
1864
      num = x.value;
×
1865
    } else {
1866
      num = Number(x.value);
×
1867
    }
1868

1869
    if (num < 0) {
×
1870
      handleRuntimeError(
×
1871
        context,
1872
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1873
      );
1874
    }
1875

1876
    const result = Math.sqrt(num);
×
1877
    return { type: "number", value: result };
×
1878
  }
1879

1880
  @Validate(2, null, "max", true)
1881
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1882
    const numericTypes = ["bigint", "number"];
2✔
1883
    const firstType = args[0].type;
2✔
1884
    const isNumericValue = numericTypes.includes(firstType);
2✔
1885
    const isString = firstType === "string";
2✔
1886

1887
    for (let i = 1; i < args.length; i++) {
2✔
1888
      const t = args[i].type;
5✔
1889
      if (isNumericValue && !numericTypes.includes(t)) {
5!
1890
        handleRuntimeError(
×
1891
          context,
1892
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1893
        );
1894
      }
1895
      if (isString && t !== "string") {
5!
1896
        handleRuntimeError(
×
1897
          context,
1898
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1899
        );
1900
      }
1901
    }
1902

1903
    let useFloat = false;
2✔
1904
    if (isNumericValue) {
2✔
1905
      for (const arg of args) {
2✔
1906
        if (arg.type === "number") {
7!
1907
          useFloat = true;
×
1908
          break;
×
1909
        }
1910
      }
1911
    }
1912

1913
    let maxIndex = 0;
2✔
1914
    if (isNumericValue) {
2!
1915
      if (useFloat) {
2!
1916
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
1917
          handleRuntimeError(
×
1918
            context,
1919
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1920
          );
1921
        }
1922
        let maxVal: number = Number(args[0].value);
×
1923
        for (let i = 1; i < args.length; i++) {
×
1924
          const arg = args[i];
×
1925
          if (!isNumeric(arg)) {
×
1926
            handleRuntimeError(
×
1927
              context,
1928
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
1929
            );
1930
          }
1931
          const curr: number = Number(arg.value);
×
1932
          if (curr > maxVal) {
×
1933
            maxVal = curr;
×
1934
            maxIndex = i;
×
1935
          }
1936
        }
1937
      } else {
1938
        if (args[0].type !== "bigint") {
2!
1939
          handleRuntimeError(
×
1940
            context,
1941
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
1942
          );
1943
        }
1944
        let maxVal: bigint = args[0].value;
2✔
1945
        for (let i = 1; i < args.length; i++) {
2✔
1946
          const arg = args[i];
5✔
1947
          if (arg.type !== "bigint") {
5!
1948
            handleRuntimeError(
×
1949
              context,
1950
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
1951
            );
1952
          }
1953
          const curr: bigint = arg.value;
5✔
1954
          if (curr > maxVal) {
5✔
1955
            maxVal = curr;
5✔
1956
            maxIndex = i;
5✔
1957
          }
1958
        }
1959
      }
1960
    } else if (isString) {
×
1961
      if (args[0].type !== "string") {
×
1962
        handleRuntimeError(
×
1963
          context,
1964
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
1965
        );
1966
      }
1967
      let maxVal = args[0].value;
×
1968
      for (let i = 1; i < args.length; i++) {
×
1969
        const arg = args[i];
×
1970
        if (arg.type !== "string") {
×
1971
          handleRuntimeError(
×
1972
            context,
1973
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
1974
          );
1975
        }
1976
        const curr = arg.value;
×
1977
        if (curr > maxVal) {
×
1978
          maxVal = curr;
×
1979
          maxIndex = i;
×
1980
        }
1981
      }
1982
    } else {
1983
      // Won't happen
1984
      throw new Error(`max: unsupported type ${firstType}`);
×
1985
    }
1986

1987
    return args[maxIndex];
2✔
1988
  }
1989

1990
  @Validate(2, null, "min", true)
1991
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1992
    if (args.length < 2) {
×
1993
      handleRuntimeError(
×
1994
        context,
1995
        new MissingRequiredPositionalError(
1996
          source,
1997
          command as ExprNS.Expr,
1998
          "min",
1999
          Number(2),
2000
          args,
2001
          true,
2002
        ),
2003
      );
2004
    }
2005

2006
    const numericTypes = ["bigint", "number"];
×
2007
    const firstType = args[0].type;
×
2008
    const isNumericValue = numericTypes.includes(firstType);
×
2009
    const isString = firstType === "string";
×
2010

2011
    for (let i = 1; i < args.length; i++) {
×
2012
      const t = args[i].type;
×
2013
      if (isNumericValue && !numericTypes.includes(t)) {
×
2014
        handleRuntimeError(
×
2015
          context,
2016
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
2017
        );
2018
      }
2019
      if (isString && t !== "string") {
×
2020
        handleRuntimeError(
×
2021
          context,
2022
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
2023
        );
2024
      }
2025
    }
2026

2027
    let useFloat = false;
×
2028
    if (isNumericValue) {
×
2029
      for (const arg of args) {
×
2030
        if (arg.type === "number") {
×
2031
          useFloat = true;
×
2032
          break;
×
2033
        }
2034
      }
2035
    }
2036

2037
    let maxIndex = 0;
×
2038
    if (isNumericValue) {
×
2039
      if (useFloat) {
×
2040
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
2041
          handleRuntimeError(
×
2042
            context,
2043
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
2044
          );
2045
        }
2046
        let maxVal: number = Number(args[0].value);
×
2047
        for (let i = 1; i < args.length; i++) {
×
2048
          const arg = args[i];
×
2049
          if (!isNumeric(arg)) {
×
2050
            handleRuntimeError(
×
2051
              context,
2052
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
2053
            );
2054
          }
2055
          const curr: number = Number(arg.value);
×
2056
          if (curr < maxVal) {
×
2057
            maxVal = curr;
×
2058
            maxIndex = i;
×
2059
          }
2060
        }
2061
      } else {
2062
        if (args[0].type !== "bigint") {
×
2063
          handleRuntimeError(
×
2064
            context,
2065
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
2066
          );
2067
        }
2068
        let maxVal: bigint = args[0].value;
×
2069
        for (let i = 1; i < args.length; i++) {
×
2070
          const arg = args[i];
×
2071
          if (arg.type !== "bigint") {
×
2072
            handleRuntimeError(
×
2073
              context,
2074
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
2075
            );
2076
          }
2077
          const curr: bigint = arg.value;
×
2078
          if (curr < maxVal) {
×
2079
            maxVal = curr;
×
2080
            maxIndex = i;
×
2081
          }
2082
        }
2083
      }
2084
    } else if (isString) {
×
2085
      if (args[0].type !== "string") {
×
2086
        handleRuntimeError(
×
2087
          context,
2088
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
2089
        );
2090
      }
2091
      let maxVal = args[0].value;
×
2092
      for (let i = 1; i < args.length; i++) {
×
2093
        const arg = args[i];
×
2094
        if (arg.type !== "string") {
×
2095
          handleRuntimeError(
×
2096
            context,
2097
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
2098
          );
2099
        }
2100
        const curr = arg.value;
×
2101
        if (curr < maxVal) {
×
2102
          maxVal = curr;
×
2103
          maxIndex = i;
×
2104
        }
2105
      }
2106
    } else {
2107
      // Won't happen
2108
      throw new Error(`min: unsupported type ${firstType}`);
×
2109
    }
2110

2111
    return args[maxIndex];
×
2112
  }
2113

2114
  @Validate(null, 0, "random_random", true)
2115
  static random_random(
6✔
2116
    _args: Value[],
2117
    _source: string,
2118
    _command: ControlItem,
2119
    _context: Context,
2120
  ): NumberValue {
2121
    const result = Math.random();
×
2122
    return { type: "number", value: result };
×
2123
  }
2124

2125
  @Validate(1, 2, "round", true)
2126
  static round(
6✔
2127
    args: Value[],
2128
    source: string,
2129
    command: ControlItem,
2130
    context: Context,
2131
  ): NumberValue | BigIntValue {
2132
    const numArg = args[0];
15✔
2133
    if (!isNumeric(numArg)) {
15✔
2134
      handleRuntimeError(
2✔
2135
        context,
2136
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
2137
      );
2138
    }
2139

2140
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
2141
    if (args.length === 2 && args[1].type !== "none") {
13✔
2142
      if (args[1].type !== "bigint") {
7✔
2143
        handleRuntimeError(
1✔
2144
          context,
2145
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
2146
        );
2147
      }
2148
      ndigitsArg = args[1];
6✔
2149
    } else {
2150
      const shifted = Intl.NumberFormat("en-US", {
6✔
2151
        roundingMode: "halfEven",
2152
        useGrouping: false,
2153
        maximumFractionDigits: 0,
2154
      } as Intl.NumberFormatOptions).format(numArg.value);
2155
      return { type: "bigint", value: BigInt(shifted) };
6✔
2156
    }
2157

2158
    if (numArg.type === "number") {
6✔
2159
      const numberValue: number = numArg.value;
4✔
2160
      if (ndigitsArg.value >= 0) {
4✔
2161
        const shifted = Intl.NumberFormat("en-US", {
3✔
2162
          roundingMode: "halfEven",
2163
          useGrouping: false,
2164
          maximumFractionDigits: Number(ndigitsArg.value),
2165
        } as Intl.NumberFormatOptions).format(numberValue);
2166
        return { type: "number", value: Number(shifted) };
3✔
2167
      } else {
2168
        const shifted = Intl.NumberFormat("en-US", {
1✔
2169
          roundingMode: "halfEven",
2170
          useGrouping: false,
2171
          maximumFractionDigits: 0,
2172
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
2173
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
2174
      }
2175
    } else {
2176
      if (ndigitsArg.value >= 0) {
2!
2177
        return numArg;
2✔
2178
      } else {
2179
        const shifted = Intl.NumberFormat("en-US", {
×
2180
          roundingMode: "halfEven",
2181
          useGrouping: false,
2182
          maximumFractionDigits: 0,
2183
        } as Intl.NumberFormatOptions).format(
2184
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
2185
        );
2186
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
2187
      }
2188
    }
2189
  }
2190

2191
  @Validate(null, 0, "time_time", true)
2192
  static time_time(
6✔
2193
    _args: Value[],
2194
    _source: string,
2195
    _command: ControlItem,
2196
    _context: Context,
2197
  ): NumberValue {
2198
    const currentTime = Date.now();
×
2199
    return { type: "number", value: currentTime };
×
2200
  }
2201

2202
  @Validate(1, 1, "is_none", true)
2203
  static is_none(
6✔
2204
    args: Value[],
2205
    _source: string,
2206
    _command: ControlItem,
2207
    _context: Context,
2208
  ): BoolValue {
2209
    const obj = args[0];
365✔
2210
    return { type: "bool", value: obj.type === "none" };
365✔
2211
  }
2212

2213
  @Validate(1, 1, "is_float", true)
2214
  static is_float(
6✔
2215
    args: Value[],
2216
    _source: string,
2217
    _command: ControlItem,
2218
    _context: Context,
2219
  ): BoolValue {
2220
    const obj = args[0];
9✔
2221
    return { type: "bool", value: obj.type === "number" };
9✔
2222
  }
2223

2224
  @Validate(1, 1, "is_string", true)
2225
  static is_string(
6✔
2226
    args: Value[],
2227
    _source: string,
2228
    _command: ControlItem,
2229
    _context: Context,
2230
  ): BoolValue {
2231
    const obj = args[0];
18✔
2232
    return { type: "bool", value: obj.type === "string" };
18✔
2233
  }
2234

2235
  @Validate(1, 1, "is_boolean", true)
2236
  static is_boolean(
6✔
2237
    args: Value[],
2238
    _source: string,
2239
    _command: ControlItem,
2240
    _context: Context,
2241
  ): BoolValue {
2242
    const obj = args[0];
10✔
2243
    return { type: "bool", value: obj.type === "bool" };
10✔
2244
  }
2245

2246
  @Validate(1, 1, "is_complex", true)
2247
  static is_complex(
6✔
2248
    args: Value[],
2249
    _source: string,
2250
    _command: ControlItem,
2251
    _context: Context,
2252
  ): BoolValue {
2253
    const obj = args[0];
×
2254
    return { type: "bool", value: obj.type === "complex" };
×
2255
  }
2256

2257
  @Validate(1, 1, "is_int", true)
2258
  static is_int(
6✔
2259
    args: Value[],
2260
    _source: string,
2261
    _command: ControlItem,
2262
    _context: Context,
2263
  ): BoolValue {
2264
    const obj = args[0];
155✔
2265
    return { type: "bool", value: obj.type === "bigint" };
155✔
2266
  }
2267

2268
  @Validate(1, 1, "is_function", true)
2269
  static is_function(
6✔
2270
    args: Value[],
2271
    _source: string,
2272
    _command: ControlItem,
2273
    _context: Context,
2274
  ): BoolValue {
2275
    const obj = args[0];
20✔
2276
    return {
20✔
2277
      type: "bool",
2278
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
58✔
2279
    };
2280
  }
2281

2282
  static async input(
2283
    _args: Value[],
2284
    _source: string,
2285
    _command: ControlItem,
2286
    context: Context,
2287
  ): Promise<Value> {
2288
    const userInput = await receiveInput(context);
×
2289
    return { type: "string", value: userInput };
×
2290
  }
2291

2292
  static async print(
2293
    args: Value[],
2294
    _source: string,
2295
    _command: ControlItem,
2296
    context: Context,
2297
  ): Promise<Value> {
2298
    const output = args.map(arg => toPythonString(arg)).join(" ");
3✔
2299
    await displayOutput(context, output);
3✔
2300
    return { type: "none" };
3✔
2301
  }
2302
  static str(
2303
    args: Value[],
2304
    _source: string,
2305
    _command: ControlItem,
2306
    _context: Context,
2307
  ): StringValue {
2308
    if (args.length === 0) {
13!
2309
      return { type: "string", value: "" };
×
2310
    }
2311
    const obj = args[0];
13✔
2312
    const result = toPythonString(obj);
13✔
2313
    return { type: "string", value: result };
13✔
2314
  }
2315
  @Validate(1, 1, "repr", true)
2316
  static repr(
6✔
2317
    args: Value[],
2318
    _source: string,
2319
    _command: ControlItem,
2320
    _context: Context,
2321
  ): StringValue {
2322
    const obj = args[0];
11✔
2323
    const result = toPythonString(obj, true);
11✔
2324
    return { type: "string", value: result };
11✔
2325
  }
2326
}
2327

2328
import { ExprNS } from "./ast-types";
2329
import { isFalsy } from "./cse-machine/operators";
6✔
2330
import { isNumeric } from "./cse-machine/utils";
6✔
2331
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2332
import { PyComplexNumber } from "./types";
6✔
2333

2334
// NOTE: If we ever switch to another Python “chapter” (e.g. py_s2_constants),
2335
//       just change the variable below to switch to the set.
2336
const constants = py_s1_constants;
6✔
2337

2338
/*
2339
    Create a map to hold built-in constants.
2340
    Each constant is stored with a string key and its corresponding value object.
2341
*/
2342
export const builtInConstants = new Map<string, Value>();
6✔
2343

2344
const constantMap = {
6✔
2345
  math_e: { type: "number", value: Math.E },
2346
  math_inf: { type: "number", value: Infinity },
2347
  math_nan: { type: "number", value: NaN },
2348
  math_pi: { type: "number", value: Math.PI },
2349
  math_tau: { type: "number", value: 2 * Math.PI },
2350
} as const;
2351

2352
for (const name of constants.constants) {
6✔
2353
  const valueObj = constantMap[name as keyof typeof constantMap];
30✔
2354
  if (!valueObj) {
30!
2355
    throw new Error(`Constant '${name}' is not implemented`);
×
2356
  }
2357
  builtInConstants.set(name, valueObj);
30✔
2358
}
2359

2360
/*
2361
    Create a map to hold built-in functions.
2362
    The keys are strings (function names) and the values are functions that can take any arguments.
2363
*/
2364
export const builtIns = new Map<string, BuiltinValue>();
6✔
2365
for (const name of constants.builtInFuncs) {
6✔
2366
  const impl = BuiltInFunctions[name as keyof BuiltInFunctions];
450✔
2367
  if (typeof impl !== "function") {
450!
2368
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
2369
  }
2370
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
450!
2371
  builtIns.set(name, {
450✔
2372
    type: "builtin",
2373
    name: builtinName,
2374
    func: impl,
2375
    minArgs: minArgMap.get(name) || 0,
522✔
2376
  });
2377
}
2378

2379
/**
2380
 * Converts a number to a string that mimics Python's float formatting behavior.
2381
 *
2382
 * In Python, float values are printed in scientific notation when their absolute value
2383
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
2384
 * so we explicitly enforce these formatting thresholds.
2385
 *
2386
 * The logic here is based on Python's internal `format_float_short` implementation
2387
 * in CPython's `pystrtod.c`:
2388
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
2389
 *
2390
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
2391
 * output matches Python’s display conventions.
2392
 */
2393
export function toPythonFloat(num: number): string {
6✔
2394
  if (Object.is(num, -0)) {
2!
2395
    return "-0.0";
×
2396
  }
2397
  if (num === 0) {
2!
2398
    return "0.0";
×
2399
  }
2400

2401
  if (num === Infinity) {
2!
2402
    return "inf";
×
2403
  }
2404
  if (num === -Infinity) {
2!
2405
    return "-inf";
×
2406
  }
2407

2408
  if (Number.isNaN(num)) {
2!
2409
    return "nan";
×
2410
  }
2411

2412
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
2413
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2414
  }
2415
  if (Number.isInteger(num)) {
2!
2416
    return num.toFixed(1).toString();
×
2417
  }
2418
  return num.toString();
2✔
2419
}
2420
function escape(str: string): string {
2421
  let escaped = JSON.stringify(str);
4✔
2422
  if (!(str.includes("'") && !str.includes('"'))) {
4✔
2423
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
4✔
2424
  }
2425
  return escaped;
4✔
2426
}
2427
export function toPythonString(obj: Value, repr: boolean = false): string {
6✔
2428
  let ret: string = "";
1,713✔
2429
  if (obj.type == "builtin") {
1,713!
2430
    return `<built-in function ${obj.name}>`;
×
2431
  }
2432
  if (obj.type === "bigint" || obj.type === "complex") {
1,713✔
2433
    ret = obj.value.toString();
27✔
2434
  } else if (obj.type === "number") {
1,686✔
2435
    ret = toPythonFloat(obj.value);
2✔
2436
  } else if (obj.type === "bool") {
1,684✔
2437
    if (obj.value) {
2!
2438
      return "True";
2✔
2439
    } else {
2440
      return "False";
×
2441
    }
2442
  } else if (obj.type === "error") {
1,682!
2443
    return obj.message;
×
2444
  } else if (obj.type === "closure") {
1,682✔
2445
    if (obj.closure.node) {
2✔
2446
      const funcName =
2447
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2448
      return `<function ${funcName}>`;
2✔
2449
    }
2450
  } else if (obj.type === "none") {
1,680✔
2451
    ret = "None";
1,670✔
2452
  } else if (obj.type === "string") {
10!
2453
    ret = repr ? escape(obj.value) : obj.value;
10✔
2454
  } else if (obj.type === "function") {
×
2455
    const funcName = obj.name || "(anonymous)";
×
2456
    ret = `<function ${funcName}>`;
×
2457
  } else if (obj.type === "list") {
×
2458
    ret = `[${obj.value.map(v => toPythonString(v, true)).join(", ")}]`;
×
2459
  } else {
2460
    ret = `<${obj.type} object>`;
×
2461
  }
2462
  return ret;
1,709✔
2463
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc