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

source-academy / py-slang / 23832291996

01 Apr 2026 04:42AM UTC coverage: 63.563% (+3.0%) from 60.609%
23832291996

Pull #129

github

web-flow
Merge 45ac5b0d5 into e4df6e2f4
Pull Request #129: Add new standard library functions

794 of 1459 branches covered (54.42%)

Branch coverage included in aggregate %.

79 of 119 new or added lines in 9 files covered. (66.39%)

4 existing lines in 3 files now uncovered.

2599 of 3879 relevant lines covered (67.0%)

3524.74 hits per line

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

28.37
/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,100✔
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,095✔
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,093✔
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) {
14!
103
      return { type: "bigint", value: BigInt(0) };
×
104
    }
105
    const arg = args[0];
14✔
106
    if (!isNumeric(arg) && arg.type !== "string" && arg.type !== "bool") {
14✔
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) {
10✔
114
      if (arg.type === "number") {
10✔
115
        const truncated = Math.trunc(arg.value);
2✔
116
        return { type: "bigint", value: BigInt(truncated) };
2✔
117
      }
118
      if (arg.type === "bigint") {
8✔
119
        return { type: "bigint", value: arg.value };
1✔
120
      }
121
      if (arg.type === "string") {
7✔
122
        const str = arg.value.trim().replace(/_/g, "");
6✔
123
        if (!/^[+-]?\d+$/.test(str)) {
6✔
124
          handleRuntimeError(
3✔
125
            context,
126
            new ValueError(source, command as ExprNS.Expr, context, "int"),
127
          );
128
        }
129
        return { type: "bigint", value: BigInt(str) };
3✔
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
    const parsed = parseInt(str, base);
×
NEW
181
    return { type: "bigint", value: BigInt(sign * parsed) };
×
182
  }
183

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

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

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

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

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

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

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

347
  static toStr(val: Value): string {
348
    return toPythonString(val);
2✔
349
  }
350

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

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

371
    let num: number;
372
    if (x.type === "number") {
×
373
      num = x.value;
×
374
    } else {
375
      num = Number(x.value);
×
376
    }
377

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

385
    const result = Math.acos(num);
×
386
    return { type: "number", value: result };
×
387
  }
388

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

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

405
    let num: number;
406
    if (x.type === "number") {
×
407
      num = x.value;
×
408
    } else {
409
      num = Number(x.value);
×
410
    }
411

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

419
    const result = Math.acosh(num);
×
420
    return { type: "number", value: result };
×
421
  }
422

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

438
    let num: number;
439
    if (x.type === "number") {
×
440
      num = x.value;
×
441
    } else {
442
      num = Number(x.value);
×
443
    }
444

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

452
    const result = Math.asin(num);
×
453
    return { type: "number", value: result };
×
454
  }
455

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

471
    let num: number;
472
    if (x.type === "number") {
×
473
      num = x.value;
×
474
    } else {
475
      num = Number(x.value);
×
476
    }
477

478
    const result = Math.asinh(num);
×
479
    return { type: "number", value: result };
×
480
  }
481

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

497
    let num: number;
498
    if (x.type === "number") {
×
499
      num = x.value;
×
500
    } else {
501
      num = Number(x.value);
×
502
    }
503

504
    const result = Math.atan(num);
×
505
    return { type: "number", value: result };
×
506
  }
507

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

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

536
    if (x.type === "number") {
×
537
      xNum = x.value;
×
538
    } else {
539
      xNum = Number(x.value);
×
540
    }
541

542
    const result = Math.atan2(yNum, xNum);
×
543
    return { type: "number", value: result };
×
544
  }
545

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

561
    let num: number;
562
    if (x.type === "number") {
×
563
      num = x.value;
×
564
    } else {
565
      num = Number(x.value);
×
566
    }
567

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

575
    const result = Math.atanh(num);
×
576
    return { type: "number", value: result };
×
577
  }
578

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

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

601
    const result = Math.cos(num);
4✔
602
    return { type: "number", value: result };
4✔
603
  }
604

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

620
    let num: number;
621
    if (x.type === "number") {
×
622
      num = x.value;
×
623
    } else {
624
      num = Number(x.value);
×
625
    }
626

627
    const result = Math.cosh(num);
×
628
    return { type: "number", value: result };
×
629
  }
630

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

646
    let num: number;
647
    if (x.type === "number") {
×
648
      num = x.value;
×
649
    } else {
650
      num = Number(x.value);
×
651
    }
652

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

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

672
    let num: number;
673
    if (x.type === "number") {
×
674
      num = x.value;
×
675
    } else {
676
      num = Number(x.value);
×
677
    }
678

679
    const erfnum = erf(num);
×
680

681
    return { type: "number", value: erfnum };
×
682
  }
683

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

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

701
    return { type: "number", value: erfc };
×
702
  }
703

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

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

726
    const nVal = BigInt(n.value);
×
727
    const kVal = BigInt(k.value);
×
728

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

736
    if (kVal > nVal) {
×
737
      return { type: "bigint", value: BigInt(0) };
×
738
    }
739

740
    let result: bigint = BigInt(1);
×
741
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
742

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

747
    return { type: "bigint", value: result };
×
748
  }
749

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

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

766
    const nVal = BigInt(n.value);
×
767

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

775
    // 0! = 1
776
    if (nVal === BigInt(0)) {
×
777
      return { type: "bigint", value: BigInt(1) };
×
778
    }
779

780
    let result: bigint = BigInt(1);
×
781
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
782
      result *= i;
×
783
    }
784

785
    return { type: "bigint", value: result };
×
786
  }
787

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

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

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

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

821
    return { type: "bigint", value: currentGcd };
×
822
  }
823

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

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

850
    const n: bigint = nValObj.value;
×
851

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

859
    if (n < 2) {
×
860
      return { type: "bigint", value: n };
×
861
    }
862

863
    let low: bigint = BigInt(1);
×
864
    let high: bigint = n;
×
865

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

876
    return { type: "bigint", value: low };
×
877
  }
878

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

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

899
    if (values.some(v => v === BigInt(0))) {
×
900
      return { type: "bigint", value: BigInt(0) };
×
901
    }
902

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

911
    return { type: "bigint", value: currentLcm };
×
912
  }
913

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

919
  static absBigInt(x: bigint): bigint {
920
    return x < 0 ? -x : x;
×
921
  }
922

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

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

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

961
    if (k > n) {
×
962
      return { type: "bigint", value: BigInt(0) };
×
963
    }
964

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

970
    return { type: "bigint", value: result };
×
971
  }
972

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

982
    if (x.type === "bigint") {
×
983
      return x;
×
984
    }
985

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

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

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

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

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

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

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

1040
    if (x.type === "bigint") {
×
1041
      return x;
×
1042
    }
1043

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

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

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

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

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

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

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

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

1126
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1127
    return { type: "number", value: result };
×
1128
  }
1129

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

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

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

1150
    return { type: "number", value: remainder };
×
1151
  }
1152

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

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

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

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

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

1209
    const quotient = xValue / yValue;
×
1210
    const n = BuiltInFunctions.roundToEven(quotient);
×
1211
    const remainder = xValue - n * yValue;
×
1212

1213
    return { type: "number", value: remainder };
×
1214
  }
1215

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

1220
    if (x.type === "bigint") {
×
1221
      return x;
×
1222
    }
1223

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

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

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

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

1270
    const xVal = Number(x.value);
×
1271
    const yVal = Number(y.value);
×
1272

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

1277
    return { type: "number", value: Number(result) };
×
1278
  }
1279

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

1295
    const x = Number(xValObj.value);
×
1296
    const result: boolean = Number.isFinite(x);
×
1297

1298
    return { type: "bool", value: result };
×
1299
  }
1300

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

1316
    const x = Number(xValObj.value);
×
1317
    const result: boolean = x === Infinity || x === -Infinity;
×
1318

1319
    return { type: "bool", value: result };
×
1320
  }
1321

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

1337
    const x = Number(xValObj.value);
×
1338
    const result: boolean = Number.isNaN(x);
×
1339

1340
    return { type: "bool", value: result };
×
1341
  }
1342

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

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

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

1365
    return { type: "number", value: result };
×
1366
  }
1367

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

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

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

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

1413
    const result = Math.cbrt(x);
×
1414

1415
    return { type: "number", value: result };
×
1416
  }
1417

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

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

1441
    const result = Math.exp(x);
×
1442
    return { type: "number", value: result };
×
1443
  }
1444

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

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

1468
    const result = Math.pow(2, x);
×
1469
    return { type: "number", value: result };
×
1470
  }
1471

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

1487
    let num: number;
1488
    if (x.type === "number") {
×
1489
      num = x.value;
×
1490
    } else {
1491
      num = Number(x.value);
×
1492
    }
1493

1494
    const result = Math.expm1(num);
×
1495
    return { type: "number", value: result };
×
1496
  }
1497

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

1513
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1514
    const result = gamma(z);
×
1515

1516
    return { type: "number", value: result };
×
1517
  }
1518

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

1534
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1535
    const result = lgamma(z);
×
1536

1537
    return { type: "number", value: result };
×
1538
  }
1539

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

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

1568
    if (args.length === 1) {
×
1569
      return { type: "number", value: Math.log(num) };
×
1570
    }
1571

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

1592
    const result = Math.log(num) / Math.log(baseNum);
×
1593
    return { type: "number", value: result };
×
1594
  }
1595

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

1623
    const result = Math.log10(num);
×
1624
    return { type: "number", value: result };
×
1625
  }
1626

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

1654
    const result = Math.log1p(num);
×
1655
    return { type: "number", value: result };
×
1656
  }
1657

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

1685
    const result = Math.log2(num);
×
1686
    return { type: "number", value: result };
×
1687
  }
1688

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

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

1711
    let baseNum: number;
1712
    if (base.type === "number") {
×
1713
      baseNum = base.value;
×
1714
    } else {
1715
      baseNum = Number(base.value);
×
1716
    }
1717

1718
    let expNum: number;
1719
    if (exp.type === "number") {
×
1720
      expNum = exp.value;
×
1721
    } else {
1722
      expNum = Number(exp.value);
×
1723
    }
1724

1725
    const result = Math.pow(baseNum, expNum);
×
1726
    return { type: "number", value: result };
×
1727
  }
1728

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

1744
    let deg: number;
1745
    if (x.type === "number") {
×
1746
      deg = x.value;
×
1747
    } else {
1748
      deg = Number(x.value);
×
1749
    }
1750

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

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

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

1772
    const result = Math.sin(num);
4✔
1773
    return { type: "number", value: result };
4✔
1774
  }
1775

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

1786
    let num: number;
1787
    if (x.type === "number") {
×
1788
      num = x.value;
×
1789
    } else {
1790
      num = Number(x.value);
×
1791
    }
1792

1793
    const result = Math.sinh(num);
×
1794
    return { type: "number", value: result };
×
1795
  }
1796

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

1807
    let num: number;
1808
    if (x.type === "number") {
×
1809
      num = x.value;
×
1810
    } else {
1811
      num = Number(x.value);
×
1812
    }
1813

1814
    const result = Math.tan(num);
×
1815
    return { type: "number", value: result };
×
1816
  }
1817

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

1833
    let num: number;
1834
    if (x.type === "number") {
×
1835
      num = x.value;
×
1836
    } else {
1837
      num = Number(x.value);
×
1838
    }
1839

1840
    const result = Math.tanh(num);
×
1841
    return { type: "number", value: result };
×
1842
  }
1843

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

1859
    let num: number;
1860
    if (x.type === "number") {
×
1861
      num = x.value;
×
1862
    } else {
1863
      num = Number(x.value);
×
1864
    }
1865

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

1873
    const result = Math.sqrt(num);
×
1874
    return { type: "number", value: result };
×
1875
  }
1876

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

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

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

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

1984
    return args[maxIndex];
2✔
1985
  }
1986

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

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

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

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

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

2108
    return args[maxIndex];
×
2109
  }
2110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2398
  if (num === Infinity) {
2!
2399
    return "inf";
×
2400
  }
2401
  if (num === -Infinity) {
2!
2402
    return "-inf";
×
2403
  }
2404

2405
  if (Number.isNaN(num)) {
2!
2406
    return "nan";
×
2407
  }
2408

2409
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
2410
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2411
  }
2412
  if (Number.isInteger(num)) {
2!
2413
    return num.toFixed(1).toString();
×
2414
  }
2415
  return num.toString();
2✔
2416
}
2417
function escape(str: string): string {
2418
  let escaped = JSON.stringify(str);
4✔
2419
  if (!(str.includes("'") && !str.includes('"'))) {
4✔
2420
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
4✔
2421
  }
2422
  return escaped;
4✔
2423
}
2424
export function toPythonString(obj: Value, repr: boolean = false): string {
6✔
2425
  let ret: string = "";
1,703✔
2426
  if (obj.type == "builtin") {
1,703!
2427
    return `<built-in function ${obj.name}>`;
×
2428
  }
2429
  if (obj.type === "bigint" || obj.type === "complex") {
1,703✔
2430
    ret = obj.value.toString();
25✔
2431
  } else if (obj.type === "number") {
1,678✔
2432
    ret = toPythonFloat(obj.value);
2✔
2433
  } else if (obj.type === "bool") {
1,676✔
2434
    if (obj.value) {
2!
2435
      return "True";
2✔
2436
    } else {
2437
      return "False";
×
2438
    }
2439
  } else if (obj.type === "error") {
1,674!
2440
    return obj.message;
×
2441
  } else if (obj.type === "closure") {
1,674✔
2442
    if (obj.closure.node) {
2✔
2443
      const funcName =
2444
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2445
      return `<function ${funcName}>`;
2✔
2446
    }
2447
  } else if (obj.type === "none") {
1,672✔
2448
    ret = "None";
1,662✔
2449
  } else if (obj.type === "string") {
10!
2450
    ret = repr ? escape(obj.value) : obj.value;
10✔
2451
  } else if (obj.type === "function") {
×
2452
    const funcName = obj.name || "(anonymous)";
×
2453
    ret = `<function ${funcName}>`;
×
2454
  } else if (obj.type === "list") {
×
2455
    ret = `[${obj.value.map(v => toPythonString(v, true)).join(", ")}]`;
×
2456
  } else {
2457
    ret = `<${obj.type} object>`;
×
2458
  }
2459
  return ret;
1,699✔
2460
}
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