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

source-academy / py-slang / 23611698139

26 Mar 2026 06:36PM UTC coverage: 58.635% (+17.4%) from 41.233%
23611698139

Pull #81

github

web-flow
Merge d03813453 into 553cb45dc
Pull Request #81: Python chapter 2 (+ 3?) support

792 of 1641 branches covered (48.26%)

Branch coverage included in aggregate %.

428 of 596 new or added lines in 33 files covered. (71.81%)

17 existing lines in 4 files now uncovered.

2396 of 3796 relevant lines covered (63.12%)

3054.3 hits per line

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

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

23
export function Validate<T extends Value | Promise<Value>>(
6✔
24
  minArgs: number | null,
25
  maxArgs: number | null,
26
  functionName: string,
27
  strict: boolean,
28
) {
29
  return function (
390✔
30
    _target: unknown,
31
    _propertyKey: string,
32
    descriptor: TypedPropertyDescriptor<
33
      (args: Value[], source: string, command: ControlItem, context: Context) => T
34
    >,
35
  ): void {
36
    const originalMethod = descriptor.value!;
390✔
37

38
    descriptor.value = function (
390✔
39
      args: Value[],
40
      source: string,
41
      command: ControlItem,
42
      context: Context,
43
    ): T {
44
      if (minArgs !== null && args.length < minArgs) {
1,884✔
45
        handleRuntimeError(
5✔
46
          context,
47
          new MissingRequiredPositionalError(
48
            source,
49
            command as ExprNS.Expr,
50
            functionName,
51
            minArgs,
52
            args,
53
            strict,
54
          ),
55
        );
56
      }
57

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

72
      return originalMethod.call(this, args, source, command, context);
1,877✔
73
    };
74
  };
75
}
76

77
export class BuiltInFunctions {
6✔
78
  @Validate(null, 1, "_int", true)
79
  static _int(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
80
    if (args.length === 0) {
×
81
      return { type: "bigint", value: BigInt(0) };
×
82
    }
83

84
    const arg = args[0];
×
85
    if (args.length === 1) {
×
86
      if (arg.type === "number") {
×
87
        const truncated = Math.trunc(arg.value);
×
88
        return { type: "bigint", value: BigInt(truncated) };
×
89
      }
90
      if (arg.type === "bigint") {
×
91
        return { type: "bigint", value: arg.value };
×
92
      }
93
      if (arg.type === "string") {
×
94
        const str = arg.value.trim().replace(/_/g, "");
×
95
        if (!/^[+-]?\d+$/.test(str)) {
×
96
          handleRuntimeError(
×
97
            context,
98
            new ValueError(source, command as ExprNS.Expr, context, "int"),
99
          );
100
        }
101
        return { type: "bigint", value: BigInt(str) };
×
102
      }
103
    } else if (args.length === 2) {
×
104
      const baseArg = args[1];
×
105
      if (arg.type !== "string") {
×
106
        handleRuntimeError(
×
107
          context,
108
          new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
109
        );
110
      }
111
      if (baseArg.type !== "bigint") {
×
112
        handleRuntimeError(
×
113
          context,
114
          new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
115
        );
116
      }
117

118
      let base = Number(baseArg.value);
×
119
      let str = arg.value.trim().replace(/_/g, "");
×
120

121
      const sign = str.startsWith("-") ? -1 : 1;
×
122
      if (str.startsWith("+") || str.startsWith("-")) {
×
123
        str = str.substring(1);
×
124
      }
125

126
      if (base === 0) {
×
127
        if (str.startsWith("0x") || str.startsWith("0X")) {
×
128
          base = 16;
×
129
          str = str.substring(2);
×
130
        } else if (str.startsWith("0o") || str.startsWith("0O")) {
×
131
          base = 8;
×
132
          str = str.substring(2);
×
133
        } else if (str.startsWith("0b") || str.startsWith("0B")) {
×
134
          base = 2;
×
135
          str = str.substring(2);
×
136
        } else {
137
          base = 10;
×
138
        }
139
      }
140

141
      if (base < 2 || base > 36) {
×
142
        handleRuntimeError(
×
143
          context,
144
          new ValueError(source, command as ExprNS.Expr, context, "float' or 'int"),
145
        );
146
      }
147

148
      const validChars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base);
×
149
      const regex = new RegExp(`^[${validChars}]+$`, "i");
×
150
      if (!regex.test(str)) {
×
151
        handleRuntimeError(
×
152
          context,
153
          new ValueError(source, command as ExprNS.Expr, context, "float' or 'int"),
154
        );
155
      }
156

157
      const parsed = parseInt(str, base);
×
158
      return { type: "bigint", value: BigInt(sign * parsed) };
×
159
    }
160
    handleRuntimeError(
×
161
      context,
162
      new TypeError(
163
        source,
164
        command as ExprNS.Expr,
165
        context,
166
        arg.type,
167
        "string, a bytes-like object or a real number",
168
      ),
169
    );
170
  }
171

172
  @Validate(1, 2, "_int_from_string", true)
173
  static _int_from_string(
6✔
174
    args: Value[],
175
    source: string,
176
    command: ControlItem,
177
    context: Context,
178
  ): BigIntValue {
179
    const strVal = args[0];
×
180
    if (strVal.type !== "string") {
×
181
      handleRuntimeError(
×
182
        context,
183
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
184
      );
185
    }
186

187
    let base: number = 10;
×
188
    if (args.length === 2) {
×
189
      // The second argument must be either a bigint or a number (it will be converted to a number for uniform processing).
190
      const baseVal = args[1];
×
191
      if (baseVal.type === "bigint") {
×
192
        base = Number(baseVal.value);
×
193
      } else {
194
        handleRuntimeError(
×
195
          context,
196
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "float' or 'int"),
197
        );
198
      }
199
    }
200

201
    // base should be in between 2 and 36
202
    if (base < 2 || base > 36) {
×
203
      handleRuntimeError(
×
204
        context,
205
        new ValueError(source, command as ExprNS.Expr, context, "_int_from_string"),
206
      );
207
    }
208

209
    let str = strVal.value;
×
210
    str = str.trim();
×
211
    str = str.replace(/_/g, "");
×
212

213
    // Parse the sign (determine if the value is positive or negative)
214
    let sign: bigint = BigInt(1);
×
215
    if (str.startsWith("+")) {
×
216
      str = str.slice(1);
×
217
    } else if (str.startsWith("-")) {
×
218
      sign = BigInt(-1);
×
219
      str = str.slice(1);
×
220
    }
221

222
    // The remaining portion must consist of valid characters for the specified base.
223
    const parsedNumber = parseInt(str, base);
×
224
    if (isNaN(parsedNumber)) {
×
225
      handleRuntimeError(
×
226
        context,
227
        new ValueError(source, command as ExprNS.Expr, context, "_int_from_string"),
228
      );
229
    }
230

231
    const result: bigint = sign * BigInt(parsedNumber);
×
232

233
    return { type: "bigint", value: result };
×
234
  }
235

236
  @Validate(1, 1, "abs", false)
237
  static abs(
6✔
238
    args: Value[],
239
    source: string,
240
    command: ControlItem,
241
    context: Context,
242
  ): BigIntValue | NumberValue {
243
    const x = args[0];
9✔
244
    switch (x.type) {
9!
245
      case "bigint": {
246
        const intVal = x.value;
5✔
247
        const result: bigint = intVal < 0 ? -intVal : intVal;
5✔
248
        return { type: "bigint", value: result };
5✔
249
      }
250
      case "number": {
251
        return { type: "number", value: Math.abs(x.value) };
2✔
252
      }
253
      case "complex": {
254
        // Calculate the modulus (absolute value) of a complex number.
255
        const real = x.value.real;
×
256
        const imag = x.value.imag;
×
257
        const modulus = Math.sqrt(real * real + imag * imag);
×
258
        return { type: "number", value: modulus };
×
259
      }
260
      default:
261
        handleRuntimeError(
2✔
262
          context,
263
          new TypeError(
264
            source,
265
            command as ExprNS.Expr,
266
            context,
267
            args[0].type,
268
            "float', 'int' or 'complex",
269
          ),
270
        );
271
    }
272
  }
273

274
  static toStr(val: Value): string {
275
    return toPythonString(val);
×
276
  }
277

278
  static error(args: Value[], _source: string, _command: ControlItem, _context: Context): Value {
279
    const output = "Error: " + args.map(arg => BuiltInFunctions.toStr(arg)).join(" ") + "\n";
×
280
    throw new Error(output);
×
281
  }
282

283
  @Validate(2, 2, "isinstance", false)
284
  static isinstance(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
285
    const obj = args[0];
×
286
    const classinfo = args[1];
×
287

288
    let expectedType: string;
289
    if (classinfo.type === "string") {
×
290
      switch (classinfo.value) {
×
291
        case "int":
292
          expectedType = "bigint";
×
293
          if (obj.type === "bool") {
×
294
            handleRuntimeError(
×
295
              context,
296
              new SublanguageError(
297
                source,
298
                command as ExprNS.Expr,
299
                context,
300
                "isinstance",
301
                "1",
302
                "Python §1 does not treat bool as a subtype of int",
303
              ),
304
            );
305
          }
306
          break;
×
307
        case "float":
308
          expectedType = "number";
×
309
          break;
×
310
        case "string":
311
          expectedType = "string";
×
312
          break;
×
313
        case "bool":
314
          expectedType = "bool";
×
315
          break;
×
316
        case "complex":
317
          expectedType = "complex";
×
318
          break;
×
319
        case "NoneType":
320
          expectedType = "NoneType";
×
321
          break;
×
322
        default:
323
          handleRuntimeError(
×
324
            context,
325
            new ValueError(source, command as ExprNS.Expr, context, "isinstance"),
326
          );
327
      }
328
    } else {
329
      handleRuntimeError(
×
330
        context,
331
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
332
      );
333
    }
334

335
    const result = obj.type === expectedType;
×
336

337
    return { type: "bool", value: result };
×
338
  }
339

340
  @Validate(1, 1, "math_acos", false)
341
  static math_acos(
6✔
342
    args: Value[],
343
    source: string,
344
    command: ControlItem,
345
    context: Context,
346
  ): NumberValue {
347
    const x = args[0];
×
348
    if (x.type !== "number" && x.type !== "bigint") {
×
349
      handleRuntimeError(
×
350
        context,
351
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
352
      );
353
    }
354

355
    let num: number;
356
    if (x.type === "number") {
×
357
      num = x.value;
×
358
    } else {
359
      num = Number(x.value);
×
360
    }
361

362
    if (num < -1 || num > 1) {
×
363
      handleRuntimeError(
×
364
        context,
365
        new ValueError(source, command as ExprNS.Expr, context, "math_acos"),
366
      );
367
    }
368

369
    const result = Math.acos(num);
×
370
    return { type: "number", value: result };
×
371
  }
372

373
  @Validate(1, 1, "math_acosh", false)
374
  static math_acosh(
6✔
375
    args: Value[],
376
    source: string,
377
    command: ControlItem,
378
    context: Context,
379
  ): NumberValue {
UNCOV
380
    const x = args[0];
×
381

382
    if (x.type !== "number" && x.type !== "bigint") {
×
383
      handleRuntimeError(
×
384
        context,
385
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
386
      );
387
    }
388

389
    let num: number;
390
    if (x.type === "number") {
×
391
      num = x.value;
×
392
    } else {
393
      num = Number(x.value);
×
394
    }
395

396
    if (num < 1) {
×
397
      handleRuntimeError(
×
398
        context,
399
        new ValueError(source, command as ExprNS.Expr, context, "math_acosh"),
400
      );
401
    }
402

403
    const result = Math.acosh(num);
×
404
    return { type: "number", value: result };
×
405
  }
406

407
  @Validate(1, 1, "math_asin", false)
408
  static math_asin(
6✔
409
    args: Value[],
410
    source: string,
411
    command: ControlItem,
412
    context: Context,
413
  ): NumberValue {
414
    const x = args[0];
×
415
    if (x.type !== "number" && x.type !== "bigint") {
×
416
      handleRuntimeError(
×
417
        context,
418
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
419
      );
420
    }
421

422
    let num: number;
423
    if (x.type === "number") {
×
424
      num = x.value;
×
425
    } else {
426
      num = Number(x.value);
×
427
    }
428

429
    if (num < -1 || num > 1) {
×
430
      handleRuntimeError(
×
431
        context,
432
        new ValueError(source, command as ExprNS.Expr, context, "math_asin"),
433
      );
434
    }
435

436
    const result = Math.asin(num);
×
437
    return { type: "number", value: result };
×
438
  }
439

440
  @Validate(1, 1, "math_asinh", false)
441
  static math_asinh(
6✔
442
    args: Value[],
443
    source: string,
444
    command: ControlItem,
445
    context: Context,
446
  ): NumberValue {
447
    const x = args[0];
×
448
    if (x.type !== "number" && x.type !== "bigint") {
×
449
      handleRuntimeError(
×
450
        context,
451
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
452
      );
453
    }
454

455
    let num: number;
456
    if (x.type === "number") {
×
457
      num = x.value;
×
458
    } else {
459
      num = Number(x.value);
×
460
    }
461

462
    const result = Math.asinh(num);
×
463
    return { type: "number", value: result };
×
464
  }
465

466
  @Validate(1, 1, "math_atan", false)
467
  static math_atan(
6✔
468
    args: Value[],
469
    source: string,
470
    command: ControlItem,
471
    context: Context,
472
  ): NumberValue {
473
    const x = args[0];
×
474
    if (x.type !== "number" && x.type !== "bigint") {
×
475
      handleRuntimeError(
×
476
        context,
477
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
478
      );
479
    }
480

481
    let num: number;
482
    if (x.type === "number") {
×
483
      num = x.value;
×
484
    } else {
485
      num = Number(x.value);
×
486
    }
487

488
    const result = Math.atan(num);
×
489
    return { type: "number", value: result };
×
490
  }
491

492
  @Validate(2, 2, "math_atan2", false)
493
  static math_atan2(
6✔
494
    args: Value[],
495
    source: string,
496
    command: ControlItem,
497
    context: Context,
498
  ): NumberValue {
499
    const y = args[0];
×
500
    const x = args[1];
×
501
    if (x.type !== "number" && x.type !== "bigint") {
×
502
      handleRuntimeError(
×
503
        context,
504
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
505
      );
506
    } else if (y.type !== "number" && y.type !== "bigint") {
×
507
      handleRuntimeError(
×
508
        context,
509
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
510
      );
511
    }
512

513
    let yNum: number, xNum: number;
514
    if (y.type === "number") {
×
515
      yNum = y.value;
×
516
    } else {
517
      yNum = Number(y.value);
×
518
    }
519

520
    if (x.type === "number") {
×
521
      xNum = x.value;
×
522
    } else {
523
      xNum = Number(x.value);
×
524
    }
525

526
    const result = Math.atan2(yNum, xNum);
×
527
    return { type: "number", value: result };
×
528
  }
529

530
  @Validate(1, 1, "math_atanh", false)
531
  static math_atanh(
6✔
532
    args: Value[],
533
    source: string,
534
    command: ControlItem,
535
    context: Context,
536
  ): NumberValue {
537
    const x = args[0];
×
538
    if (x.type !== "number" && x.type !== "bigint") {
×
539
      handleRuntimeError(
×
540
        context,
541
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
542
      );
543
    }
544

545
    let num: number;
546
    if (x.type === "number") {
×
547
      num = x.value;
×
548
    } else {
549
      num = Number(x.value);
×
550
    }
551

552
    if (num <= -1 || num >= 1) {
×
553
      handleRuntimeError(
×
554
        context,
555
        new ValueError(source, command as ExprNS.Expr, context, "math_atanh"),
556
      );
557
    }
558

559
    const result = Math.atanh(num);
×
560
    return { type: "number", value: result };
×
561
  }
562

563
  @Validate(1, 1, "math_cos", false)
564
  static math_cos(
6✔
565
    args: Value[],
566
    source: string,
567
    command: ControlItem,
568
    context: Context,
569
  ): NumberValue {
570
    const x = args[0];
7✔
571
    if (x.type !== "number" && x.type !== "bigint") {
7✔
572
      handleRuntimeError(
3✔
573
        context,
574
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
575
      );
576
    }
577

578
    let num: number;
579
    if (x.type === "number") {
4✔
580
      num = x.value;
3✔
581
    } else {
582
      num = Number(x.value);
1✔
583
    }
584

585
    const result = Math.cos(num);
4✔
586
    return { type: "number", value: result };
4✔
587
  }
588

589
  @Validate(1, 1, "math_cosh", false)
590
  static math_cosh(
6✔
591
    args: Value[],
592
    source: string,
593
    command: ControlItem,
594
    context: Context,
595
  ): NumberValue {
596
    const x = args[0];
×
597
    if (x.type !== "number" && x.type !== "bigint") {
×
598
      handleRuntimeError(
×
599
        context,
600
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
601
      );
602
    }
603

604
    let num: number;
605
    if (x.type === "number") {
×
606
      num = x.value;
×
607
    } else {
608
      num = Number(x.value);
×
609
    }
610

611
    const result = Math.cosh(num);
×
612
    return { type: "number", value: result };
×
613
  }
614

615
  @Validate(1, 1, "math_degrees", false)
616
  static math_degrees(
6✔
617
    args: Value[],
618
    source: string,
619
    command: ControlItem,
620
    context: Context,
621
  ): NumberValue {
622
    const x = args[0];
×
623
    if (x.type !== "number" && x.type !== "bigint") {
×
624
      handleRuntimeError(
×
625
        context,
626
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
627
      );
628
    }
629

630
    let num: number;
631
    if (x.type === "number") {
×
632
      num = x.value;
×
633
    } else {
634
      num = Number(x.value);
×
635
    }
636

637
    const result = (num * 180) / Math.PI;
×
638
    return { type: "number", value: result };
×
639
  }
640

641
  @Validate(1, 1, "math_erf", false)
642
  static math_erf(
6✔
643
    args: Value[],
644
    source: string,
645
    command: ControlItem,
646
    context: Context,
647
  ): NumberValue {
648
    const x = args[0];
×
649
    if (x.type !== "number" && x.type !== "bigint") {
×
650
      handleRuntimeError(
×
651
        context,
652
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
653
      );
654
    }
655

656
    let num: number;
657
    if (x.type === "number") {
×
658
      num = x.value;
×
659
    } else {
660
      num = Number(x.value);
×
661
    }
662

663
    const erfnum = erf(num);
×
664

665
    return { type: "number", value: erfnum };
×
666
  }
667

668
  @Validate(1, 1, "math_efc", false)
669
  static math_erfc(
6✔
670
    args: Value[],
671
    source: string,
672
    command: ControlItem,
673
    context: Context,
674
  ): NumberValue {
675
    const x = args[0];
×
676
    if (x.type !== "number" && x.type !== "bigint") {
×
677
      handleRuntimeError(
×
678
        context,
679
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
680
      );
681
    }
682

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

UNCOV
685
    return { type: "number", value: erfc };
×
686
  }
687

688
  @Validate(2, 2, "char_at", false)
689
  static char_at(
6✔
690
    args: Value[],
691
    source: string,
692
    command: ControlItem,
693
    context: Context,
694
  ): StringValue {
695
    const s = args[0];
×
696
    const i = args[1];
×
697

698
    if (s.type !== "string") {
×
699
      handleRuntimeError(
×
700
        context,
701
        new TypeError(source, command as ExprNS.Expr, context, s.type, "string"),
702
      );
703
    }
704
    if (i.type !== "number" && i.type !== "bigint") {
×
705
      handleRuntimeError(
×
706
        context,
707
        new TypeError(source, command as ExprNS.Expr, context, i.type, "float' or 'int"),
708
      );
709
    }
710

NEW
711
    const index = Number(i.value);
×
712

NEW
713
    return { type: "string", value: s.value[index] };
×
714
  }
715

716
  @Validate(2, 2, "math_comb", false)
717
  static math_comb(
6✔
718
    args: Value[],
719
    source: string,
720
    command: ControlItem,
721
    context: Context,
722
  ): BigIntValue {
723
    const n = args[0];
×
724
    const k = args[1];
×
725

726
    if (n.type !== "bigint") {
×
727
      handleRuntimeError(
×
728
        context,
729
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
730
      );
731
    } else if (k.type !== "bigint") {
×
732
      handleRuntimeError(
×
733
        context,
734
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
735
      );
736
    }
737

738
    const nVal = BigInt(n.value);
×
739
    const kVal = BigInt(k.value);
×
740

741
    if (nVal < 0 || kVal < 0) {
×
742
      handleRuntimeError(
×
743
        context,
744
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
745
      );
746
    }
747

748
    if (kVal > nVal) {
×
749
      return { type: "bigint", value: BigInt(0) };
×
750
    }
751

752
    let result: bigint = BigInt(1);
×
753
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
754

755
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
756
      result = (result * (nVal - i)) / (i + BigInt(1));
×
757
    }
758

759
    return { type: "bigint", value: result };
×
760
  }
761

762
  @Validate(1, 1, "math_factorial", false)
763
  static math_factorial(
6✔
764
    args: Value[],
765
    source: string,
766
    command: ControlItem,
767
    context: Context,
768
  ): BigIntValue {
769
    const n = args[0];
×
770

771
    if (n.type !== "bigint") {
×
772
      handleRuntimeError(
×
773
        context,
774
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
775
      );
776
    }
777

778
    const nVal = BigInt(n.value);
×
779

780
    if (nVal < 0) {
×
781
      handleRuntimeError(
×
782
        context,
783
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
784
      );
785
    }
786

787
    // 0! = 1
788
    if (nVal === BigInt(0)) {
×
789
      return { type: "bigint", value: BigInt(1) };
×
790
    }
791

792
    let result: bigint = BigInt(1);
×
793
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
794
      result *= i;
×
795
    }
796

797
    return { type: "bigint", value: result };
×
798
  }
799

800
  static math_gcd(
801
    args: Value[],
802
    source: string,
803
    command: ControlItem,
804
    context: Context,
805
  ): BigIntValue {
806
    if (args.length === 0) {
×
807
      return { type: "bigint", value: BigInt(0) };
×
808
    }
809

810
    const values = args.map(v => {
×
811
      if (v.type !== "bigint") {
×
812
        handleRuntimeError(
×
813
          context,
814
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
815
        );
816
      }
817
      return BigInt(v.value);
×
818
    });
819

820
    const allZero = values.every(val => val === BigInt(0));
×
821
    if (allZero) {
×
822
      return { type: "bigint", value: BigInt(0) };
×
823
    }
824

825
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
826
    for (let i = 1; i < values.length; i++) {
×
827
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
828
      if (currentGcd === BigInt(1)) {
×
829
        break;
×
830
      }
831
    }
832

833
    return { type: "bigint", value: currentGcd };
×
834
  }
835

836
  static gcdOfTwo(a: bigint, b: bigint): bigint {
837
    let x: bigint = a;
×
838
    let y: bigint = b;
×
839
    while (y !== BigInt(0)) {
×
840
      const temp = x % y;
×
841
      x = y;
×
842
      y = temp;
×
843
    }
844
    return x < 0 ? -x : x;
×
845
  }
846

847
  @Validate(1, 1, "math_isqrt", false)
848
  static math_isqrt(
6✔
849
    args: Value[],
850
    source: string,
851
    command: ControlItem,
852
    context: Context,
853
  ): BigIntValue {
854
    const nValObj = args[0];
×
855
    if (nValObj.type !== "bigint") {
×
856
      handleRuntimeError(
×
857
        context,
858
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
859
      );
860
    }
861

862
    const n: bigint = nValObj.value;
×
863

864
    if (n < 0) {
×
865
      handleRuntimeError(
×
866
        context,
867
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
868
      );
869
    }
870

871
    if (n < 2) {
×
872
      return { type: "bigint", value: n };
×
873
    }
874

875
    let low: bigint = BigInt(1);
×
876
    let high: bigint = n;
×
877

878
    while (low < high) {
×
879
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
880
      const sq = mid * mid;
×
881
      if (sq <= n) {
×
882
        low = mid;
×
883
      } else {
884
        high = mid - BigInt(1);
×
885
      }
886
    }
887

888
    return { type: "bigint", value: low };
×
889
  }
890

891
  static math_lcm(
892
    args: Value[],
893
    source: string,
894
    command: ControlItem,
895
    context: Context,
896
  ): BigIntValue {
897
    if (args.length === 0) {
×
898
      return { type: "bigint", value: BigInt(1) };
×
899
    }
900

901
    const values = args.map(val => {
×
902
      if (val.type !== "bigint") {
×
903
        handleRuntimeError(
×
904
          context,
905
          new TypeError(source, command as ExprNS.Expr, context, val.type, "int"),
906
        );
907
      }
908
      return BigInt(val.value);
×
909
    });
910

911
    if (values.some(v => v === BigInt(0))) {
×
912
      return { type: "bigint", value: BigInt(0) };
×
913
    }
914

915
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
916
    for (let i = 1; i < values.length; i++) {
×
917
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
918
      if (currentLcm === BigInt(0)) {
×
919
        break;
×
920
      }
921
    }
922

923
    return { type: "bigint", value: currentLcm };
×
924
  }
925

926
  static lcmOfTwo(a: bigint, b: bigint): bigint {
927
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
928
    return BigInt((a / gcdVal) * b);
×
929
  }
930

931
  static absBigInt(x: bigint): bigint {
932
    return x < 0 ? -x : x;
×
933
  }
934

935
  @Validate(1, 2, "math_perm", true)
936
  static math_perm(
6✔
937
    args: Value[],
938
    source: string,
939
    command: ControlItem,
940
    context: Context,
941
  ): BigIntValue {
942
    const nValObj = args[0];
×
943
    if (nValObj.type !== "bigint") {
×
944
      handleRuntimeError(
×
945
        context,
946
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
947
      );
948
    }
949
    const n = BigInt(nValObj.value);
×
950

951
    let k = n;
×
952
    if (args.length === 2) {
×
953
      const kValObj = args[1];
×
954
      if (kValObj.type === "none") {
×
955
        k = n;
×
956
      } else if (kValObj.type === "bigint") {
×
957
        k = BigInt(kValObj.value);
×
958
      } else {
959
        handleRuntimeError(
×
960
          context,
961
          new TypeError(source, command as ExprNS.Expr, context, kValObj.type, "int' or 'None"),
962
        );
963
      }
964
    }
965

966
    if (n < 0 || k < 0) {
×
967
      handleRuntimeError(
×
968
        context,
969
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
970
      );
971
    }
972

973
    if (k > n) {
×
974
      return { type: "bigint", value: BigInt(0) };
×
975
    }
976

977
    let result: bigint = BigInt(1);
×
978
    for (let i: bigint = BigInt(0); i < k; i++) {
×
979
      result *= n - i;
×
980
    }
981

982
    return { type: "bigint", value: result };
×
983
  }
984

985
  @Validate(1, 1, "math_ceil", false)
986
  static math_ceil(
6✔
987
    args: Value[],
988
    source: string,
989
    command: ControlItem,
990
    context: Context,
991
  ): BigIntValue {
UNCOV
992
    const x = args[0];
×
993

994
    if (x.type === "bigint") {
×
995
      return x;
×
996
    }
997

998
    if (x.type === "number") {
×
999
      const numVal = x.value;
×
1000
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
1001
      return { type: "bigint", value: ceiled };
×
1002
    }
1003

1004
    handleRuntimeError(
×
1005
      context,
1006
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1007
    );
1008
  }
1009

1010
  @Validate(1, 1, "math_fabs", false)
1011
  static math_fabs(
6✔
1012
    args: Value[],
1013
    source: string,
1014
    command: ControlItem,
1015
    context: Context,
1016
  ): NumberValue {
UNCOV
1017
    const x = args[0];
×
1018

1019
    if (x.type === "bigint") {
×
1020
      const bigVal: bigint = BigInt(x.value);
×
1021
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
1022
      return { type: "number", value: absVal };
×
1023
    }
1024

1025
    if (x.type === "number") {
×
1026
      const numVal: number = x.value;
×
1027
      if (typeof numVal !== "number") {
×
1028
        handleRuntimeError(
×
1029
          context,
1030
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1031
        );
1032
      }
1033
      const absVal: number = Math.abs(numVal);
×
1034
      return { type: "number", value: absVal };
×
1035
    }
1036

1037
    handleRuntimeError(
×
1038
      context,
1039
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1040
    );
1041
  }
1042

1043
  @Validate(1, 1, "math_floor", false)
1044
  static math_floor(
6✔
1045
    args: Value[],
1046
    source: string,
1047
    command: ControlItem,
1048
    context: Context,
1049
  ): BigIntValue {
UNCOV
1050
    const x = args[0];
×
1051

1052
    if (x.type === "bigint") {
×
1053
      return x;
×
1054
    }
1055

1056
    if (x.type === "number") {
×
1057
      const numVal: number = x.value;
×
1058
      if (typeof numVal !== "number") {
×
1059
        handleRuntimeError(
×
1060
          context,
1061
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1062
        );
1063
      }
1064
      const floored: bigint = BigInt(Math.floor(numVal));
×
1065
      return { type: "bigint", value: floored };
×
1066
    }
1067

1068
    handleRuntimeError(
×
1069
      context,
1070
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1071
    );
1072
  }
1073

1074
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
1075
  static twoProd(a: number, b: number): { prod: number; err: number } {
1076
    const prod = a * b;
×
1077
    const c = 134217729; // 2^27 + 1
×
1078
    const a_hi = a * c - (a * c - a);
×
1079
    const a_lo = a - a_hi;
×
1080
    const b_hi = b * c - (b * c - b);
×
1081
    const b_lo = b - b_hi;
×
1082
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
1083
    return { prod, err };
×
1084
  }
1085

1086
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
1087
  static twoSum(a: number, b: number): { sum: number; err: number } {
1088
    const sum = a + b;
×
1089
    const v = sum - a;
×
1090
    const err = a - (sum - v) + (b - v);
×
1091
    return { sum, err };
×
1092
  }
1093

1094
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1095
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1096
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1097
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1098
    const result = sum + (prodErr + sumErr);
×
1099
    return result;
×
1100
  }
1101

1102
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
1103
    if (val.type === "bigint") {
×
1104
      return Number(val.value);
×
1105
    } else if (val.type === "number") {
×
1106
      return val.value;
×
1107
    } else {
1108
      handleRuntimeError(
×
1109
        context,
1110
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
1111
      );
1112
    }
1113
  }
1114

1115
  @Validate(3, 3, "math_fma", false)
1116
  static math_fma(
6✔
1117
    args: Value[],
1118
    source: string,
1119
    command: ControlItem,
1120
    context: Context,
1121
  ): NumberValue {
1122
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1123
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1124
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1125

1126
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1127
    // and fma(inf, 0, nan) should return NaN.
1128
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1129
      return { type: "number", value: NaN };
×
1130
    }
1131
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1132
      return { type: "number", value: NaN };
×
1133
    }
1134
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1135
      return { type: "number", value: NaN };
×
1136
    }
1137

1138
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1139
    return { type: "number", value: result };
×
1140
  }
1141

1142
  @Validate(2, 2, "math_fmod", false)
1143
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1144
    // Convert inputs to numbers
1145
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1146
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1147

1148
    // Divisor cannot be zero
1149
    if (yVal === 0) {
×
1150
      handleRuntimeError(
×
1151
        context,
1152
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1153
      );
1154
    }
1155

1156
    // JavaScript's % operator behaves similarly to C's fmod
1157
    // in that the sign of the result is the same as the sign of x.
1158
    // For corner cases (NaN, Infinity), JavaScript remainder
1159
    // yields results consistent with typical C library fmod behavior.
1160
    const remainder = xVal % yVal;
×
1161

1162
    return { type: "number", value: remainder };
×
1163
  }
1164

1165
  static roundToEven(num: number): number {
1166
    //uses Banker's Rounding as per Python's Round() function
1167
    const floorVal = Math.floor(num);
×
1168
    const ceilVal = Math.ceil(num);
×
1169
    const diffFloor = num - floorVal;
×
1170
    const diffCeil = ceilVal - num;
×
1171
    if (diffFloor < diffCeil) {
×
1172
      return floorVal;
×
1173
    } else if (diffCeil < diffFloor) {
×
1174
      return ceilVal;
×
1175
    } else {
1176
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1177
    }
1178
  }
1179

1180
  @Validate(2, 2, "math_remainder", false)
1181
  static math_remainder(
6✔
1182
    args: Value[],
1183
    source: string,
1184
    command: ControlItem,
1185
    context: Context,
1186
  ): NumberValue {
1187
    const x = args[0];
×
1188
    const y = args[1];
×
1189

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

1202
    let yValue: number;
1203
    if (y.type === "bigint") {
×
1204
      yValue = Number(y.value);
×
1205
    } else if (y.type === "number") {
×
1206
      yValue = y.value;
×
1207
    } else {
1208
      handleRuntimeError(
×
1209
        context,
1210
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1211
      );
1212
    }
1213

1214
    if (yValue === 0) {
×
1215
      handleRuntimeError(
×
1216
        context,
1217
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1218
      );
1219
    }
1220

1221
    const quotient = xValue / yValue;
×
1222
    const n = BuiltInFunctions.roundToEven(quotient);
×
1223
    const remainder = xValue - n * yValue;
×
1224

1225
    return { type: "number", value: remainder };
×
1226
  }
1227

1228
  @Validate(1, 1, "math_trunc", false)
1229
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1230
    const x = args[0];
×
1231

1232
    if (x.type === "bigint") {
×
1233
      return x;
×
1234
    }
1235

1236
    if (x.type === "number") {
×
1237
      const numVal: number = x.value;
×
1238
      if (typeof numVal !== "number") {
×
1239
        handleRuntimeError(
×
1240
          context,
1241
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1242
        );
1243
      }
1244
      let truncated: number;
1245
      if (numVal === 0) {
×
1246
        truncated = 0;
×
1247
      } else if (numVal < 0) {
×
1248
        truncated = Math.ceil(numVal);
×
1249
      } else {
1250
        truncated = Math.floor(numVal);
×
1251
      }
1252
      return { type: "bigint", value: BigInt(truncated) };
×
1253
    }
1254

1255
    handleRuntimeError(
×
1256
      context,
1257
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1258
    );
1259
  }
1260

1261
  @Validate(2, 2, "math_copysign", false)
1262
  static math_copysign(
6✔
1263
    args: Value[],
1264
    source: string,
1265
    command: ControlItem,
1266
    context: Context,
1267
  ): NumberValue {
1268
    const [x, y] = args;
×
1269

1270
    if (x.type !== "number" && x.type !== "bigint") {
×
1271
      handleRuntimeError(
×
1272
        context,
1273
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1274
      );
1275
    } else if (y.type !== "number" && y.type !== "bigint") {
×
1276
      handleRuntimeError(
×
1277
        context,
1278
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1279
      );
1280
    }
1281

1282
    const xVal = Number(x.value);
×
1283
    const yVal = Number(y.value);
×
1284

1285
    const absVal = Math.abs(xVal);
×
1286
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1287
    const result = isNegative ? -absVal : absVal;
×
1288

1289
    return { type: "number", value: Number(result) };
×
1290
  }
1291

1292
  @Validate(1, 1, "math_isfinite", false)
1293
  static math_isfinite(
6✔
1294
    args: Value[],
1295
    source: string,
1296
    command: ControlItem,
1297
    context: Context,
1298
  ): BoolValue {
1299
    const xValObj = args[0];
×
1300
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1301
      handleRuntimeError(
×
1302
        context,
1303
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1304
      );
1305
    }
1306

1307
    const x = Number(xValObj.value);
×
1308
    const result: boolean = Number.isFinite(x);
×
1309

1310
    return { type: "bool", value: result };
×
1311
  }
1312

1313
  @Validate(1, 1, "math_isinf", false)
1314
  static math_isinf(
6✔
1315
    args: Value[],
1316
    source: string,
1317
    command: ControlItem,
1318
    context: Context,
1319
  ): BoolValue {
1320
    const xValObj = args[0];
×
1321
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1322
      handleRuntimeError(
×
1323
        context,
1324
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1325
      );
1326
    }
1327

1328
    const x = Number(xValObj.value);
×
1329
    const result: boolean = x === Infinity || x === -Infinity;
×
1330

1331
    return { type: "bool", value: result };
×
1332
  }
1333

1334
  @Validate(1, 1, "math_isnan", false)
1335
  static math_isnan(
6✔
1336
    args: Value[],
1337
    source: string,
1338
    command: ControlItem,
1339
    context: Context,
1340
  ): BoolValue {
1341
    const xValObj = args[0];
×
1342
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1343
      handleRuntimeError(
×
1344
        context,
1345
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1346
      );
1347
    }
1348

1349
    const x = Number(xValObj.value);
×
1350
    const result: boolean = Number.isNaN(x);
×
1351

1352
    return { type: "bool", value: result };
×
1353
  }
1354

1355
  @Validate(2, 2, "math_ldexp", false)
1356
  static math_ldexp(
6✔
1357
    args: Value[],
1358
    source: string,
1359
    command: ControlItem,
1360
    context: Context,
1361
  ): NumberValue {
UNCOV
1362
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1363

1364
    if (args[1].type !== "bigint") {
×
1365
      handleRuntimeError(
×
1366
        context,
1367
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1368
      );
1369
    }
1370
    const expVal = args[1].value;
×
1371

1372
    // Perform x * 2^expVal
1373
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1374
    // That behavior parallels typical C library rules for ldexp.
1375
    const result = xVal * Math.pow(2, Number(expVal));
×
1376

1377
    return { type: "number", value: result };
×
1378
  }
1379

1380
  static math_nextafter(
1381
    _args: Value[],
1382
    _source: string,
1383
    _command: ControlItem,
1384
    _context: Context,
1385
  ): Value {
1386
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1387
    throw new Error("math_nextafter not implemented");
×
1388
  }
1389

1390
  static math_ulp(
1391
    _args: Value[],
1392
    _source: string,
1393
    _command: ControlItem,
1394
    _context: Context,
1395
  ): Value {
1396
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1397
    throw new Error("math_ulp not implemented");
×
1398
  }
1399

1400
  @Validate(1, 1, "math_cbrt", false)
1401
  static math_cbrt(
6✔
1402
    args: Value[],
1403
    source: string,
1404
    command: ControlItem,
1405
    context: Context,
1406
  ): NumberValue {
UNCOV
1407
    const xVal = args[0];
×
1408
    let x: number;
1409

1410
    if (xVal.type !== "number") {
×
1411
      if (xVal.type === "bigint") {
×
1412
        x = Number(xVal.value);
×
1413
      } else {
1414
        handleRuntimeError(
×
1415
          context,
1416
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1417
        );
1418
      }
1419
    } else {
1420
      x = xVal.value;
×
1421
    }
1422

1423
    const result = Math.cbrt(x);
×
1424

1425
    return { type: "number", value: result };
×
1426
  }
1427

1428
  @Validate(1, 1, "math_exp", false)
1429
  static math_exp(
6✔
1430
    args: Value[],
1431
    source: string,
1432
    command: ControlItem,
1433
    context: Context,
1434
  ): NumberValue {
UNCOV
1435
    const xVal = args[0];
×
1436
    let x: number;
1437

1438
    if (xVal.type !== "number") {
×
1439
      if (xVal.type === "bigint") {
×
1440
        x = Number(xVal.value);
×
1441
      } else {
1442
        handleRuntimeError(
×
1443
          context,
1444
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1445
        );
1446
      }
1447
    } else {
1448
      x = xVal.value;
×
1449
    }
1450

1451
    const result = Math.exp(x);
×
1452
    return { type: "number", value: result };
×
1453
  }
1454

1455
  @Validate(1, 1, "math_exps", false)
1456
  static math_exp2(
6✔
1457
    args: Value[],
1458
    source: string,
1459
    command: ControlItem,
1460
    context: Context,
1461
  ): NumberValue {
UNCOV
1462
    const xVal = args[0];
×
1463
    let x: number;
1464

1465
    if (xVal.type !== "number") {
×
1466
      if (xVal.type === "bigint") {
×
1467
        x = Number(xVal.value);
×
1468
      } else {
1469
        handleRuntimeError(
×
1470
          context,
1471
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1472
        );
1473
      }
1474
    } else {
1475
      x = xVal.value;
×
1476
    }
1477

1478
    const result = Math.pow(2, x);
×
1479
    return { type: "number", value: result };
×
1480
  }
1481

1482
  @Validate(1, 1, "math_expm1", false)
1483
  static math_expm1(
6✔
1484
    args: Value[],
1485
    source: string,
1486
    command: ControlItem,
1487
    context: Context,
1488
  ): NumberValue {
1489
    const x = args[0];
×
1490
    if (x.type !== "number" && x.type !== "bigint") {
×
1491
      handleRuntimeError(
×
1492
        context,
1493
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1494
      );
1495
    }
1496

1497
    let num: number;
1498
    if (x.type === "number") {
×
1499
      num = x.value;
×
1500
    } else {
1501
      num = Number(x.value);
×
1502
    }
1503

1504
    const result = Math.expm1(num);
×
1505
    return { type: "number", value: result };
×
1506
  }
1507

1508
  @Validate(1, 1, "math_gamma", false)
1509
  static math_gamma(
6✔
1510
    args: Value[],
1511
    source: string,
1512
    command: ControlItem,
1513
    context: Context,
1514
  ): NumberValue {
1515
    const x = args[0];
×
1516
    if (x.type !== "number" && x.type !== "bigint") {
×
1517
      handleRuntimeError(
×
1518
        context,
1519
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1520
      );
1521
    }
1522

1523
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1524
    const result = gamma(z);
×
1525

1526
    return { type: "number", value: result };
×
1527
  }
1528

1529
  @Validate(1, 1, "math_lgamma", false)
1530
  static math_lgamma(
6✔
1531
    args: Value[],
1532
    source: string,
1533
    command: ControlItem,
1534
    context: Context,
1535
  ): NumberValue {
1536
    const x = args[0];
×
1537
    if (x.type !== "number" && x.type !== "bigint") {
×
1538
      handleRuntimeError(
×
1539
        context,
1540
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1541
      );
1542
    }
1543

1544
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1545
    const result = lgamma(z);
×
1546

1547
    return { type: "number", value: result };
×
1548
  }
1549

1550
  @Validate(1, 2, "math_log", true)
1551
  static math_log(
6✔
1552
    args: Value[],
1553
    source: string,
1554
    command: ControlItem,
1555
    context: Context,
1556
  ): NumberValue {
1557
    const x = args[0];
×
1558
    if (x.type !== "number" && x.type !== "bigint") {
×
1559
      handleRuntimeError(
×
1560
        context,
1561
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1562
      );
1563
    }
1564
    let num: number;
1565
    if (x.type === "number") {
×
1566
      num = x.value;
×
1567
    } else {
1568
      num = Number(x.value);
×
1569
    }
1570

1571
    if (num <= 0) {
×
1572
      handleRuntimeError(
×
1573
        context,
1574
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1575
      );
1576
    }
1577

1578
    if (args.length === 1) {
×
1579
      return { type: "number", value: Math.log(num) };
×
1580
    }
1581

1582
    const baseArg = args[1];
×
1583
    if (baseArg.type !== "number" && baseArg.type !== "bigint") {
×
1584
      handleRuntimeError(
×
1585
        context,
1586
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
1587
      );
1588
    }
1589
    let baseNum: number;
1590
    if (baseArg.type === "number") {
×
1591
      baseNum = baseArg.value;
×
1592
    } else {
1593
      baseNum = Number(baseArg.value);
×
1594
    }
1595
    if (baseNum <= 0) {
×
1596
      handleRuntimeError(
×
1597
        context,
1598
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1599
      );
1600
    }
1601

1602
    const result = Math.log(num) / Math.log(baseNum);
×
1603
    return { type: "number", value: result };
×
1604
  }
1605

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

1633
    const result = Math.log10(num);
×
1634
    return { type: "number", value: result };
×
1635
  }
1636

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

1664
    const result = Math.log1p(num);
×
1665
    return { type: "number", value: result };
×
1666
  }
1667

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

1695
    const result = Math.log2(num);
×
1696
    return { type: "number", value: result };
×
1697
  }
1698

1699
  @Validate(2, 2, "math_pow", false)
1700
  static math_pow(
6✔
1701
    args: Value[],
1702
    source: string,
1703
    command: ControlItem,
1704
    context: Context,
1705
  ): NumberValue {
1706
    const base = args[0];
×
1707
    const exp = args[1];
×
1708

1709
    if (base.type !== "number" && base.type !== "bigint") {
×
1710
      handleRuntimeError(
×
1711
        context,
1712
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1713
      );
1714
    } else if (exp.type !== "number" && exp.type !== "bigint") {
×
1715
      handleRuntimeError(
×
1716
        context,
1717
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1718
      );
1719
    }
1720

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

1728
    let expNum: number;
1729
    if (exp.type === "number") {
×
1730
      expNum = exp.value;
×
1731
    } else {
1732
      expNum = Number(exp.value);
×
1733
    }
1734

1735
    const result = Math.pow(baseNum, expNum);
×
1736
    return { type: "number", value: result };
×
1737
  }
1738

1739
  @Validate(1, 1, "math_radians", false)
1740
  static math_radians(
6✔
1741
    args: Value[],
1742
    source: string,
1743
    command: ControlItem,
1744
    context: Context,
1745
  ): NumberValue {
1746
    const x = args[0];
×
1747
    if (x.type !== "number" && x.type !== "bigint") {
×
1748
      handleRuntimeError(
×
1749
        context,
1750
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1751
      );
1752
    }
1753

1754
    let deg: number;
1755
    if (x.type === "number") {
×
1756
      deg = x.value;
×
1757
    } else {
1758
      deg = Number(x.value);
×
1759
    }
1760

1761
    const radians = (deg * Math.PI) / 180;
×
1762
    return { type: "number", value: radians };
×
1763
  }
1764

1765
  @Validate(1, 1, "math_sin", false)
1766
  static math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1767
    const x = args[0];
7✔
1768
    if (x.type !== "number" && x.type !== "bigint") {
7✔
1769
      handleRuntimeError(
3✔
1770
        context,
1771
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1772
      );
1773
    }
1774

1775
    let num: number;
1776
    if (x.type === "number") {
4✔
1777
      num = x.value;
3✔
1778
    } else {
1779
      num = Number(x.value);
1✔
1780
    }
1781

1782
    const result = Math.sin(num);
4✔
1783
    return { type: "number", value: result };
4✔
1784
  }
1785

1786
  @Validate(1, 1, "math_sinh", false)
1787
  static math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1788
    const x = args[0];
×
1789
    if (x.type !== "number" && x.type !== "bigint") {
×
1790
      handleRuntimeError(
×
1791
        context,
1792
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1793
      );
1794
    }
1795

1796
    let num: number;
1797
    if (x.type === "number") {
×
1798
      num = x.value;
×
1799
    } else {
1800
      num = Number(x.value);
×
1801
    }
1802

1803
    const result = Math.sinh(num);
×
1804
    return { type: "number", value: result };
×
1805
  }
1806

1807
  @Validate(1, 1, "math_tan", false)
1808
  static math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1809
    const x = args[0];
×
1810
    if (x.type !== "number" && x.type !== "bigint") {
×
1811
      handleRuntimeError(
×
1812
        context,
1813
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1814
      );
1815
    }
1816

1817
    let num: number;
1818
    if (x.type === "number") {
×
1819
      num = x.value;
×
1820
    } else {
1821
      num = Number(x.value);
×
1822
    }
1823

1824
    const result = Math.tan(num);
×
1825
    return { type: "number", value: result };
×
1826
  }
1827

1828
  @Validate(1, 1, "math_tanh", false)
1829
  static math_tanh(
6✔
1830
    args: Value[],
1831
    source: string,
1832
    command: ControlItem,
1833
    context: Context,
1834
  ): NumberValue {
1835
    const x = args[0];
×
1836
    if (x.type !== "number" && x.type !== "bigint") {
×
1837
      handleRuntimeError(
×
1838
        context,
1839
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1840
      );
1841
    }
1842

1843
    let num: number;
1844
    if (x.type === "number") {
×
1845
      num = x.value;
×
1846
    } else {
1847
      num = Number(x.value);
×
1848
    }
1849

1850
    const result = Math.tanh(num);
×
1851
    return { type: "number", value: result };
×
1852
  }
1853

1854
  @Validate(1, 1, "math_sqrt", false)
1855
  static math_sqrt(
6✔
1856
    args: Value[],
1857
    source: string,
1858
    command: ControlItem,
1859
    context: Context,
1860
  ): NumberValue {
1861
    const x = args[0];
×
1862
    if (x.type !== "number" && x.type !== "bigint") {
×
1863
      handleRuntimeError(
×
1864
        context,
1865
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1866
      );
1867
    }
1868

1869
    let num: number;
1870
    if (x.type === "number") {
×
1871
      num = x.value;
×
1872
    } else {
1873
      num = Number(x.value);
×
1874
    }
1875

1876
    if (num < 0) {
×
1877
      handleRuntimeError(
×
1878
        context,
1879
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1880
      );
1881
    }
1882

1883
    const result = Math.sqrt(num);
×
1884
    return { type: "number", value: result };
×
1885
  }
1886

1887
  @Validate(2, null, "max", true)
1888
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1889
    const numericTypes = ["bigint", "number"];
×
1890
    const firstType = args[0].type;
×
1891
    const isNumeric = numericTypes.includes(firstType);
×
1892
    const isString = firstType === "string";
×
1893

1894
    for (let i = 1; i < args.length; i++) {
×
1895
      const t = args[i].type;
×
1896
      if (isNumeric && !numericTypes.includes(t)) {
×
1897
        handleRuntimeError(
×
1898
          context,
1899
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1900
        );
1901
      }
1902
      if (isString && t !== "string") {
×
1903
        handleRuntimeError(
×
1904
          context,
1905
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1906
        );
1907
      }
1908
    }
1909

1910
    let useFloat = false;
×
1911
    if (isNumeric) {
×
1912
      for (const arg of args) {
×
1913
        if (arg.type === "number") {
×
1914
          useFloat = true;
×
1915
          break;
×
1916
        }
1917
      }
1918
    }
1919

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

1994
    return args[maxIndex];
×
1995
  }
1996

1997
  @Validate(2, null, "min", true)
1998
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1999
    if (args.length < 2) {
×
2000
      handleRuntimeError(
×
2001
        context,
2002
        new MissingRequiredPositionalError(
2003
          source,
2004
          command as ExprNS.Expr,
2005
          "min",
2006
          Number(2),
2007
          args,
2008
          true,
2009
        ),
2010
      );
2011
    }
2012

2013
    const numericTypes = ["bigint", "number"];
×
2014
    const firstType = args[0].type;
×
2015
    const isNumeric = numericTypes.includes(firstType);
×
2016
    const isString = firstType === "string";
×
2017

2018
    for (let i = 1; i < args.length; i++) {
×
2019
      const t = args[i].type;
×
2020
      if (isNumeric && !numericTypes.includes(t)) {
×
2021
        handleRuntimeError(
×
2022
          context,
2023
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
2024
        );
2025
      }
2026
      if (isString && t !== "string") {
×
2027
        handleRuntimeError(
×
2028
          context,
2029
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
2030
        );
2031
      }
2032
    }
2033

2034
    let useFloat = false;
×
2035
    if (isNumeric) {
×
2036
      for (const arg of args) {
×
2037
        if (arg.type === "number") {
×
2038
          useFloat = true;
×
2039
          break;
×
2040
        }
2041
      }
2042
    }
2043

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

2118
    return args[maxIndex];
×
2119
  }
2120

2121
  @Validate(null, 0, "random_random", true)
2122
  static random_random(
6✔
2123
    _args: Value[],
2124
    _source: string,
2125
    _command: ControlItem,
2126
    _context: Context,
2127
  ): NumberValue {
2128
    const result = Math.random();
×
2129
    return { type: "number", value: result };
×
2130
  }
2131

2132
  @Validate(1, 2, "round", true)
2133
  static round(
6✔
2134
    args: Value[],
2135
    source: string,
2136
    command: ControlItem,
2137
    context: Context,
2138
  ): NumberValue | BigIntValue {
2139
    const numArg = args[0];
15✔
2140
    if (numArg.type !== "number" && numArg.type !== "bigint") {
15✔
2141
      handleRuntimeError(
2✔
2142
        context,
2143
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
2144
      );
2145
    }
2146

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

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

2198
  @Validate(null, 0, "time_time", true)
2199
  static time_time(
6✔
2200
    _args: Value[],
2201
    _source: string,
2202
    _command: ControlItem,
2203
    _context: Context,
2204
  ): NumberValue {
2205
    const currentTime = Date.now();
×
2206
    return { type: "number", value: currentTime };
×
2207
  }
2208

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

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

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

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

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

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

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

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

2324
import { ExprNS } from "./ast-types";
2325
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2326

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

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

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

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

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

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

2389
  if (num === Infinity) {
51!
2390
    return "inf";
×
2391
  }
2392
  if (num === -Infinity) {
51!
2393
    return "-inf";
×
2394
  }
2395

2396
  if (Number.isNaN(num)) {
51!
2397
    return "nan";
×
2398
  }
2399

2400
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
51✔
2401
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
1✔
2402
  }
2403
  if (Number.isInteger(num)) {
50✔
2404
    return num.toFixed(1).toString();
28✔
2405
  }
2406
  return num.toString();
22✔
2407
}
2408
function escape(str: string): string {
2409
  let escaped = JSON.stringify(str);
4✔
2410
  if (!(str.includes("'") && !str.includes('"'))) {
4✔
2411
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
4✔
2412
  }
2413
  return escaped;
4✔
2414
}
2415
export function toPythonString(obj: Value, repr: boolean = false): string {
6✔
2416
  let ret: string = "";
1,317✔
2417
  if (obj.type == "builtin") {
1,317!
2418
    return `<built-in function ${obj.name}>`;
×
2419
  }
2420
  if (obj.type === "bigint" || obj.type === "complex") {
1,317✔
2421
    ret = obj.value.toString();
129✔
2422
  } else if (obj.type === "number") {
1,188✔
2423
    ret = toPythonFloat(obj.value);
59✔
2424
  } else if (obj.type === "bool") {
1,129✔
2425
    if (obj.value) {
249✔
2426
      return "True";
130✔
2427
    } else {
2428
      return "False";
119✔
2429
    }
2430
  } else if (obj.type === "error") {
880!
2431
    return obj.message;
×
2432
  } else if (obj.type === "closure") {
880✔
2433
    if (obj.closure.node) {
2✔
2434
      const funcName =
2435
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2436
      return `<function ${funcName}>`;
2✔
2437
    }
2438
  } else if (obj.type === "none") {
878✔
2439
    ret = "None";
838✔
2440
  } else if (obj.type === "string") {
40!
2441
    ret = repr ? escape(obj.value) : obj.value;
40✔
NEW
2442
  } else if (obj.type === "function") {
×
NEW
2443
    const funcName = obj.name || "(anonymous)";
×
NEW
2444
    ret = `<function ${funcName}>`;
×
NEW
2445
  } else if (obj.type === "list") {
×
NEW
2446
    ret = `[${obj.value.map(v => toPythonString(v, true)).join(", ")}]`;
×
2447
  } else {
NEW
2448
    ret = `<${obj.type} object>`;
×
2449
  }
2450
  return ret;
1,066✔
2451
}
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