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

source-academy / py-slang / 23714824004

29 Mar 2026 05:29PM UTC coverage: 58.482% (+17.2%) from 41.233%
23714824004

Pull #81

github

web-flow
Merge b8f74f50e into 7b1e59a17
Pull Request #81: Python chapter 2 (+ 3?) support

786 of 1641 branches covered (47.9%)

Branch coverage included in aggregate %.

431 of 601 new or added lines in 32 files covered. (71.71%)

17 existing lines in 4 files now uncovered.

2396 of 3800 relevant lines covered (63.05%)

3192.42 hits per line

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

18.14
/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
  UserError,
21
  ValueError,
22
} from "./errors/errors";
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

275
  static toStr(val: Value): string {
276
    return toPythonString(val);
2✔
277
  }
278

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

664
    const erfnum = erf(num);
×
665

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1995
    return args[maxIndex];
×
1996
  }
1997

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

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

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

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

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

2119
    return args[maxIndex];
×
2120
  }
2121

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

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

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

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

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

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

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

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

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

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

2265
  @Validate(1, 1, "is_int", true)
2266
  static is_int(
6✔
2267
    args: Value[],
2268
    _source: string,
2269
    _command: ControlItem,
2270
    _context: Context,
2271
  ): BoolValue {
2272
    const obj = args[0];
155✔
2273
    return { type: "bool", value: obj.type === "bigint" };
155✔
2274
  }
2275

2276
  @Validate(1, 1, "is_function", true)
2277
  static is_function(
6✔
2278
    args: Value[],
2279
    _source: string,
2280
    _command: ControlItem,
2281
    _context: Context,
2282
  ): BoolValue {
2283
    const obj = args[0];
10✔
2284
    return {
10✔
2285
      type: "bool",
2286
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
29✔
2287
    };
2288
  }
2289

2290
  static async input(
2291
    _args: Value[],
2292
    _source: string,
2293
    _command: ControlItem,
2294
    context: Context,
2295
  ): Promise<Value> {
2296
    const userInput = await receiveInput(context);
×
2297
    return { type: "string", value: userInput };
×
2298
  }
2299

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

2336
import { ExprNS } from "./ast-types";
2337
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2338

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

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

2349
const constantMap = {
6✔
2350
  math_e: { type: "number", value: Math.E },
2351
  math_inf: { type: "number", value: Infinity },
2352
  math_nan: { type: "number", value: NaN },
2353
  math_pi: { type: "number", value: Math.PI },
2354
  math_tau: { type: "number", value: 2 * Math.PI },
2355
} as const;
2356

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

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

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

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

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

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

© 2026 Coveralls, Inc