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

source-academy / py-slang / 23791094507

31 Mar 2026 09:46AM UTC coverage: 62.934% (+2.5%) from 60.472%
23791094507

Pull #129

github

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

781 of 1453 branches covered (53.75%)

Branch coverage included in aggregate %.

69 of 112 new or added lines in 8 files covered. (61.61%)

3 existing lines in 2 files now uncovered.

2552 of 3843 relevant lines covered (66.41%)

3449.79 hits per line

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

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

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

26
export function Validate<T extends Value | Promise<Value>>(
6✔
27
  minArgs: number | null,
28
  maxArgs: number | null,
29
  functionName: string,
30
  strict: boolean,
31
) {
32
  return function (
432✔
33
    _target: unknown,
34
    _propertyKey: string,
35
    descriptor: TypedPropertyDescriptor<
36
      (args: Value[], source: string, command: ControlItem, context: Context) => T
37
    >,
38
  ): void {
39
    const originalMethod = descriptor.value!;
432✔
40
    minArgMap.set(functionName, minArgs || 0);
432✔
41
    descriptor.value = function (
432✔
42
      args: Value[],
43
      source: string,
44
      command: ControlItem,
45
      context: Context,
46
    ): T {
47
      if (minArgs !== null && args.length < minArgs) {
2,092✔
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,087✔
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,085✔
76
    };
77
  };
78
}
79

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

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

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

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

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

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

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

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

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

184
  @Validate(null, 1, "float", true)
185
  static float(args: Value[], source: string, command: ControlItem, context: Context): NumberValue {
6✔
186
    if (args.length === 0) {
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
  @Validate(1, 1, "len", true)
314
  static len(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
315
    const val = args[0];
14✔
316
    if (val.type === "string" || val.type === "list") {
14✔
317
      // The spread operator is used to count the number of Unicode code points
318
      // in the string
319
      return { type: "bigint", value: BigInt([...val.value].length) };
7✔
320
    }
321
    handleRuntimeError(
7✔
322
      context,
323
      new TypeError(source, command as ExprNS.Expr, context, val.type, "object with length"),
324
    );
325
  }
326

327
  static toStr(val: Value): string {
328
    return toPythonString(val);
2✔
329
  }
330

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

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

351
    let num: number;
352
    if (x.type === "number") {
×
353
      num = x.value;
×
354
    } else {
355
      num = Number(x.value);
×
356
    }
357

358
    if (num < -1 || num > 1) {
×
359
      handleRuntimeError(
×
360
        context,
361
        new ValueError(source, command as ExprNS.Expr, context, "math_acos"),
362
      );
363
    }
364

365
    const result = Math.acos(num);
×
366
    return { type: "number", value: result };
×
367
  }
368

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

378
    if (!isNumeric(x)) {
×
379
      handleRuntimeError(
×
380
        context,
381
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
382
      );
383
    }
384

385
    let num: number;
386
    if (x.type === "number") {
×
387
      num = x.value;
×
388
    } else {
389
      num = Number(x.value);
×
390
    }
391

392
    if (num < 1) {
×
393
      handleRuntimeError(
×
394
        context,
395
        new ValueError(source, command as ExprNS.Expr, context, "math_acosh"),
396
      );
397
    }
398

399
    const result = Math.acosh(num);
×
400
    return { type: "number", value: result };
×
401
  }
402

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

418
    let num: number;
419
    if (x.type === "number") {
×
420
      num = x.value;
×
421
    } else {
422
      num = Number(x.value);
×
423
    }
424

425
    if (num < -1 || num > 1) {
×
426
      handleRuntimeError(
×
427
        context,
428
        new ValueError(source, command as ExprNS.Expr, context, "math_asin"),
429
      );
430
    }
431

432
    const result = Math.asin(num);
×
433
    return { type: "number", value: result };
×
434
  }
435

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

451
    let num: number;
452
    if (x.type === "number") {
×
453
      num = x.value;
×
454
    } else {
455
      num = Number(x.value);
×
456
    }
457

458
    const result = Math.asinh(num);
×
459
    return { type: "number", value: result };
×
460
  }
461

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

477
    let num: number;
478
    if (x.type === "number") {
×
479
      num = x.value;
×
480
    } else {
481
      num = Number(x.value);
×
482
    }
483

484
    const result = Math.atan(num);
×
485
    return { type: "number", value: result };
×
486
  }
487

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

509
    let yNum: number, xNum: number;
510
    if (y.type === "number") {
×
511
      yNum = y.value;
×
512
    } else {
513
      yNum = Number(y.value);
×
514
    }
515

516
    if (x.type === "number") {
×
517
      xNum = x.value;
×
518
    } else {
519
      xNum = Number(x.value);
×
520
    }
521

522
    const result = Math.atan2(yNum, xNum);
×
523
    return { type: "number", value: result };
×
524
  }
525

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

541
    let num: number;
542
    if (x.type === "number") {
×
543
      num = x.value;
×
544
    } else {
545
      num = Number(x.value);
×
546
    }
547

548
    if (num <= -1 || num >= 1) {
×
549
      handleRuntimeError(
×
550
        context,
551
        new ValueError(source, command as ExprNS.Expr, context, "math_atanh"),
552
      );
553
    }
554

555
    const result = Math.atanh(num);
×
556
    return { type: "number", value: result };
×
557
  }
558

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

574
    let num: number;
575
    if (x.type === "number") {
4✔
576
      num = x.value;
3✔
577
    } else {
578
      num = Number(x.value);
1✔
579
    }
580

581
    const result = Math.cos(num);
4✔
582
    return { type: "number", value: result };
4✔
583
  }
584

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

600
    let num: number;
601
    if (x.type === "number") {
×
602
      num = x.value;
×
603
    } else {
604
      num = Number(x.value);
×
605
    }
606

607
    const result = Math.cosh(num);
×
608
    return { type: "number", value: result };
×
609
  }
610

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

626
    let num: number;
627
    if (x.type === "number") {
×
628
      num = x.value;
×
629
    } else {
630
      num = Number(x.value);
×
631
    }
632

633
    const result = (num * 180) / Math.PI;
×
634
    return { type: "number", value: result };
×
635
  }
636

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

652
    let num: number;
653
    if (x.type === "number") {
×
654
      num = x.value;
×
655
    } else {
656
      num = Number(x.value);
×
657
    }
658

659
    const erfnum = erf(num);
×
660

661
    return { type: "number", value: erfnum };
×
662
  }
663

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

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

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

684
  @Validate(2, 2, "math_comb", false)
685
  static math_comb(
6✔
686
    args: Value[],
687
    source: string,
688
    command: ControlItem,
689
    context: Context,
690
  ): BigIntValue {
691
    const n = args[0];
×
692
    const k = args[1];
×
693

694
    if (n.type !== "bigint") {
×
695
      handleRuntimeError(
×
696
        context,
697
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
698
      );
699
    } else if (k.type !== "bigint") {
×
700
      handleRuntimeError(
×
701
        context,
702
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
703
      );
704
    }
705

706
    const nVal = BigInt(n.value);
×
707
    const kVal = BigInt(k.value);
×
708

709
    if (nVal < 0 || kVal < 0) {
×
710
      handleRuntimeError(
×
711
        context,
712
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
713
      );
714
    }
715

716
    if (kVal > nVal) {
×
717
      return { type: "bigint", value: BigInt(0) };
×
718
    }
719

720
    let result: bigint = BigInt(1);
×
721
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
722

723
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
724
      result = (result * (nVal - i)) / (i + BigInt(1));
×
725
    }
726

727
    return { type: "bigint", value: result };
×
728
  }
729

730
  @Validate(1, 1, "math_factorial", false)
731
  static math_factorial(
6✔
732
    args: Value[],
733
    source: string,
734
    command: ControlItem,
735
    context: Context,
736
  ): BigIntValue {
737
    const n = args[0];
×
738

739
    if (n.type !== "bigint") {
×
740
      handleRuntimeError(
×
741
        context,
742
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
743
      );
744
    }
745

746
    const nVal = BigInt(n.value);
×
747

748
    if (nVal < 0) {
×
749
      handleRuntimeError(
×
750
        context,
751
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
752
      );
753
    }
754

755
    // 0! = 1
756
    if (nVal === BigInt(0)) {
×
757
      return { type: "bigint", value: BigInt(1) };
×
758
    }
759

760
    let result: bigint = BigInt(1);
×
761
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
762
      result *= i;
×
763
    }
764

765
    return { type: "bigint", value: result };
×
766
  }
767

768
  static math_gcd(
769
    args: Value[],
770
    source: string,
771
    command: ControlItem,
772
    context: Context,
773
  ): BigIntValue {
774
    if (args.length === 0) {
×
775
      return { type: "bigint", value: BigInt(0) };
×
776
    }
777

778
    const values = args.map(v => {
×
779
      if (v.type !== "bigint") {
×
780
        handleRuntimeError(
×
781
          context,
782
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
783
        );
784
      }
785
      return BigInt(v.value);
×
786
    });
787

788
    const allZero = values.every(val => val === BigInt(0));
×
789
    if (allZero) {
×
790
      return { type: "bigint", value: BigInt(0) };
×
791
    }
792

793
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
794
    for (let i = 1; i < values.length; i++) {
×
795
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
796
      if (currentGcd === BigInt(1)) {
×
797
        break;
×
798
      }
799
    }
800

801
    return { type: "bigint", value: currentGcd };
×
802
  }
803

804
  static gcdOfTwo(a: bigint, b: bigint): bigint {
805
    let x: bigint = a;
×
806
    let y: bigint = b;
×
807
    while (y !== BigInt(0)) {
×
808
      const temp = x % y;
×
809
      x = y;
×
810
      y = temp;
×
811
    }
812
    return x < 0 ? -x : x;
×
813
  }
814

815
  @Validate(1, 1, "math_isqrt", false)
816
  static math_isqrt(
6✔
817
    args: Value[],
818
    source: string,
819
    command: ControlItem,
820
    context: Context,
821
  ): BigIntValue {
822
    const nValObj = args[0];
×
823
    if (nValObj.type !== "bigint") {
×
824
      handleRuntimeError(
×
825
        context,
826
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
827
      );
828
    }
829

830
    const n: bigint = nValObj.value;
×
831

832
    if (n < 0) {
×
833
      handleRuntimeError(
×
834
        context,
835
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
836
      );
837
    }
838

839
    if (n < 2) {
×
840
      return { type: "bigint", value: n };
×
841
    }
842

843
    let low: bigint = BigInt(1);
×
844
    let high: bigint = n;
×
845

846
    while (low < high) {
×
847
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
848
      const sq = mid * mid;
×
849
      if (sq <= n) {
×
850
        low = mid;
×
851
      } else {
852
        high = mid - BigInt(1);
×
853
      }
854
    }
855

856
    return { type: "bigint", value: low };
×
857
  }
858

859
  static math_lcm(
860
    args: Value[],
861
    source: string,
862
    command: ControlItem,
863
    context: Context,
864
  ): BigIntValue {
865
    if (args.length === 0) {
×
866
      return { type: "bigint", value: BigInt(1) };
×
867
    }
868

869
    const values = args.map(val => {
×
870
      if (val.type !== "bigint") {
×
871
        handleRuntimeError(
×
872
          context,
873
          new TypeError(source, command as ExprNS.Expr, context, val.type, "int"),
874
        );
875
      }
876
      return BigInt(val.value);
×
877
    });
878

879
    if (values.some(v => v === BigInt(0))) {
×
880
      return { type: "bigint", value: BigInt(0) };
×
881
    }
882

883
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
884
    for (let i = 1; i < values.length; i++) {
×
885
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
886
      if (currentLcm === BigInt(0)) {
×
887
        break;
×
888
      }
889
    }
890

891
    return { type: "bigint", value: currentLcm };
×
892
  }
893

894
  static lcmOfTwo(a: bigint, b: bigint): bigint {
895
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
896
    return BigInt((a / gcdVal) * b);
×
897
  }
898

899
  static absBigInt(x: bigint): bigint {
900
    return x < 0 ? -x : x;
×
901
  }
902

903
  @Validate(1, 2, "math_perm", true)
904
  static math_perm(
6✔
905
    args: Value[],
906
    source: string,
907
    command: ControlItem,
908
    context: Context,
909
  ): BigIntValue {
910
    const nValObj = args[0];
×
911
    if (nValObj.type !== "bigint") {
×
912
      handleRuntimeError(
×
913
        context,
914
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
915
      );
916
    }
917
    const n = BigInt(nValObj.value);
×
918

919
    let k = n;
×
920
    if (args.length === 2) {
×
921
      const kValObj = args[1];
×
922
      if (kValObj.type === "none") {
×
923
        k = n;
×
924
      } else if (kValObj.type === "bigint") {
×
925
        k = BigInt(kValObj.value);
×
926
      } else {
927
        handleRuntimeError(
×
928
          context,
929
          new TypeError(source, command as ExprNS.Expr, context, kValObj.type, "int' or 'None"),
930
        );
931
      }
932
    }
933

934
    if (n < 0 || k < 0) {
×
935
      handleRuntimeError(
×
936
        context,
937
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
938
      );
939
    }
940

941
    if (k > n) {
×
942
      return { type: "bigint", value: BigInt(0) };
×
943
    }
944

945
    let result: bigint = BigInt(1);
×
946
    for (let i: bigint = BigInt(0); i < k; i++) {
×
947
      result *= n - i;
×
948
    }
949

950
    return { type: "bigint", value: result };
×
951
  }
952

953
  @Validate(1, 1, "math_ceil", false)
954
  static math_ceil(
6✔
955
    args: Value[],
956
    source: string,
957
    command: ControlItem,
958
    context: Context,
959
  ): BigIntValue {
960
    const x = args[0];
×
961

962
    if (x.type === "bigint") {
×
963
      return x;
×
964
    }
965

966
    if (x.type === "number") {
×
967
      const numVal = x.value;
×
968
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
969
      return { type: "bigint", value: ceiled };
×
970
    }
971

972
    handleRuntimeError(
×
973
      context,
974
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
975
    );
976
  }
977

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

987
    if (x.type === "bigint") {
×
988
      const bigVal: bigint = BigInt(x.value);
×
989
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
990
      return { type: "number", value: absVal };
×
991
    }
992

993
    if (x.type === "number") {
×
994
      const numVal: number = x.value;
×
995
      if (typeof numVal !== "number") {
×
996
        handleRuntimeError(
×
997
          context,
998
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
999
        );
1000
      }
1001
      const absVal: number = Math.abs(numVal);
×
1002
      return { type: "number", value: absVal };
×
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_floor", false)
1012
  static math_floor(
6✔
1013
    args: Value[],
1014
    source: string,
1015
    command: ControlItem,
1016
    context: Context,
1017
  ): BigIntValue {
1018
    const x = args[0];
×
1019

1020
    if (x.type === "bigint") {
×
1021
      return x;
×
1022
    }
1023

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

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

1042
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
1043
  static twoProd(a: number, b: number): { prod: number; err: number } {
1044
    const prod = a * b;
×
1045
    const c = 134217729; // 2^27 + 1
×
1046
    const a_hi = a * c - (a * c - a);
×
1047
    const a_lo = a - a_hi;
×
1048
    const b_hi = b * c - (b * c - b);
×
1049
    const b_lo = b - b_hi;
×
1050
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
1051
    return { prod, err };
×
1052
  }
1053

1054
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
1055
  static twoSum(a: number, b: number): { sum: number; err: number } {
1056
    const sum = a + b;
×
1057
    const v = sum - a;
×
1058
    const err = a - (sum - v) + (b - v);
×
1059
    return { sum, err };
×
1060
  }
1061

1062
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1063
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1064
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1065
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1066
    const result = sum + (prodErr + sumErr);
×
1067
    return result;
×
1068
  }
1069

1070
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
1071
    if (val.type === "bigint") {
×
1072
      return Number(val.value);
×
1073
    } else if (val.type === "number") {
×
1074
      return val.value;
×
1075
    } else {
1076
      handleRuntimeError(
×
1077
        context,
1078
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
1079
      );
1080
    }
1081
  }
1082

1083
  @Validate(3, 3, "math_fma", false)
1084
  static math_fma(
6✔
1085
    args: Value[],
1086
    source: string,
1087
    command: ControlItem,
1088
    context: Context,
1089
  ): NumberValue {
1090
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1091
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1092
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1093

1094
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1095
    // and fma(inf, 0, nan) should return NaN.
1096
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1097
      return { type: "number", value: NaN };
×
1098
    }
1099
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1100
      return { type: "number", value: NaN };
×
1101
    }
1102
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1103
      return { type: "number", value: NaN };
×
1104
    }
1105

1106
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1107
    return { type: "number", value: result };
×
1108
  }
1109

1110
  @Validate(2, 2, "math_fmod", false)
1111
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1112
    // Convert inputs to numbers
1113
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1114
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1115

1116
    // Divisor cannot be zero
1117
    if (yVal === 0) {
×
1118
      handleRuntimeError(
×
1119
        context,
1120
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1121
      );
1122
    }
1123

1124
    // JavaScript's % operator behaves similarly to C's fmod
1125
    // in that the sign of the result is the same as the sign of x.
1126
    // For corner cases (NaN, Infinity), JavaScript remainder
1127
    // yields results consistent with typical C library fmod behavior.
1128
    const remainder = xVal % yVal;
×
1129

1130
    return { type: "number", value: remainder };
×
1131
  }
1132

1133
  static roundToEven(num: number): number {
1134
    //uses Banker's Rounding as per Python's Round() function
1135
    const floorVal = Math.floor(num);
×
1136
    const ceilVal = Math.ceil(num);
×
1137
    const diffFloor = num - floorVal;
×
1138
    const diffCeil = ceilVal - num;
×
1139
    if (diffFloor < diffCeil) {
×
1140
      return floorVal;
×
1141
    } else if (diffCeil < diffFloor) {
×
1142
      return ceilVal;
×
1143
    } else {
1144
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1145
    }
1146
  }
1147

1148
  @Validate(2, 2, "math_remainder", false)
1149
  static math_remainder(
6✔
1150
    args: Value[],
1151
    source: string,
1152
    command: ControlItem,
1153
    context: Context,
1154
  ): NumberValue {
1155
    const x = args[0];
×
1156
    const y = args[1];
×
1157

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

1170
    let yValue: number;
1171
    if (y.type === "bigint") {
×
1172
      yValue = Number(y.value);
×
1173
    } else if (y.type === "number") {
×
1174
      yValue = y.value;
×
1175
    } else {
1176
      handleRuntimeError(
×
1177
        context,
1178
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1179
      );
1180
    }
1181

1182
    if (yValue === 0) {
×
1183
      handleRuntimeError(
×
1184
        context,
1185
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1186
      );
1187
    }
1188

1189
    const quotient = xValue / yValue;
×
1190
    const n = BuiltInFunctions.roundToEven(quotient);
×
1191
    const remainder = xValue - n * yValue;
×
1192

1193
    return { type: "number", value: remainder };
×
1194
  }
1195

1196
  @Validate(1, 1, "math_trunc", false)
1197
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1198
    const x = args[0];
×
1199

1200
    if (x.type === "bigint") {
×
1201
      return x;
×
1202
    }
1203

1204
    if (x.type === "number") {
×
1205
      const numVal: number = x.value;
×
1206
      if (typeof numVal !== "number") {
×
1207
        handleRuntimeError(
×
1208
          context,
1209
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1210
        );
1211
      }
1212
      let truncated: number;
1213
      if (numVal === 0) {
×
1214
        truncated = 0;
×
1215
      } else if (numVal < 0) {
×
1216
        truncated = Math.ceil(numVal);
×
1217
      } else {
1218
        truncated = Math.floor(numVal);
×
1219
      }
1220
      return { type: "bigint", value: BigInt(truncated) };
×
1221
    }
1222

1223
    handleRuntimeError(
×
1224
      context,
1225
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1226
    );
1227
  }
1228

1229
  @Validate(2, 2, "math_copysign", false)
1230
  static math_copysign(
6✔
1231
    args: Value[],
1232
    source: string,
1233
    command: ControlItem,
1234
    context: Context,
1235
  ): NumberValue {
1236
    const [x, y] = args;
×
1237

1238
    if (!isNumeric(x)) {
×
1239
      handleRuntimeError(
×
1240
        context,
1241
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1242
      );
1243
    } else if (!isNumeric(y)) {
×
1244
      handleRuntimeError(
×
1245
        context,
1246
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1247
      );
1248
    }
1249

1250
    const xVal = Number(x.value);
×
1251
    const yVal = Number(y.value);
×
1252

1253
    const absVal = Math.abs(xVal);
×
1254
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1255
    const result = isNegative ? -absVal : absVal;
×
1256

1257
    return { type: "number", value: Number(result) };
×
1258
  }
1259

1260
  @Validate(1, 1, "math_isfinite", false)
1261
  static math_isfinite(
6✔
1262
    args: Value[],
1263
    source: string,
1264
    command: ControlItem,
1265
    context: Context,
1266
  ): BoolValue {
1267
    const xValObj = args[0];
×
1268
    if (!isNumeric(xValObj)) {
×
1269
      handleRuntimeError(
×
1270
        context,
1271
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1272
      );
1273
    }
1274

1275
    const x = Number(xValObj.value);
×
1276
    const result: boolean = Number.isFinite(x);
×
1277

1278
    return { type: "bool", value: result };
×
1279
  }
1280

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

1296
    const x = Number(xValObj.value);
×
1297
    const result: boolean = x === Infinity || x === -Infinity;
×
1298

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

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

1317
    const x = Number(xValObj.value);
×
1318
    const result: boolean = Number.isNaN(x);
×
1319

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

1323
  @Validate(2, 2, "math_ldexp", false)
1324
  static math_ldexp(
6✔
1325
    args: Value[],
1326
    source: string,
1327
    command: ControlItem,
1328
    context: Context,
1329
  ): NumberValue {
1330
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1331

1332
    if (args[1].type !== "bigint") {
×
1333
      handleRuntimeError(
×
1334
        context,
1335
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1336
      );
1337
    }
1338
    const expVal = args[1].value;
×
1339

1340
    // Perform x * 2^expVal
1341
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1342
    // That behavior parallels typical C library rules for ldexp.
1343
    const result = xVal * Math.pow(2, Number(expVal));
×
1344

1345
    return { type: "number", value: result };
×
1346
  }
1347

1348
  @Validate(2, 2, "math_nextafter", false)
1349
  static math_nextafter(
6✔
1350
    _args: Value[],
1351
    _source: string,
1352
    _command: ControlItem,
1353
    _context: Context,
1354
  ): Value {
1355
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1356
    throw new Error("math_nextafter not implemented");
×
1357
  }
1358

1359
  @Validate(1, 1, "math_ulp", false)
1360
  static math_ulp(
6✔
1361
    _args: Value[],
1362
    _source: string,
1363
    _command: ControlItem,
1364
    _context: Context,
1365
  ): Value {
1366
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1367
    throw new Error("math_ulp not implemented");
×
1368
  }
1369

1370
  @Validate(1, 1, "math_cbrt", false)
1371
  static math_cbrt(
6✔
1372
    args: Value[],
1373
    source: string,
1374
    command: ControlItem,
1375
    context: Context,
1376
  ): NumberValue {
1377
    const xVal = args[0];
×
1378
    let x: number;
1379

1380
    if (xVal.type !== "number") {
×
1381
      if (xVal.type === "bigint") {
×
1382
        x = Number(xVal.value);
×
1383
      } else {
1384
        handleRuntimeError(
×
1385
          context,
1386
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1387
        );
1388
      }
1389
    } else {
1390
      x = xVal.value;
×
1391
    }
1392

1393
    const result = Math.cbrt(x);
×
1394

1395
    return { type: "number", value: result };
×
1396
  }
1397

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

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

1421
    const result = Math.exp(x);
×
1422
    return { type: "number", value: result };
×
1423
  }
1424

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

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

1448
    const result = Math.pow(2, x);
×
1449
    return { type: "number", value: result };
×
1450
  }
1451

1452
  @Validate(1, 1, "math_expm1", false)
1453
  static math_expm1(
6✔
1454
    args: Value[],
1455
    source: string,
1456
    command: ControlItem,
1457
    context: Context,
1458
  ): NumberValue {
1459
    const x = args[0];
×
1460
    if (!isNumeric(x)) {
×
1461
      handleRuntimeError(
×
1462
        context,
1463
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1464
      );
1465
    }
1466

1467
    let num: number;
1468
    if (x.type === "number") {
×
1469
      num = x.value;
×
1470
    } else {
1471
      num = Number(x.value);
×
1472
    }
1473

1474
    const result = Math.expm1(num);
×
1475
    return { type: "number", value: result };
×
1476
  }
1477

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

1493
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1494
    const result = gamma(z);
×
1495

1496
    return { type: "number", value: result };
×
1497
  }
1498

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

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

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

1520
  @Validate(1, 2, "math_log", true)
1521
  static math_log(
6✔
1522
    args: Value[],
1523
    source: string,
1524
    command: ControlItem,
1525
    context: Context,
1526
  ): NumberValue {
1527
    const x = args[0];
×
1528
    if (!isNumeric(x)) {
×
1529
      handleRuntimeError(
×
1530
        context,
1531
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1532
      );
1533
    }
1534
    let num: number;
1535
    if (x.type === "number") {
×
1536
      num = x.value;
×
1537
    } else {
1538
      num = Number(x.value);
×
1539
    }
1540

1541
    if (num <= 0) {
×
1542
      handleRuntimeError(
×
1543
        context,
1544
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1545
      );
1546
    }
1547

1548
    if (args.length === 1) {
×
1549
      return { type: "number", value: Math.log(num) };
×
1550
    }
1551

1552
    const baseArg = args[1];
×
1553
    if (!isNumeric(baseArg)) {
×
1554
      handleRuntimeError(
×
1555
        context,
1556
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
1557
      );
1558
    }
1559
    let baseNum: number;
1560
    if (baseArg.type === "number") {
×
1561
      baseNum = baseArg.value;
×
1562
    } else {
1563
      baseNum = Number(baseArg.value);
×
1564
    }
1565
    if (baseNum <= 0) {
×
1566
      handleRuntimeError(
×
1567
        context,
1568
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1569
      );
1570
    }
1571

1572
    const result = Math.log(num) / Math.log(baseNum);
×
1573
    return { type: "number", value: result };
×
1574
  }
1575

1576
  @Validate(1, 1, "math_log10", false)
1577
  static math_log10(
6✔
1578
    args: Value[],
1579
    source: string,
1580
    command: ControlItem,
1581
    context: Context,
1582
  ): NumberValue {
1583
    const x = args[0];
×
1584
    if (!isNumeric(x)) {
×
1585
      handleRuntimeError(
×
1586
        context,
1587
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1588
      );
1589
    }
1590
    let num: number;
1591
    if (x.type === "number") {
×
1592
      num = x.value;
×
1593
    } else {
1594
      num = Number(x.value);
×
1595
    }
1596
    if (num <= 0) {
×
1597
      handleRuntimeError(
×
1598
        context,
1599
        new ValueError(source, command as ExprNS.Expr, context, "math_log10"),
1600
      );
1601
    }
1602

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

1607
  @Validate(1, 1, "math_log1p", false)
1608
  static math_log1p(
6✔
1609
    args: Value[],
1610
    source: string,
1611
    command: ControlItem,
1612
    context: Context,
1613
  ): NumberValue {
1614
    const x = args[0];
×
1615
    if (!isNumeric(x)) {
×
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 (1 + num <= 0) {
×
1628
      handleRuntimeError(
×
1629
        context,
1630
        new ValueError(source, command as ExprNS.Expr, context, "math_log1p"),
1631
      );
1632
    }
1633

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

1638
  @Validate(1, 1, "math_log2", false)
1639
  static math_log2(
6✔
1640
    args: Value[],
1641
    source: string,
1642
    command: ControlItem,
1643
    context: Context,
1644
  ): NumberValue {
1645
    const x = args[0];
×
1646
    if (!isNumeric(x)) {
×
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 (num <= 0) {
×
1659
      handleRuntimeError(
×
1660
        context,
1661
        new ValueError(source, command as ExprNS.Expr, context, "math_log2"),
1662
      );
1663
    }
1664

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

1669
  @Validate(2, 2, "math_pow", false)
1670
  static math_pow(
6✔
1671
    args: Value[],
1672
    source: string,
1673
    command: ControlItem,
1674
    context: Context,
1675
  ): NumberValue {
1676
    const base = args[0];
×
1677
    const exp = args[1];
×
1678

1679
    if (!isNumeric(base)) {
×
1680
      handleRuntimeError(
×
1681
        context,
1682
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1683
      );
1684
    } else if (!isNumeric(exp)) {
×
1685
      handleRuntimeError(
×
1686
        context,
1687
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1688
      );
1689
    }
1690

1691
    let baseNum: number;
1692
    if (base.type === "number") {
×
1693
      baseNum = base.value;
×
1694
    } else {
1695
      baseNum = Number(base.value);
×
1696
    }
1697

1698
    let expNum: number;
1699
    if (exp.type === "number") {
×
1700
      expNum = exp.value;
×
1701
    } else {
1702
      expNum = Number(exp.value);
×
1703
    }
1704

1705
    const result = Math.pow(baseNum, expNum);
×
1706
    return { type: "number", value: result };
×
1707
  }
1708

1709
  @Validate(1, 1, "math_radians", false)
1710
  static math_radians(
6✔
1711
    args: Value[],
1712
    source: string,
1713
    command: ControlItem,
1714
    context: Context,
1715
  ): NumberValue {
1716
    const x = args[0];
×
1717
    if (!isNumeric(x)) {
×
1718
      handleRuntimeError(
×
1719
        context,
1720
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1721
      );
1722
    }
1723

1724
    let deg: number;
1725
    if (x.type === "number") {
×
1726
      deg = x.value;
×
1727
    } else {
1728
      deg = Number(x.value);
×
1729
    }
1730

1731
    const radians = (deg * Math.PI) / 180;
×
1732
    return { type: "number", value: radians };
×
1733
  }
1734

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

1745
    let num: number;
1746
    if (x.type === "number") {
4✔
1747
      num = x.value;
3✔
1748
    } else {
1749
      num = Number(x.value);
1✔
1750
    }
1751

1752
    const result = Math.sin(num);
4✔
1753
    return { type: "number", value: result };
4✔
1754
  }
1755

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

1766
    let num: number;
1767
    if (x.type === "number") {
×
1768
      num = x.value;
×
1769
    } else {
1770
      num = Number(x.value);
×
1771
    }
1772

1773
    const result = Math.sinh(num);
×
1774
    return { type: "number", value: result };
×
1775
  }
1776

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

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

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

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

1813
    let num: number;
1814
    if (x.type === "number") {
×
1815
      num = x.value;
×
1816
    } else {
1817
      num = Number(x.value);
×
1818
    }
1819

1820
    const result = Math.tanh(num);
×
1821
    return { type: "number", value: result };
×
1822
  }
1823

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

1839
    let num: number;
1840
    if (x.type === "number") {
×
1841
      num = x.value;
×
1842
    } else {
1843
      num = Number(x.value);
×
1844
    }
1845

1846
    if (num < 0) {
×
1847
      handleRuntimeError(
×
1848
        context,
1849
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1850
      );
1851
    }
1852

1853
    const result = Math.sqrt(num);
×
1854
    return { type: "number", value: result };
×
1855
  }
1856

1857
  @Validate(2, null, "max", true)
1858
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1859
    const numericTypes = ["bigint", "number"];
2✔
1860
    const firstType = args[0].type;
2✔
1861
    const isNumericValue = numericTypes.includes(firstType);
2✔
1862
    const isString = firstType === "string";
2✔
1863

1864
    for (let i = 1; i < args.length; i++) {
2✔
1865
      const t = args[i].type;
5✔
1866
      if (isNumericValue && !numericTypes.includes(t)) {
5!
1867
        handleRuntimeError(
×
1868
          context,
1869
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1870
        );
1871
      }
1872
      if (isString && t !== "string") {
5!
1873
        handleRuntimeError(
×
1874
          context,
1875
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1876
        );
1877
      }
1878
    }
1879

1880
    let useFloat = false;
2✔
1881
    if (isNumericValue) {
2✔
1882
      for (const arg of args) {
2✔
1883
        if (arg.type === "number") {
7!
1884
          useFloat = true;
×
1885
          break;
×
1886
        }
1887
      }
1888
    }
1889

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

1964
    return args[maxIndex];
2✔
1965
  }
1966

1967
  @Validate(2, null, "min", true)
1968
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1969
    if (args.length < 2) {
×
1970
      handleRuntimeError(
×
1971
        context,
1972
        new MissingRequiredPositionalError(
1973
          source,
1974
          command as ExprNS.Expr,
1975
          "min",
1976
          Number(2),
1977
          args,
1978
          true,
1979
        ),
1980
      );
1981
    }
1982

1983
    const numericTypes = ["bigint", "number"];
×
1984
    const firstType = args[0].type;
×
1985
    const isNumericValue = numericTypes.includes(firstType);
×
1986
    const isString = firstType === "string";
×
1987

1988
    for (let i = 1; i < args.length; i++) {
×
1989
      const t = args[i].type;
×
1990
      if (isNumericValue && !numericTypes.includes(t)) {
×
1991
        handleRuntimeError(
×
1992
          context,
1993
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1994
        );
1995
      }
1996
      if (isString && t !== "string") {
×
1997
        handleRuntimeError(
×
1998
          context,
1999
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
2000
        );
2001
      }
2002
    }
2003

2004
    let useFloat = false;
×
2005
    if (isNumericValue) {
×
2006
      for (const arg of args) {
×
2007
        if (arg.type === "number") {
×
2008
          useFloat = true;
×
2009
          break;
×
2010
        }
2011
      }
2012
    }
2013

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

2088
    return args[maxIndex];
×
2089
  }
2090

2091
  @Validate(null, 0, "random_random", true)
2092
  static random_random(
6✔
2093
    _args: Value[],
2094
    _source: string,
2095
    _command: ControlItem,
2096
    _context: Context,
2097
  ): NumberValue {
2098
    const result = Math.random();
×
2099
    return { type: "number", value: result };
×
2100
  }
2101

2102
  @Validate(1, 2, "round", true)
2103
  static round(
6✔
2104
    args: Value[],
2105
    source: string,
2106
    command: ControlItem,
2107
    context: Context,
2108
  ): NumberValue | BigIntValue {
2109
    const numArg = args[0];
15✔
2110
    if (!isNumeric(numArg)) {
15✔
2111
      handleRuntimeError(
2✔
2112
        context,
2113
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
2114
      );
2115
    }
2116

2117
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
2118
    if (args.length === 2 && args[1].type !== "none") {
13✔
2119
      if (args[1].type !== "bigint") {
7✔
2120
        handleRuntimeError(
1✔
2121
          context,
2122
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
2123
        );
2124
      }
2125
      ndigitsArg = args[1];
6✔
2126
    } else {
2127
      const shifted = Intl.NumberFormat("en-US", {
6✔
2128
        roundingMode: "halfEven",
2129
        useGrouping: false,
2130
        maximumFractionDigits: 0,
2131
      } as Intl.NumberFormatOptions).format(numArg.value);
2132
      return { type: "bigint", value: BigInt(shifted) };
6✔
2133
    }
2134

2135
    if (numArg.type === "number") {
6✔
2136
      const numberValue: number = numArg.value;
4✔
2137
      if (ndigitsArg.value >= 0) {
4✔
2138
        const shifted = Intl.NumberFormat("en-US", {
3✔
2139
          roundingMode: "halfEven",
2140
          useGrouping: false,
2141
          maximumFractionDigits: Number(ndigitsArg.value),
2142
        } as Intl.NumberFormatOptions).format(numberValue);
2143
        return { type: "number", value: Number(shifted) };
3✔
2144
      } else {
2145
        const shifted = Intl.NumberFormat("en-US", {
1✔
2146
          roundingMode: "halfEven",
2147
          useGrouping: false,
2148
          maximumFractionDigits: 0,
2149
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
2150
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
2151
      }
2152
    } else {
2153
      if (ndigitsArg.value >= 0) {
2!
2154
        return numArg;
2✔
2155
      } else {
2156
        const shifted = Intl.NumberFormat("en-US", {
×
2157
          roundingMode: "halfEven",
2158
          useGrouping: false,
2159
          maximumFractionDigits: 0,
2160
        } as Intl.NumberFormatOptions).format(
2161
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
2162
        );
2163
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
2164
      }
2165
    }
2166
  }
2167

2168
  @Validate(null, 0, "time_time", true)
2169
  static time_time(
6✔
2170
    _args: Value[],
2171
    _source: string,
2172
    _command: ControlItem,
2173
    _context: Context,
2174
  ): NumberValue {
2175
    const currentTime = Date.now();
×
2176
    return { type: "number", value: currentTime };
×
2177
  }
2178

2179
  @Validate(1, 1, "is_none", true)
2180
  static is_none(
6✔
2181
    args: Value[],
2182
    _source: string,
2183
    _command: ControlItem,
2184
    _context: Context,
2185
  ): BoolValue {
2186
    const obj = args[0];
365✔
2187
    return { type: "bool", value: obj.type === "none" };
365✔
2188
  }
2189

2190
  @Validate(1, 1, "is_float", true)
2191
  static is_float(
6✔
2192
    args: Value[],
2193
    _source: string,
2194
    _command: ControlItem,
2195
    _context: Context,
2196
  ): BoolValue {
2197
    const obj = args[0];
9✔
2198
    return { type: "bool", value: obj.type === "number" };
9✔
2199
  }
2200

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

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

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

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

2245
  @Validate(1, 1, "is_function", true)
2246
  static is_function(
6✔
2247
    args: Value[],
2248
    _source: string,
2249
    _command: ControlItem,
2250
    _context: Context,
2251
  ): BoolValue {
2252
    const obj = args[0];
20✔
2253
    return {
20✔
2254
      type: "bool",
2255
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
58✔
2256
    };
2257
  }
2258

2259
  static async input(
2260
    _args: Value[],
2261
    _source: string,
2262
    _command: ControlItem,
2263
    context: Context,
2264
  ): Promise<Value> {
2265
    const userInput = await receiveInput(context);
×
2266
    return { type: "string", value: userInput };
×
2267
  }
2268

2269
  static async print(
2270
    args: Value[],
2271
    _source: string,
2272
    _command: ControlItem,
2273
    context: Context,
2274
  ): Promise<Value> {
2275
    const output = args.map(arg => toPythonString(arg)).join(" ");
3✔
2276
    await displayOutput(context, output);
3✔
2277
    return { type: "none" };
3✔
2278
  }
2279
  static str(
2280
    args: Value[],
2281
    _source: string,
2282
    _command: ControlItem,
2283
    _context: Context,
2284
  ): StringValue {
2285
    if (args.length === 0) {
12!
2286
      return { type: "string", value: "" };
×
2287
    }
2288
    const obj = args[0];
12✔
2289
    const result = toPythonString(obj);
12✔
2290
    return { type: "string", value: result };
12✔
2291
  }
2292
  @Validate(1, 1, "repr", true)
2293
  static repr(
6✔
2294
    args: Value[],
2295
    _source: string,
2296
    _command: ControlItem,
2297
    _context: Context,
2298
  ): StringValue {
2299
    const obj = args[0];
10✔
2300
    const result = toPythonString(obj, true);
10✔
2301
    return { type: "string", value: result };
10✔
2302
  }
2303
}
2304

2305
import { ExprNS } from "./ast-types";
2306
import { isFalsy } from "./cse-machine/operators";
6✔
2307
import { isNumeric } from "./cse-machine/utils";
6✔
2308
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2309
import { PyComplexNumber } from "./types";
6✔
2310

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

2315
/*
2316
    Create a map to hold built-in constants.
2317
    Each constant is stored with a string key and its corresponding value object.
2318
*/
2319
export const builtInConstants = new Map<string, Value>();
6✔
2320

2321
const constantMap = {
6✔
2322
  math_e: { type: "number", value: Math.E },
2323
  math_inf: { type: "number", value: Infinity },
2324
  math_nan: { type: "number", value: NaN },
2325
  math_pi: { type: "number", value: Math.PI },
2326
  math_tau: { type: "number", value: 2 * Math.PI },
2327
} as const;
2328

2329
for (const name of constants.constants) {
6✔
2330
  const valueObj = constantMap[name as keyof typeof constantMap];
30✔
2331
  if (!valueObj) {
30!
2332
    throw new Error(`Constant '${name}' is not implemented`);
×
2333
  }
2334
  builtInConstants.set(name, valueObj);
30✔
2335
}
2336

2337
/*
2338
    Create a map to hold built-in functions.
2339
    The keys are strings (function names) and the values are functions that can take any arguments.
2340
*/
2341
export const builtIns = new Map<string, BuiltinValue>();
6✔
2342
for (const name of constants.builtInFuncs) {
6✔
2343
  const impl = BuiltInFunctions[name as keyof BuiltInFunctions];
450✔
2344
  if (typeof impl !== "function") {
450!
2345
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
2346
  }
2347
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
450!
2348
  builtIns.set(name, {
450✔
2349
    type: "builtin",
2350
    name: builtinName,
2351
    func: impl,
2352
    minArgs: minArgMap.get(name) || 0,
522✔
2353
  });
2354
}
2355

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

2378
  if (num === Infinity) {
2!
2379
    return "inf";
×
2380
  }
2381
  if (num === -Infinity) {
2!
2382
    return "-inf";
×
2383
  }
2384

2385
  if (Number.isNaN(num)) {
2!
2386
    return "nan";
×
2387
  }
2388

2389
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
2390
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2391
  }
2392
  if (Number.isInteger(num)) {
2!
2393
    return num.toFixed(1).toString();
×
2394
  }
2395
  return num.toString();
2✔
2396
}
2397
function escape(str: string): string {
2398
  let escaped = JSON.stringify(str);
4✔
2399
  if (!(str.includes("'") && !str.includes('"'))) {
4✔
2400
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
4✔
2401
  }
2402
  return escaped;
4✔
2403
}
2404
export function toPythonString(obj: Value, repr: boolean = false): string {
6✔
2405
  let ret: string = "";
1,671✔
2406
  if (obj.type == "builtin") {
1,671!
2407
    return `<built-in function ${obj.name}>`;
×
2408
  }
2409
  if (obj.type === "bigint" || obj.type === "complex") {
1,671✔
2410
    ret = obj.value.toString();
25✔
2411
  } else if (obj.type === "number") {
1,646✔
2412
    ret = toPythonFloat(obj.value);
2✔
2413
  } else if (obj.type === "bool") {
1,644✔
2414
    if (obj.value) {
2!
2415
      return "True";
2✔
2416
    } else {
2417
      return "False";
×
2418
    }
2419
  } else if (obj.type === "error") {
1,642!
2420
    return obj.message;
×
2421
  } else if (obj.type === "closure") {
1,642✔
2422
    if (obj.closure.node) {
2✔
2423
      const funcName =
2424
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2425
      return `<function ${funcName}>`;
2✔
2426
    }
2427
  } else if (obj.type === "none") {
1,640✔
2428
    ret = "None";
1,630✔
2429
  } else if (obj.type === "string") {
10!
2430
    ret = repr ? escape(obj.value) : obj.value;
10✔
2431
  } else if (obj.type === "function") {
×
2432
    const funcName = obj.name || "(anonymous)";
×
2433
    ret = `<function ${funcName}>`;
×
2434
  } else if (obj.type === "list") {
×
2435
    ret = `[${obj.value.map(v => toPythonString(v, true)).join(", ")}]`;
×
2436
  } else {
2437
    ret = `<${obj.type} object>`;
×
2438
  }
2439
  return ret;
1,667✔
2440
}
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