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

source-academy / py-slang / 23790308861

31 Mar 2026 09:27AM UTC coverage: 62.878% (+2.4%) from 60.472%
23790308861

Pull #129

github

web-flow
Merge 85b8cb3b5 into 0e5a36381
Pull Request #129: Add new standard library functions

778 of 1450 branches covered (53.66%)

Branch coverage included in aggregate %.

64 of 107 new or added lines in 8 files covered. (59.81%)

3 existing lines in 2 files now uncovered.

2547 of 3838 relevant lines covered (66.36%)

3398.47 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
180
    const parsed = parseInt(str, base);
×
NEW
181
    return { type: "bigint", value: BigInt(sign * parsed) };
×
182
  }
183

184
  @Validate(null, 1, "float", true)
185
  static float(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
186
    if (args.length === 0) {
16!
NEW
187
      return { type: "number", value: 0 };
×
188
    }
189
    const val = args[0];
16✔
190
    if (val.type === "bigint") {
16✔
191
      return { type: "number", value: Number(val.value) };
1✔
192
    } else if (val.type === "number") {
15✔
193
      return { type: "number", value: val.value };
2✔
194
    } else if (val.type === "bool") {
13✔
195
      return { type: "number", value: val.value ? 1 : 0 };
1!
196
    } else if (val.type === "string") {
12✔
197
      const str = val.value.trim().replace(/_/g, "");
8✔
198
      const num = Number(str);
8✔
199
      if (isNaN(num) && str.toLowerCase() !== "nan") {
8✔
200
        handleRuntimeError(
1✔
201
          context,
202
          new ValueError(source, command as ExprNS.Expr, context, "float"),
203
        );
204
      }
205
      return { type: "number", value: num };
7✔
206
    }
207
    handleRuntimeError(
4✔
208
      context,
209
      new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
210
    );
211
  }
212

213
  @Validate(null, 1, "complex", true)
214
  static complex(
6✔
215
    args: Value[],
216
    source: string,
217
    command: ControlItem,
218
    context: Context,
219
  ): ComplexValue {
220
    if (args.length === 0) {
21!
NEW
221
      return { type: "complex", value: new PyComplexNumber(0, 0) };
×
222
    }
223
    const val = args[0];
21✔
224
    if (
21✔
225
      val.type !== "bigint" &&
79✔
226
      val.type !== "number" &&
227
      val.type !== "bool" &&
228
      val.type !== "string" &&
229
      val.type !== "complex"
230
    ) {
231
      handleRuntimeError(
3✔
232
        context,
233
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
234
      );
235
    }
236
    return {
18✔
237
      type: "complex",
238
      value: PyComplexNumber.fromValue(context, source, command as ExprNS.Expr, val.value),
239
    };
240
  }
241

242
  @Validate(1, 1, "real", true)
243
  static real(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
NEW
244
    const val = args[0];
×
NEW
245
    if (val.type !== "complex") {
×
UNCOV
246
      handleRuntimeError(
×
247
        context,
248
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
249
      );
250
    }
NEW
251
    return { type: "number", value: val.value.real };
×
252
  }
253

254
  @Validate(1, 1, "imag", true)
255
  static imag(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
NEW
256
    const val = args[0];
×
NEW
257
    if (val.type !== "complex") {
×
UNCOV
258
      handleRuntimeError(
×
259
        context,
260
        new TypeError(source, command as ExprNS.Expr, context, val.type, "complex"),
261
      );
262
    }
NEW
263
    return { type: "number", value: val.value.imag };
×
264
  }
265

266
  @Validate(null, 1, "bool", true)
267
  static bool(args: Value[], _source: string, _command: ControlItem, _context: Context): BoolValue {
6✔
268
    if (args.length === 0) {
12!
NEW
269
      return { type: "bool", value: false };
×
270
    }
271
    const val = args[0];
12✔
272
    return { type: "bool", value: !isFalsy(val) };
12✔
273
  }
274

275
  @Validate(1, 1, "abs", false)
276
  static abs(
6✔
277
    args: Value[],
278
    source: string,
279
    command: ControlItem,
280
    context: Context,
281
  ): BigIntValue | NumberValue {
282
    const x = args[0];
15✔
283
    switch (x.type) {
15!
284
      case "bigint": {
285
        const intVal = x.value;
11✔
286
        const result: bigint = intVal < 0 ? -intVal : intVal;
11✔
287
        return { type: "bigint", value: result };
11✔
288
      }
289
      case "number": {
290
        return { type: "number", value: Math.abs(x.value) };
2✔
291
      }
292
      case "complex": {
293
        // Calculate the modulus (absolute value) of a complex number.
294
        const real = x.value.real;
×
295
        const imag = x.value.imag;
×
296
        const modulus = Math.sqrt(real * real + imag * imag);
×
297
        return { type: "number", value: modulus };
×
298
      }
299
      default:
300
        handleRuntimeError(
2✔
301
          context,
302
          new TypeError(
303
            source,
304
            command as ExprNS.Expr,
305
            context,
306
            args[0].type,
307
            "float', 'int' or 'complex",
308
          ),
309
        );
310
    }
311
  }
312

313
  static toStr(val: Value): string {
314
    return toPythonString(val);
2✔
315
  }
316

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

322
  @Validate(1, 1, "math_acos", false)
323
  static math_acos(
6✔
324
    args: Value[],
325
    source: string,
326
    command: ControlItem,
327
    context: Context,
328
  ): NumberValue {
329
    const x = args[0];
×
330
    if (!isNumeric(x)) {
×
331
      handleRuntimeError(
×
332
        context,
333
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
334
      );
335
    }
336

337
    let num: number;
338
    if (x.type === "number") {
×
339
      num = x.value;
×
340
    } else {
341
      num = Number(x.value);
×
342
    }
343

344
    if (num < -1 || num > 1) {
×
345
      handleRuntimeError(
×
346
        context,
347
        new ValueError(source, command as ExprNS.Expr, context, "math_acos"),
348
      );
349
    }
350

351
    const result = Math.acos(num);
×
352
    return { type: "number", value: result };
×
353
  }
354

355
  @Validate(1, 1, "math_acosh", false)
356
  static math_acosh(
6✔
357
    args: Value[],
358
    source: string,
359
    command: ControlItem,
360
    context: Context,
361
  ): NumberValue {
362
    const x = args[0];
×
363

364
    if (!isNumeric(x)) {
×
365
      handleRuntimeError(
×
366
        context,
367
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
368
      );
369
    }
370

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

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

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

389
  @Validate(1, 1, "math_asin", false)
390
  static math_asin(
6✔
391
    args: Value[],
392
    source: string,
393
    command: ControlItem,
394
    context: Context,
395
  ): NumberValue {
396
    const x = args[0];
×
397
    if (!isNumeric(x)) {
×
398
      handleRuntimeError(
×
399
        context,
400
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
401
      );
402
    }
403

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

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

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

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

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

444
    const result = Math.asinh(num);
×
445
    return { type: "number", value: result };
×
446
  }
447

448
  @Validate(1, 1, "math_atan", false)
449
  static math_atan(
6✔
450
    args: Value[],
451
    source: string,
452
    command: ControlItem,
453
    context: Context,
454
  ): NumberValue {
455
    const x = args[0];
×
456
    if (!isNumeric(x)) {
×
457
      handleRuntimeError(
×
458
        context,
459
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
460
      );
461
    }
462

463
    let num: number;
464
    if (x.type === "number") {
×
465
      num = x.value;
×
466
    } else {
467
      num = Number(x.value);
×
468
    }
469

470
    const result = Math.atan(num);
×
471
    return { type: "number", value: result };
×
472
  }
473

474
  @Validate(2, 2, "math_atan2", false)
475
  static math_atan2(
6✔
476
    args: Value[],
477
    source: string,
478
    command: ControlItem,
479
    context: Context,
480
  ): NumberValue {
481
    const y = args[0];
×
482
    const x = args[1];
×
483
    if (!isNumeric(x)) {
×
484
      handleRuntimeError(
×
485
        context,
486
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
487
      );
488
    } else if (!isNumeric(y)) {
×
489
      handleRuntimeError(
×
490
        context,
491
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
492
      );
493
    }
494

495
    let yNum: number, xNum: number;
496
    if (y.type === "number") {
×
497
      yNum = y.value;
×
498
    } else {
499
      yNum = Number(y.value);
×
500
    }
501

502
    if (x.type === "number") {
×
503
      xNum = x.value;
×
504
    } else {
505
      xNum = Number(x.value);
×
506
    }
507

508
    const result = Math.atan2(yNum, xNum);
×
509
    return { type: "number", value: result };
×
510
  }
511

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

527
    let num: number;
528
    if (x.type === "number") {
×
529
      num = x.value;
×
530
    } else {
531
      num = Number(x.value);
×
532
    }
533

534
    if (num <= -1 || num >= 1) {
×
535
      handleRuntimeError(
×
536
        context,
537
        new ValueError(source, command as ExprNS.Expr, context, "math_atanh"),
538
      );
539
    }
540

541
    const result = Math.atanh(num);
×
542
    return { type: "number", value: result };
×
543
  }
544

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

560
    let num: number;
561
    if (x.type === "number") {
4✔
562
      num = x.value;
3✔
563
    } else {
564
      num = Number(x.value);
1✔
565
    }
566

567
    const result = Math.cos(num);
4✔
568
    return { type: "number", value: result };
4✔
569
  }
570

571
  @Validate(1, 1, "math_cosh", false)
572
  static math_cosh(
6✔
573
    args: Value[],
574
    source: string,
575
    command: ControlItem,
576
    context: Context,
577
  ): NumberValue {
578
    const x = args[0];
×
579
    if (!isNumeric(x)) {
×
580
      handleRuntimeError(
×
581
        context,
582
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
583
      );
584
    }
585

586
    let num: number;
587
    if (x.type === "number") {
×
588
      num = x.value;
×
589
    } else {
590
      num = Number(x.value);
×
591
    }
592

593
    const result = Math.cosh(num);
×
594
    return { type: "number", value: result };
×
595
  }
596

597
  @Validate(1, 1, "math_degrees", false)
598
  static math_degrees(
6✔
599
    args: Value[],
600
    source: string,
601
    command: ControlItem,
602
    context: Context,
603
  ): NumberValue {
604
    const x = args[0];
×
605
    if (!isNumeric(x)) {
×
606
      handleRuntimeError(
×
607
        context,
608
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
609
      );
610
    }
611

612
    let num: number;
613
    if (x.type === "number") {
×
614
      num = x.value;
×
615
    } else {
616
      num = Number(x.value);
×
617
    }
618

619
    const result = (num * 180) / Math.PI;
×
620
    return { type: "number", value: result };
×
621
  }
622

623
  @Validate(1, 1, "math_erf", false)
624
  static math_erf(
6✔
625
    args: Value[],
626
    source: string,
627
    command: ControlItem,
628
    context: Context,
629
  ): NumberValue {
630
    const x = args[0];
×
631
    if (!isNumeric(x)) {
×
632
      handleRuntimeError(
×
633
        context,
634
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
635
      );
636
    }
637

638
    let num: number;
639
    if (x.type === "number") {
×
640
      num = x.value;
×
641
    } else {
642
      num = Number(x.value);
×
643
    }
644

645
    const erfnum = erf(num);
×
646

647
    return { type: "number", value: erfnum };
×
648
  }
649

650
  @Validate(1, 1, "math_erfc", false)
651
  static math_erfc(
6✔
652
    args: Value[],
653
    source: string,
654
    command: ControlItem,
655
    context: Context,
656
  ): NumberValue {
657
    const x = args[0];
×
658
    if (!isNumeric(x)) {
×
659
      handleRuntimeError(
×
660
        context,
661
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
662
      );
663
    }
664

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

667
    return { type: "number", value: erfc };
×
668
  }
669

670
  @Validate(2, 2, "math_comb", false)
671
  static math_comb(
6✔
672
    args: Value[],
673
    source: string,
674
    command: ControlItem,
675
    context: Context,
676
  ): BigIntValue {
677
    const n = args[0];
×
678
    const k = args[1];
×
679

680
    if (n.type !== "bigint") {
×
681
      handleRuntimeError(
×
682
        context,
683
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
684
      );
685
    } else if (k.type !== "bigint") {
×
686
      handleRuntimeError(
×
687
        context,
688
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
689
      );
690
    }
691

692
    const nVal = BigInt(n.value);
×
693
    const kVal = BigInt(k.value);
×
694

695
    if (nVal < 0 || kVal < 0) {
×
696
      handleRuntimeError(
×
697
        context,
698
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
699
      );
700
    }
701

702
    if (kVal > nVal) {
×
703
      return { type: "bigint", value: BigInt(0) };
×
704
    }
705

706
    let result: bigint = BigInt(1);
×
707
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
708

709
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
710
      result = (result * (nVal - i)) / (i + BigInt(1));
×
711
    }
712

713
    return { type: "bigint", value: result };
×
714
  }
715

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

725
    if (n.type !== "bigint") {
×
726
      handleRuntimeError(
×
727
        context,
728
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
729
      );
730
    }
731

732
    const nVal = BigInt(n.value);
×
733

734
    if (nVal < 0) {
×
735
      handleRuntimeError(
×
736
        context,
737
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
738
      );
739
    }
740

741
    // 0! = 1
742
    if (nVal === BigInt(0)) {
×
743
      return { type: "bigint", value: BigInt(1) };
×
744
    }
745

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

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

754
  static math_gcd(
755
    args: Value[],
756
    source: string,
757
    command: ControlItem,
758
    context: Context,
759
  ): BigIntValue {
760
    if (args.length === 0) {
×
761
      return { type: "bigint", value: BigInt(0) };
×
762
    }
763

764
    const values = args.map(v => {
×
765
      if (v.type !== "bigint") {
×
766
        handleRuntimeError(
×
767
          context,
768
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
769
        );
770
      }
771
      return BigInt(v.value);
×
772
    });
773

774
    const allZero = values.every(val => val === BigInt(0));
×
775
    if (allZero) {
×
776
      return { type: "bigint", value: BigInt(0) };
×
777
    }
778

779
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
780
    for (let i = 1; i < values.length; i++) {
×
781
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
782
      if (currentGcd === BigInt(1)) {
×
783
        break;
×
784
      }
785
    }
786

787
    return { type: "bigint", value: currentGcd };
×
788
  }
789

790
  static gcdOfTwo(a: bigint, b: bigint): bigint {
791
    let x: bigint = a;
×
792
    let y: bigint = b;
×
793
    while (y !== BigInt(0)) {
×
794
      const temp = x % y;
×
795
      x = y;
×
796
      y = temp;
×
797
    }
798
    return x < 0 ? -x : x;
×
799
  }
800

801
  @Validate(1, 1, "math_isqrt", false)
802
  static math_isqrt(
6✔
803
    args: Value[],
804
    source: string,
805
    command: ControlItem,
806
    context: Context,
807
  ): BigIntValue {
808
    const nValObj = args[0];
×
809
    if (nValObj.type !== "bigint") {
×
810
      handleRuntimeError(
×
811
        context,
812
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
813
      );
814
    }
815

816
    const n: bigint = nValObj.value;
×
817

818
    if (n < 0) {
×
819
      handleRuntimeError(
×
820
        context,
821
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
822
      );
823
    }
824

825
    if (n < 2) {
×
826
      return { type: "bigint", value: n };
×
827
    }
828

829
    let low: bigint = BigInt(1);
×
830
    let high: bigint = n;
×
831

832
    while (low < high) {
×
833
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
834
      const sq = mid * mid;
×
835
      if (sq <= n) {
×
836
        low = mid;
×
837
      } else {
838
        high = mid - BigInt(1);
×
839
      }
840
    }
841

842
    return { type: "bigint", value: low };
×
843
  }
844

845
  static math_lcm(
846
    args: Value[],
847
    source: string,
848
    command: ControlItem,
849
    context: Context,
850
  ): BigIntValue {
851
    if (args.length === 0) {
×
852
      return { type: "bigint", value: BigInt(1) };
×
853
    }
854

855
    const values = args.map(val => {
×
856
      if (val.type !== "bigint") {
×
857
        handleRuntimeError(
×
858
          context,
859
          new TypeError(source, command as ExprNS.Expr, context, val.type, "int"),
860
        );
861
      }
862
      return BigInt(val.value);
×
863
    });
864

865
    if (values.some(v => v === BigInt(0))) {
×
866
      return { type: "bigint", value: BigInt(0) };
×
867
    }
868

869
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
870
    for (let i = 1; i < values.length; i++) {
×
871
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
872
      if (currentLcm === BigInt(0)) {
×
873
        break;
×
874
      }
875
    }
876

877
    return { type: "bigint", value: currentLcm };
×
878
  }
879

880
  static lcmOfTwo(a: bigint, b: bigint): bigint {
881
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
882
    return BigInt((a / gcdVal) * b);
×
883
  }
884

885
  static absBigInt(x: bigint): bigint {
886
    return x < 0 ? -x : x;
×
887
  }
888

889
  @Validate(1, 2, "math_perm", true)
890
  static math_perm(
6✔
891
    args: Value[],
892
    source: string,
893
    command: ControlItem,
894
    context: Context,
895
  ): BigIntValue {
896
    const nValObj = args[0];
×
897
    if (nValObj.type !== "bigint") {
×
898
      handleRuntimeError(
×
899
        context,
900
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
901
      );
902
    }
903
    const n = BigInt(nValObj.value);
×
904

905
    let k = n;
×
906
    if (args.length === 2) {
×
907
      const kValObj = args[1];
×
908
      if (kValObj.type === "none") {
×
909
        k = n;
×
910
      } else if (kValObj.type === "bigint") {
×
911
        k = BigInt(kValObj.value);
×
912
      } else {
913
        handleRuntimeError(
×
914
          context,
915
          new TypeError(source, command as ExprNS.Expr, context, kValObj.type, "int' or 'None"),
916
        );
917
      }
918
    }
919

920
    if (n < 0 || k < 0) {
×
921
      handleRuntimeError(
×
922
        context,
923
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
924
      );
925
    }
926

927
    if (k > n) {
×
928
      return { type: "bigint", value: BigInt(0) };
×
929
    }
930

931
    let result: bigint = BigInt(1);
×
932
    for (let i: bigint = BigInt(0); i < k; i++) {
×
933
      result *= n - i;
×
934
    }
935

936
    return { type: "bigint", value: result };
×
937
  }
938

939
  @Validate(1, 1, "math_ceil", false)
940
  static math_ceil(
6✔
941
    args: Value[],
942
    source: string,
943
    command: ControlItem,
944
    context: Context,
945
  ): BigIntValue {
946
    const x = args[0];
×
947

948
    if (x.type === "bigint") {
×
949
      return x;
×
950
    }
951

952
    if (x.type === "number") {
×
953
      const numVal = x.value;
×
954
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
955
      return { type: "bigint", value: ceiled };
×
956
    }
957

958
    handleRuntimeError(
×
959
      context,
960
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
961
    );
962
  }
963

964
  @Validate(1, 1, "math_fabs", false)
965
  static math_fabs(
6✔
966
    args: Value[],
967
    source: string,
968
    command: ControlItem,
969
    context: Context,
970
  ): NumberValue {
971
    const x = args[0];
×
972

973
    if (x.type === "bigint") {
×
974
      const bigVal: bigint = BigInt(x.value);
×
975
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
976
      return { type: "number", value: absVal };
×
977
    }
978

979
    if (x.type === "number") {
×
980
      const numVal: number = x.value;
×
981
      if (typeof numVal !== "number") {
×
982
        handleRuntimeError(
×
983
          context,
984
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
985
        );
986
      }
987
      const absVal: number = Math.abs(numVal);
×
988
      return { type: "number", value: absVal };
×
989
    }
990

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

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

1006
    if (x.type === "bigint") {
×
1007
      return x;
×
1008
    }
1009

1010
    if (x.type === "number") {
×
1011
      const numVal: number = x.value;
×
1012
      if (typeof numVal !== "number") {
×
1013
        handleRuntimeError(
×
1014
          context,
1015
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1016
        );
1017
      }
1018
      const floored: bigint = BigInt(Math.floor(numVal));
×
1019
      return { type: "bigint", value: floored };
×
1020
    }
1021

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

1028
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
1029
  static twoProd(a: number, b: number): { prod: number; err: number } {
1030
    const prod = a * b;
×
1031
    const c = 134217729; // 2^27 + 1
×
1032
    const a_hi = a * c - (a * c - a);
×
1033
    const a_lo = a - a_hi;
×
1034
    const b_hi = b * c - (b * c - b);
×
1035
    const b_lo = b - b_hi;
×
1036
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
1037
    return { prod, err };
×
1038
  }
1039

1040
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
1041
  static twoSum(a: number, b: number): { sum: number; err: number } {
1042
    const sum = a + b;
×
1043
    const v = sum - a;
×
1044
    const err = a - (sum - v) + (b - v);
×
1045
    return { sum, err };
×
1046
  }
1047

1048
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1049
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1050
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1051
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1052
    const result = sum + (prodErr + sumErr);
×
1053
    return result;
×
1054
  }
1055

1056
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
1057
    if (val.type === "bigint") {
×
1058
      return Number(val.value);
×
1059
    } else if (val.type === "number") {
×
1060
      return val.value;
×
1061
    } else {
1062
      handleRuntimeError(
×
1063
        context,
1064
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
1065
      );
1066
    }
1067
  }
1068

1069
  @Validate(3, 3, "math_fma", false)
1070
  static math_fma(
6✔
1071
    args: Value[],
1072
    source: string,
1073
    command: ControlItem,
1074
    context: Context,
1075
  ): NumberValue {
1076
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1077
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1078
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1079

1080
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1081
    // and fma(inf, 0, nan) should return NaN.
1082
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1083
      return { type: "number", value: NaN };
×
1084
    }
1085
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1086
      return { type: "number", value: NaN };
×
1087
    }
1088
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1089
      return { type: "number", value: NaN };
×
1090
    }
1091

1092
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1093
    return { type: "number", value: result };
×
1094
  }
1095

1096
  @Validate(2, 2, "math_fmod", false)
1097
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1098
    // Convert inputs to numbers
1099
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1100
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1101

1102
    // Divisor cannot be zero
1103
    if (yVal === 0) {
×
1104
      handleRuntimeError(
×
1105
        context,
1106
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1107
      );
1108
    }
1109

1110
    // JavaScript's % operator behaves similarly to C's fmod
1111
    // in that the sign of the result is the same as the sign of x.
1112
    // For corner cases (NaN, Infinity), JavaScript remainder
1113
    // yields results consistent with typical C library fmod behavior.
1114
    const remainder = xVal % yVal;
×
1115

1116
    return { type: "number", value: remainder };
×
1117
  }
1118

1119
  static roundToEven(num: number): number {
1120
    //uses Banker's Rounding as per Python's Round() function
1121
    const floorVal = Math.floor(num);
×
1122
    const ceilVal = Math.ceil(num);
×
1123
    const diffFloor = num - floorVal;
×
1124
    const diffCeil = ceilVal - num;
×
1125
    if (diffFloor < diffCeil) {
×
1126
      return floorVal;
×
1127
    } else if (diffCeil < diffFloor) {
×
1128
      return ceilVal;
×
1129
    } else {
1130
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1131
    }
1132
  }
1133

1134
  @Validate(2, 2, "math_remainder", false)
1135
  static math_remainder(
6✔
1136
    args: Value[],
1137
    source: string,
1138
    command: ControlItem,
1139
    context: Context,
1140
  ): NumberValue {
1141
    const x = args[0];
×
1142
    const y = args[1];
×
1143

1144
    let xValue: number;
1145
    if (x.type === "bigint") {
×
1146
      xValue = Number(x.value);
×
1147
    } else if (x.type === "number") {
×
1148
      xValue = x.value;
×
1149
    } else {
1150
      handleRuntimeError(
×
1151
        context,
1152
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1153
      );
1154
    }
1155

1156
    let yValue: number;
1157
    if (y.type === "bigint") {
×
1158
      yValue = Number(y.value);
×
1159
    } else if (y.type === "number") {
×
1160
      yValue = y.value;
×
1161
    } else {
1162
      handleRuntimeError(
×
1163
        context,
1164
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1165
      );
1166
    }
1167

1168
    if (yValue === 0) {
×
1169
      handleRuntimeError(
×
1170
        context,
1171
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1172
      );
1173
    }
1174

1175
    const quotient = xValue / yValue;
×
1176
    const n = BuiltInFunctions.roundToEven(quotient);
×
1177
    const remainder = xValue - n * yValue;
×
1178

1179
    return { type: "number", value: remainder };
×
1180
  }
1181

1182
  @Validate(1, 1, "math_trunc", false)
1183
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1184
    const x = args[0];
×
1185

1186
    if (x.type === "bigint") {
×
1187
      return x;
×
1188
    }
1189

1190
    if (x.type === "number") {
×
1191
      const numVal: number = x.value;
×
1192
      if (typeof numVal !== "number") {
×
1193
        handleRuntimeError(
×
1194
          context,
1195
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1196
        );
1197
      }
1198
      let truncated: number;
1199
      if (numVal === 0) {
×
1200
        truncated = 0;
×
1201
      } else if (numVal < 0) {
×
1202
        truncated = Math.ceil(numVal);
×
1203
      } else {
1204
        truncated = Math.floor(numVal);
×
1205
      }
1206
      return { type: "bigint", value: BigInt(truncated) };
×
1207
    }
1208

1209
    handleRuntimeError(
×
1210
      context,
1211
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1212
    );
1213
  }
1214

1215
  @Validate(2, 2, "math_copysign", false)
1216
  static math_copysign(
6✔
1217
    args: Value[],
1218
    source: string,
1219
    command: ControlItem,
1220
    context: Context,
1221
  ): NumberValue {
1222
    const [x, y] = args;
×
1223

1224
    if (!isNumeric(x)) {
×
1225
      handleRuntimeError(
×
1226
        context,
1227
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1228
      );
1229
    } else if (!isNumeric(y)) {
×
1230
      handleRuntimeError(
×
1231
        context,
1232
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1233
      );
1234
    }
1235

1236
    const xVal = Number(x.value);
×
1237
    const yVal = Number(y.value);
×
1238

1239
    const absVal = Math.abs(xVal);
×
1240
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1241
    const result = isNegative ? -absVal : absVal;
×
1242

1243
    return { type: "number", value: Number(result) };
×
1244
  }
1245

1246
  @Validate(1, 1, "math_isfinite", false)
1247
  static math_isfinite(
6✔
1248
    args: Value[],
1249
    source: string,
1250
    command: ControlItem,
1251
    context: Context,
1252
  ): BoolValue {
1253
    const xValObj = args[0];
×
1254
    if (!isNumeric(xValObj)) {
×
1255
      handleRuntimeError(
×
1256
        context,
1257
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1258
      );
1259
    }
1260

1261
    const x = Number(xValObj.value);
×
1262
    const result: boolean = Number.isFinite(x);
×
1263

1264
    return { type: "bool", value: result };
×
1265
  }
1266

1267
  @Validate(1, 1, "math_isinf", false)
1268
  static math_isinf(
6✔
1269
    args: Value[],
1270
    source: string,
1271
    command: ControlItem,
1272
    context: Context,
1273
  ): BoolValue {
1274
    const xValObj = args[0];
×
1275
    if (!isNumeric(xValObj)) {
×
1276
      handleRuntimeError(
×
1277
        context,
1278
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1279
      );
1280
    }
1281

1282
    const x = Number(xValObj.value);
×
1283
    const result: boolean = x === Infinity || x === -Infinity;
×
1284

1285
    return { type: "bool", value: result };
×
1286
  }
1287

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

1303
    const x = Number(xValObj.value);
×
1304
    const result: boolean = Number.isNaN(x);
×
1305

1306
    return { type: "bool", value: result };
×
1307
  }
1308

1309
  @Validate(2, 2, "math_ldexp", false)
1310
  static math_ldexp(
6✔
1311
    args: Value[],
1312
    source: string,
1313
    command: ControlItem,
1314
    context: Context,
1315
  ): NumberValue {
1316
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1317

1318
    if (args[1].type !== "bigint") {
×
1319
      handleRuntimeError(
×
1320
        context,
1321
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1322
      );
1323
    }
1324
    const expVal = args[1].value;
×
1325

1326
    // Perform x * 2^expVal
1327
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1328
    // That behavior parallels typical C library rules for ldexp.
1329
    const result = xVal * Math.pow(2, Number(expVal));
×
1330

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

1334
  @Validate(2, 2, "math_nextafter", false)
1335
  static math_nextafter(
6✔
1336
    _args: Value[],
1337
    _source: string,
1338
    _command: ControlItem,
1339
    _context: Context,
1340
  ): Value {
1341
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1342
    throw new Error("math_nextafter not implemented");
×
1343
  }
1344

1345
  @Validate(1, 1, "math_ulp", false)
1346
  static math_ulp(
6✔
1347
    _args: Value[],
1348
    _source: string,
1349
    _command: ControlItem,
1350
    _context: Context,
1351
  ): Value {
1352
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1353
    throw new Error("math_ulp not implemented");
×
1354
  }
1355

1356
  @Validate(1, 1, "math_cbrt", false)
1357
  static math_cbrt(
6✔
1358
    args: Value[],
1359
    source: string,
1360
    command: ControlItem,
1361
    context: Context,
1362
  ): NumberValue {
1363
    const xVal = args[0];
×
1364
    let x: number;
1365

1366
    if (xVal.type !== "number") {
×
1367
      if (xVal.type === "bigint") {
×
1368
        x = Number(xVal.value);
×
1369
      } else {
1370
        handleRuntimeError(
×
1371
          context,
1372
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1373
        );
1374
      }
1375
    } else {
1376
      x = xVal.value;
×
1377
    }
1378

1379
    const result = Math.cbrt(x);
×
1380

1381
    return { type: "number", value: result };
×
1382
  }
1383

1384
  @Validate(1, 1, "math_exp", false)
1385
  static math_exp(
6✔
1386
    args: Value[],
1387
    source: string,
1388
    command: ControlItem,
1389
    context: Context,
1390
  ): NumberValue {
1391
    const xVal = args[0];
×
1392
    let x: number;
1393

1394
    if (xVal.type !== "number") {
×
1395
      if (xVal.type === "bigint") {
×
1396
        x = Number(xVal.value);
×
1397
      } else {
1398
        handleRuntimeError(
×
1399
          context,
1400
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1401
        );
1402
      }
1403
    } else {
1404
      x = xVal.value;
×
1405
    }
1406

1407
    const result = Math.exp(x);
×
1408
    return { type: "number", value: result };
×
1409
  }
1410

1411
  @Validate(1, 1, "math_exp2", false)
1412
  static math_exp2(
6✔
1413
    args: Value[],
1414
    source: string,
1415
    command: ControlItem,
1416
    context: Context,
1417
  ): NumberValue {
1418
    const xVal = args[0];
×
1419
    let x: number;
1420

1421
    if (xVal.type !== "number") {
×
1422
      if (xVal.type === "bigint") {
×
1423
        x = Number(xVal.value);
×
1424
      } else {
1425
        handleRuntimeError(
×
1426
          context,
1427
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1428
        );
1429
      }
1430
    } else {
1431
      x = xVal.value;
×
1432
    }
1433

1434
    const result = Math.pow(2, x);
×
1435
    return { type: "number", value: result };
×
1436
  }
1437

1438
  @Validate(1, 1, "math_expm1", false)
1439
  static math_expm1(
6✔
1440
    args: Value[],
1441
    source: string,
1442
    command: ControlItem,
1443
    context: Context,
1444
  ): NumberValue {
1445
    const x = args[0];
×
1446
    if (!isNumeric(x)) {
×
1447
      handleRuntimeError(
×
1448
        context,
1449
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1450
      );
1451
    }
1452

1453
    let num: number;
1454
    if (x.type === "number") {
×
1455
      num = x.value;
×
1456
    } else {
1457
      num = Number(x.value);
×
1458
    }
1459

1460
    const result = Math.expm1(num);
×
1461
    return { type: "number", value: result };
×
1462
  }
1463

1464
  @Validate(1, 1, "math_gamma", false)
1465
  static math_gamma(
6✔
1466
    args: Value[],
1467
    source: string,
1468
    command: ControlItem,
1469
    context: Context,
1470
  ): NumberValue {
1471
    const x = args[0];
×
1472
    if (!isNumeric(x)) {
×
1473
      handleRuntimeError(
×
1474
        context,
1475
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1476
      );
1477
    }
1478

1479
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1480
    const result = gamma(z);
×
1481

1482
    return { type: "number", value: result };
×
1483
  }
1484

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

1500
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1501
    const result = lgamma(z);
×
1502

1503
    return { type: "number", value: result };
×
1504
  }
1505

1506
  @Validate(1, 2, "math_log", true)
1507
  static math_log(
6✔
1508
    args: Value[],
1509
    source: string,
1510
    command: ControlItem,
1511
    context: Context,
1512
  ): NumberValue {
1513
    const x = args[0];
×
1514
    if (!isNumeric(x)) {
×
1515
      handleRuntimeError(
×
1516
        context,
1517
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1518
      );
1519
    }
1520
    let num: number;
1521
    if (x.type === "number") {
×
1522
      num = x.value;
×
1523
    } else {
1524
      num = Number(x.value);
×
1525
    }
1526

1527
    if (num <= 0) {
×
1528
      handleRuntimeError(
×
1529
        context,
1530
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1531
      );
1532
    }
1533

1534
    if (args.length === 1) {
×
1535
      return { type: "number", value: Math.log(num) };
×
1536
    }
1537

1538
    const baseArg = args[1];
×
1539
    if (!isNumeric(baseArg)) {
×
1540
      handleRuntimeError(
×
1541
        context,
1542
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
1543
      );
1544
    }
1545
    let baseNum: number;
1546
    if (baseArg.type === "number") {
×
1547
      baseNum = baseArg.value;
×
1548
    } else {
1549
      baseNum = Number(baseArg.value);
×
1550
    }
1551
    if (baseNum <= 0) {
×
1552
      handleRuntimeError(
×
1553
        context,
1554
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1555
      );
1556
    }
1557

1558
    const result = Math.log(num) / Math.log(baseNum);
×
1559
    return { type: "number", value: result };
×
1560
  }
1561

1562
  @Validate(1, 1, "math_log10", false)
1563
  static math_log10(
6✔
1564
    args: Value[],
1565
    source: string,
1566
    command: ControlItem,
1567
    context: Context,
1568
  ): NumberValue {
1569
    const x = args[0];
×
1570
    if (!isNumeric(x)) {
×
1571
      handleRuntimeError(
×
1572
        context,
1573
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1574
      );
1575
    }
1576
    let num: number;
1577
    if (x.type === "number") {
×
1578
      num = x.value;
×
1579
    } else {
1580
      num = Number(x.value);
×
1581
    }
1582
    if (num <= 0) {
×
1583
      handleRuntimeError(
×
1584
        context,
1585
        new ValueError(source, command as ExprNS.Expr, context, "math_log10"),
1586
      );
1587
    }
1588

1589
    const result = Math.log10(num);
×
1590
    return { type: "number", value: result };
×
1591
  }
1592

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

1620
    const result = Math.log1p(num);
×
1621
    return { type: "number", value: result };
×
1622
  }
1623

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

1651
    const result = Math.log2(num);
×
1652
    return { type: "number", value: result };
×
1653
  }
1654

1655
  @Validate(2, 2, "math_pow", false)
1656
  static math_pow(
6✔
1657
    args: Value[],
1658
    source: string,
1659
    command: ControlItem,
1660
    context: Context,
1661
  ): NumberValue {
1662
    const base = args[0];
×
1663
    const exp = args[1];
×
1664

1665
    if (!isNumeric(base)) {
×
1666
      handleRuntimeError(
×
1667
        context,
1668
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1669
      );
1670
    } else if (!isNumeric(exp)) {
×
1671
      handleRuntimeError(
×
1672
        context,
1673
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1674
      );
1675
    }
1676

1677
    let baseNum: number;
1678
    if (base.type === "number") {
×
1679
      baseNum = base.value;
×
1680
    } else {
1681
      baseNum = Number(base.value);
×
1682
    }
1683

1684
    let expNum: number;
1685
    if (exp.type === "number") {
×
1686
      expNum = exp.value;
×
1687
    } else {
1688
      expNum = Number(exp.value);
×
1689
    }
1690

1691
    const result = Math.pow(baseNum, expNum);
×
1692
    return { type: "number", value: result };
×
1693
  }
1694

1695
  @Validate(1, 1, "math_radians", false)
1696
  static math_radians(
6✔
1697
    args: Value[],
1698
    source: string,
1699
    command: ControlItem,
1700
    context: Context,
1701
  ): NumberValue {
1702
    const x = args[0];
×
1703
    if (!isNumeric(x)) {
×
1704
      handleRuntimeError(
×
1705
        context,
1706
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1707
      );
1708
    }
1709

1710
    let deg: number;
1711
    if (x.type === "number") {
×
1712
      deg = x.value;
×
1713
    } else {
1714
      deg = Number(x.value);
×
1715
    }
1716

1717
    const radians = (deg * Math.PI) / 180;
×
1718
    return { type: "number", value: radians };
×
1719
  }
1720

1721
  @Validate(1, 1, "math_sin", false)
1722
  static math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1723
    const x = args[0];
7✔
1724
    if (!isNumeric(x)) {
7✔
1725
      handleRuntimeError(
3✔
1726
        context,
1727
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1728
      );
1729
    }
1730

1731
    let num: number;
1732
    if (x.type === "number") {
4✔
1733
      num = x.value;
3✔
1734
    } else {
1735
      num = Number(x.value);
1✔
1736
    }
1737

1738
    const result = Math.sin(num);
4✔
1739
    return { type: "number", value: result };
4✔
1740
  }
1741

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

1752
    let num: number;
1753
    if (x.type === "number") {
×
1754
      num = x.value;
×
1755
    } else {
1756
      num = Number(x.value);
×
1757
    }
1758

1759
    const result = Math.sinh(num);
×
1760
    return { type: "number", value: result };
×
1761
  }
1762

1763
  @Validate(1, 1, "math_tan", false)
1764
  static math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1765
    const x = args[0];
×
1766
    if (!isNumeric(x)) {
×
1767
      handleRuntimeError(
×
1768
        context,
1769
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1770
      );
1771
    }
1772

1773
    let num: number;
1774
    if (x.type === "number") {
×
1775
      num = x.value;
×
1776
    } else {
1777
      num = Number(x.value);
×
1778
    }
1779

1780
    const result = Math.tan(num);
×
1781
    return { type: "number", value: result };
×
1782
  }
1783

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

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

1806
    const result = Math.tanh(num);
×
1807
    return { type: "number", value: result };
×
1808
  }
1809

1810
  @Validate(1, 1, "math_sqrt", false)
1811
  static math_sqrt(
6✔
1812
    args: Value[],
1813
    source: string,
1814
    command: ControlItem,
1815
    context: Context,
1816
  ): NumberValue {
1817
    const x = args[0];
×
1818
    if (!isNumeric(x)) {
×
1819
      handleRuntimeError(
×
1820
        context,
1821
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1822
      );
1823
    }
1824

1825
    let num: number;
1826
    if (x.type === "number") {
×
1827
      num = x.value;
×
1828
    } else {
1829
      num = Number(x.value);
×
1830
    }
1831

1832
    if (num < 0) {
×
1833
      handleRuntimeError(
×
1834
        context,
1835
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1836
      );
1837
    }
1838

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

1843
  @Validate(2, null, "max", true)
1844
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1845
    const numericTypes = ["bigint", "number"];
2✔
1846
    const firstType = args[0].type;
2✔
1847
    const isNumericValue = numericTypes.includes(firstType);
2✔
1848
    const isString = firstType === "string";
2✔
1849

1850
    for (let i = 1; i < args.length; i++) {
2✔
1851
      const t = args[i].type;
5✔
1852
      if (isNumericValue && !numericTypes.includes(t)) {
5!
1853
        handleRuntimeError(
×
1854
          context,
1855
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1856
        );
1857
      }
1858
      if (isString && t !== "string") {
5!
1859
        handleRuntimeError(
×
1860
          context,
1861
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1862
        );
1863
      }
1864
    }
1865

1866
    let useFloat = false;
2✔
1867
    if (isNumericValue) {
2✔
1868
      for (const arg of args) {
2✔
1869
        if (arg.type === "number") {
7!
1870
          useFloat = true;
×
1871
          break;
×
1872
        }
1873
      }
1874
    }
1875

1876
    let maxIndex = 0;
2✔
1877
    if (isNumericValue) {
2!
1878
      if (useFloat) {
2!
1879
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
1880
          handleRuntimeError(
×
1881
            context,
1882
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1883
          );
1884
        }
1885
        let maxVal: number = Number(args[0].value);
×
1886
        for (let i = 1; i < args.length; i++) {
×
1887
          const arg = args[i];
×
1888
          if (!isNumeric(arg)) {
×
1889
            handleRuntimeError(
×
1890
              context,
1891
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
1892
            );
1893
          }
1894
          const curr: number = Number(arg.value);
×
1895
          if (curr > maxVal) {
×
1896
            maxVal = curr;
×
1897
            maxIndex = i;
×
1898
          }
1899
        }
1900
      } else {
1901
        if (args[0].type !== "bigint") {
2!
1902
          handleRuntimeError(
×
1903
            context,
1904
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
1905
          );
1906
        }
1907
        let maxVal: bigint = args[0].value;
2✔
1908
        for (let i = 1; i < args.length; i++) {
2✔
1909
          const arg = args[i];
5✔
1910
          if (arg.type !== "bigint") {
5!
1911
            handleRuntimeError(
×
1912
              context,
1913
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
1914
            );
1915
          }
1916
          const curr: bigint = arg.value;
5✔
1917
          if (curr > maxVal) {
5✔
1918
            maxVal = curr;
5✔
1919
            maxIndex = i;
5✔
1920
          }
1921
        }
1922
      }
1923
    } else if (isString) {
×
1924
      if (args[0].type !== "string") {
×
1925
        handleRuntimeError(
×
1926
          context,
1927
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
1928
        );
1929
      }
1930
      let maxVal = args[0].value;
×
1931
      for (let i = 1; i < args.length; i++) {
×
1932
        const arg = args[i];
×
1933
        if (arg.type !== "string") {
×
1934
          handleRuntimeError(
×
1935
            context,
1936
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
1937
          );
1938
        }
1939
        const curr = arg.value;
×
1940
        if (curr > maxVal) {
×
1941
          maxVal = curr;
×
1942
          maxIndex = i;
×
1943
        }
1944
      }
1945
    } else {
1946
      // Won't happen
1947
      throw new Error(`max: unsupported type ${firstType}`);
×
1948
    }
1949

1950
    return args[maxIndex];
2✔
1951
  }
1952

1953
  @Validate(2, null, "min", true)
1954
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1955
    if (args.length < 2) {
×
1956
      handleRuntimeError(
×
1957
        context,
1958
        new MissingRequiredPositionalError(
1959
          source,
1960
          command as ExprNS.Expr,
1961
          "min",
1962
          Number(2),
1963
          args,
1964
          true,
1965
        ),
1966
      );
1967
    }
1968

1969
    const numericTypes = ["bigint", "number"];
×
1970
    const firstType = args[0].type;
×
1971
    const isNumericValue = numericTypes.includes(firstType);
×
1972
    const isString = firstType === "string";
×
1973

1974
    for (let i = 1; i < args.length; i++) {
×
1975
      const t = args[i].type;
×
1976
      if (isNumericValue && !numericTypes.includes(t)) {
×
1977
        handleRuntimeError(
×
1978
          context,
1979
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1980
        );
1981
      }
1982
      if (isString && t !== "string") {
×
1983
        handleRuntimeError(
×
1984
          context,
1985
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1986
        );
1987
      }
1988
    }
1989

1990
    let useFloat = false;
×
1991
    if (isNumericValue) {
×
1992
      for (const arg of args) {
×
1993
        if (arg.type === "number") {
×
1994
          useFloat = true;
×
1995
          break;
×
1996
        }
1997
      }
1998
    }
1999

2000
    let maxIndex = 0;
×
2001
    if (isNumericValue) {
×
2002
      if (useFloat) {
×
2003
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
2004
          handleRuntimeError(
×
2005
            context,
2006
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
2007
          );
2008
        }
2009
        let maxVal: number = Number(args[0].value);
×
2010
        for (let i = 1; i < args.length; i++) {
×
2011
          const arg = args[i];
×
2012
          if (!isNumeric(arg)) {
×
2013
            handleRuntimeError(
×
2014
              context,
2015
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
2016
            );
2017
          }
2018
          const curr: number = Number(arg.value);
×
2019
          if (curr < maxVal) {
×
2020
            maxVal = curr;
×
2021
            maxIndex = i;
×
2022
          }
2023
        }
2024
      } else {
2025
        if (args[0].type !== "bigint") {
×
2026
          handleRuntimeError(
×
2027
            context,
2028
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
2029
          );
2030
        }
2031
        let maxVal: bigint = args[0].value;
×
2032
        for (let i = 1; i < args.length; i++) {
×
2033
          const arg = args[i];
×
2034
          if (arg.type !== "bigint") {
×
2035
            handleRuntimeError(
×
2036
              context,
2037
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
2038
            );
2039
          }
2040
          const curr: bigint = arg.value;
×
2041
          if (curr < maxVal) {
×
2042
            maxVal = curr;
×
2043
            maxIndex = i;
×
2044
          }
2045
        }
2046
      }
2047
    } else if (isString) {
×
2048
      if (args[0].type !== "string") {
×
2049
        handleRuntimeError(
×
2050
          context,
2051
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
2052
        );
2053
      }
2054
      let maxVal = args[0].value;
×
2055
      for (let i = 1; i < args.length; i++) {
×
2056
        const arg = args[i];
×
2057
        if (arg.type !== "string") {
×
2058
          handleRuntimeError(
×
2059
            context,
2060
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
2061
          );
2062
        }
2063
        const curr = arg.value;
×
2064
        if (curr < maxVal) {
×
2065
          maxVal = curr;
×
2066
          maxIndex = i;
×
2067
        }
2068
      }
2069
    } else {
2070
      // Won't happen
2071
      throw new Error(`min: unsupported type ${firstType}`);
×
2072
    }
2073

2074
    return args[maxIndex];
×
2075
  }
2076

2077
  @Validate(null, 0, "random_random", true)
2078
  static random_random(
6✔
2079
    _args: Value[],
2080
    _source: string,
2081
    _command: ControlItem,
2082
    _context: Context,
2083
  ): NumberValue {
2084
    const result = Math.random();
×
2085
    return { type: "number", value: result };
×
2086
  }
2087

2088
  @Validate(1, 2, "round", true)
2089
  static round(
6✔
2090
    args: Value[],
2091
    source: string,
2092
    command: ControlItem,
2093
    context: Context,
2094
  ): NumberValue | BigIntValue {
2095
    const numArg = args[0];
15✔
2096
    if (!isNumeric(numArg)) {
15✔
2097
      handleRuntimeError(
2✔
2098
        context,
2099
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
2100
      );
2101
    }
2102

2103
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
2104
    if (args.length === 2 && args[1].type !== "none") {
13✔
2105
      if (args[1].type !== "bigint") {
7✔
2106
        handleRuntimeError(
1✔
2107
          context,
2108
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
2109
        );
2110
      }
2111
      ndigitsArg = args[1];
6✔
2112
    } else {
2113
      const shifted = Intl.NumberFormat("en-US", {
6✔
2114
        roundingMode: "halfEven",
2115
        useGrouping: false,
2116
        maximumFractionDigits: 0,
2117
      } as Intl.NumberFormatOptions).format(numArg.value);
2118
      return { type: "bigint", value: BigInt(shifted) };
6✔
2119
    }
2120

2121
    if (numArg.type === "number") {
6✔
2122
      const numberValue: number = numArg.value;
4✔
2123
      if (ndigitsArg.value >= 0) {
4✔
2124
        const shifted = Intl.NumberFormat("en-US", {
3✔
2125
          roundingMode: "halfEven",
2126
          useGrouping: false,
2127
          maximumFractionDigits: Number(ndigitsArg.value),
2128
        } as Intl.NumberFormatOptions).format(numberValue);
2129
        return { type: "number", value: Number(shifted) };
3✔
2130
      } else {
2131
        const shifted = Intl.NumberFormat("en-US", {
1✔
2132
          roundingMode: "halfEven",
2133
          useGrouping: false,
2134
          maximumFractionDigits: 0,
2135
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
2136
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
2137
      }
2138
    } else {
2139
      if (ndigitsArg.value >= 0) {
2!
2140
        return numArg;
2✔
2141
      } else {
2142
        const shifted = Intl.NumberFormat("en-US", {
×
2143
          roundingMode: "halfEven",
2144
          useGrouping: false,
2145
          maximumFractionDigits: 0,
2146
        } as Intl.NumberFormatOptions).format(
2147
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
2148
        );
2149
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
2150
      }
2151
    }
2152
  }
2153

2154
  @Validate(null, 0, "time_time", true)
2155
  static time_time(
6✔
2156
    _args: Value[],
2157
    _source: string,
2158
    _command: ControlItem,
2159
    _context: Context,
2160
  ): NumberValue {
2161
    const currentTime = Date.now();
×
2162
    return { type: "number", value: currentTime };
×
2163
  }
2164

2165
  @Validate(1, 1, "is_none", true)
2166
  static is_none(
6✔
2167
    args: Value[],
2168
    _source: string,
2169
    _command: ControlItem,
2170
    _context: Context,
2171
  ): BoolValue {
2172
    const obj = args[0];
365✔
2173
    return { type: "bool", value: obj.type === "none" };
365✔
2174
  }
2175

2176
  @Validate(1, 1, "is_float", true)
2177
  static is_float(
6✔
2178
    args: Value[],
2179
    _source: string,
2180
    _command: ControlItem,
2181
    _context: Context,
2182
  ): BoolValue {
2183
    const obj = args[0];
9✔
2184
    return { type: "bool", value: obj.type === "number" };
9✔
2185
  }
2186

2187
  @Validate(1, 1, "is_string", true)
2188
  static is_string(
6✔
2189
    args: Value[],
2190
    _source: string,
2191
    _command: ControlItem,
2192
    _context: Context,
2193
  ): BoolValue {
2194
    const obj = args[0];
18✔
2195
    return { type: "bool", value: obj.type === "string" };
18✔
2196
  }
2197

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

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

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

2231
  @Validate(1, 1, "is_function", true)
2232
  static is_function(
6✔
2233
    args: Value[],
2234
    _source: string,
2235
    _command: ControlItem,
2236
    _context: Context,
2237
  ): BoolValue {
2238
    const obj = args[0];
20✔
2239
    return {
20✔
2240
      type: "bool",
2241
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
58✔
2242
    };
2243
  }
2244

2245
  static async input(
2246
    _args: Value[],
2247
    _source: string,
2248
    _command: ControlItem,
2249
    context: Context,
2250
  ): Promise<Value> {
2251
    const userInput = await receiveInput(context);
×
2252
    return { type: "string", value: userInput };
×
2253
  }
2254

2255
  static async print(
2256
    args: Value[],
2257
    _source: string,
2258
    _command: ControlItem,
2259
    context: Context,
2260
  ): Promise<Value> {
2261
    const output = args.map(arg => toPythonString(arg)).join(" ");
3✔
2262
    await displayOutput(context, output);
3✔
2263
    return { type: "none" };
3✔
2264
  }
2265
  static str(
2266
    args: Value[],
2267
    _source: string,
2268
    _command: ControlItem,
2269
    _context: Context,
2270
  ): StringValue {
2271
    if (args.length === 0) {
12!
2272
      return { type: "string", value: "" };
×
2273
    }
2274
    const obj = args[0];
12✔
2275
    const result = toPythonString(obj);
12✔
2276
    return { type: "string", value: result };
12✔
2277
  }
2278
  @Validate(1, 1, "repr", true)
2279
  static repr(
6✔
2280
    args: Value[],
2281
    _source: string,
2282
    _command: ControlItem,
2283
    _context: Context,
2284
  ): StringValue {
2285
    const obj = args[0];
10✔
2286
    const result = toPythonString(obj, true);
10✔
2287
    return { type: "string", value: result };
10✔
2288
  }
2289
}
2290

2291
import { ExprNS } from "./ast-types";
2292
import { isFalsy } from "./cse-machine/operators";
6✔
2293
import { isNumeric } from "./cse-machine/utils";
6✔
2294
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2295
import { PyComplexNumber } from "./types";
6✔
2296

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

2301
/*
2302
    Create a map to hold built-in constants.
2303
    Each constant is stored with a string key and its corresponding value object.
2304
*/
2305
export const builtInConstants = new Map<string, Value>();
6✔
2306

2307
const constantMap = {
6✔
2308
  math_e: { type: "number", value: Math.E },
2309
  math_inf: { type: "number", value: Infinity },
2310
  math_nan: { type: "number", value: NaN },
2311
  math_pi: { type: "number", value: Math.PI },
2312
  math_tau: { type: "number", value: 2 * Math.PI },
2313
} as const;
2314

2315
for (const name of constants.constants) {
6✔
2316
  const valueObj = constantMap[name as keyof typeof constantMap];
30✔
2317
  if (!valueObj) {
30!
2318
    throw new Error(`Constant '${name}' is not implemented`);
×
2319
  }
2320
  builtInConstants.set(name, valueObj);
30✔
2321
}
2322

2323
/*
2324
    Create a map to hold built-in functions.
2325
    The keys are strings (function names) and the values are functions that can take any arguments.
2326
*/
2327
export const builtIns = new Map<string, BuiltinValue>();
6✔
2328
for (const name of constants.builtInFuncs) {
6✔
2329
  const impl = BuiltInFunctions[name as keyof BuiltInFunctions];
444✔
2330
  if (typeof impl !== "function") {
444!
2331
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
2332
  }
2333
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
444!
2334
  builtIns.set(name, {
444✔
2335
    type: "builtin",
2336
    name: builtinName,
2337
    func: impl,
2338
    minArgs: minArgMap.get(name) || 0,
516✔
2339
  });
2340
}
2341

2342
/**
2343
 * Converts a number to a string that mimics Python's float formatting behavior.
2344
 *
2345
 * In Python, float values are printed in scientific notation when their absolute value
2346
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
2347
 * so we explicitly enforce these formatting thresholds.
2348
 *
2349
 * The logic here is based on Python's internal `format_float_short` implementation
2350
 * in CPython's `pystrtod.c`:
2351
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
2352
 *
2353
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
2354
 * output matches Python’s display conventions.
2355
 */
2356
export function toPythonFloat(num: number): string {
6✔
2357
  if (Object.is(num, -0)) {
2!
2358
    return "-0.0";
×
2359
  }
2360
  if (num === 0) {
2!
2361
    return "0.0";
×
2362
  }
2363

2364
  if (num === Infinity) {
2!
2365
    return "inf";
×
2366
  }
2367
  if (num === -Infinity) {
2!
2368
    return "-inf";
×
2369
  }
2370

2371
  if (Number.isNaN(num)) {
2!
2372
    return "nan";
×
2373
  }
2374

2375
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
2376
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2377
  }
2378
  if (Number.isInteger(num)) {
2!
2379
    return num.toFixed(1).toString();
×
2380
  }
2381
  return num.toString();
2✔
2382
}
2383
function escape(str: string): string {
2384
  let escaped = JSON.stringify(str);
4✔
2385
  if (!(str.includes("'") && !str.includes('"'))) {
4✔
2386
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
4✔
2387
  }
2388
  return escaped;
4✔
2389
}
2390
export function toPythonString(obj: Value, repr: boolean = false): string {
6✔
2391
  let ret: string = "";
1,648✔
2392
  if (obj.type == "builtin") {
1,648!
2393
    return `<built-in function ${obj.name}>`;
×
2394
  }
2395
  if (obj.type === "bigint" || obj.type === "complex") {
1,648✔
2396
    ret = obj.value.toString();
25✔
2397
  } else if (obj.type === "number") {
1,623✔
2398
    ret = toPythonFloat(obj.value);
2✔
2399
  } else if (obj.type === "bool") {
1,621✔
2400
    if (obj.value) {
2!
2401
      return "True";
2✔
2402
    } else {
2403
      return "False";
×
2404
    }
2405
  } else if (obj.type === "error") {
1,619!
2406
    return obj.message;
×
2407
  } else if (obj.type === "closure") {
1,619✔
2408
    if (obj.closure.node) {
2✔
2409
      const funcName =
2410
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2411
      return `<function ${funcName}>`;
2✔
2412
    }
2413
  } else if (obj.type === "none") {
1,617✔
2414
    ret = "None";
1,607✔
2415
  } else if (obj.type === "string") {
10!
2416
    ret = repr ? escape(obj.value) : obj.value;
10✔
2417
  } else if (obj.type === "function") {
×
2418
    const funcName = obj.name || "(anonymous)";
×
2419
    ret = `<function ${funcName}>`;
×
2420
  } else if (obj.type === "list") {
×
2421
    ret = `[${obj.value.map(v => toPythonString(v, true)).join(", ")}]`;
×
2422
  } else {
2423
    ret = `<${obj.type} object>`;
×
2424
  }
2425
  return ret;
1,644✔
2426
}
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