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

source-academy / py-slang / 23827880064

01 Apr 2026 01:43AM UTC coverage: 63.053% (+2.4%) from 60.609%
23827880064

Pull #129

github

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

785 of 1459 branches covered (53.8%)

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.

2577 of 3873 relevant lines covered (66.54%)

3430.92 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(
210
        source,
211
        command as ExprNS.Expr,
212
        context,
213
        val.type,
214
        "'float', 'int', 'bool' or 'str'",
215
      ),
216
    );
217
  }
218

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

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

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

272
  @Validate(null, 1, "bool", true)
273
  static bool(args: Value[], _source: string, _command: ControlItem, _context: Context): BoolValue {
6✔
274
    if (args.length === 0) {
12!
NEW
275
      return { type: "bool", value: false };
×
276
    }
277
    const val = args[0];
12✔
278
    return { type: "bool", value: !isFalsy(val) };
12✔
279
  }
280

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

319
  @Validate(1, 1, "len", true)
320
  static len(args: Value[], source: string, command: ControlItem, context: Context): BigIntValue {
6✔
321
    const val = args[0];
14✔
322
    if (val.type === "string" || val.type === "list") {
14✔
323
      // The spread operator is used to count the number of Unicode code points
324
      // in the string
325
      return { type: "bigint", value: BigInt([...val.value].length) };
7✔
326
    }
327
    handleRuntimeError(
7✔
328
      context,
329
      new TypeError(source, command as ExprNS.Expr, context, val.type, "object with length"),
330
    );
331
  }
332

333
  static toStr(val: Value): string {
334
    return toPythonString(val);
2✔
335
  }
336

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

665
    const erfnum = erf(num);
×
666

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

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

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

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

690
  @Validate(2, 2, "math_comb", false)
691
  static math_comb(
6✔
692
    args: Value[],
693
    source: string,
694
    command: ControlItem,
695
    context: Context,
696
  ): BigIntValue {
697
    const n = args[0];
×
698
    const k = args[1];
×
699

700
    if (n.type !== "bigint") {
×
701
      handleRuntimeError(
×
702
        context,
703
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
704
      );
705
    } else if (k.type !== "bigint") {
×
706
      handleRuntimeError(
×
707
        context,
708
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
709
      );
710
    }
711

712
    const nVal = BigInt(n.value);
×
713
    const kVal = BigInt(k.value);
×
714

715
    if (nVal < 0 || kVal < 0) {
×
716
      handleRuntimeError(
×
717
        context,
718
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
719
      );
720
    }
721

722
    if (kVal > nVal) {
×
723
      return { type: "bigint", value: BigInt(0) };
×
724
    }
725

726
    let result: bigint = BigInt(1);
×
727
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
728

729
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
730
      result = (result * (nVal - i)) / (i + BigInt(1));
×
731
    }
732

733
    return { type: "bigint", value: result };
×
734
  }
735

736
  @Validate(1, 1, "math_factorial", false)
737
  static math_factorial(
6✔
738
    args: Value[],
739
    source: string,
740
    command: ControlItem,
741
    context: Context,
742
  ): BigIntValue {
743
    const n = args[0];
×
744

745
    if (n.type !== "bigint") {
×
746
      handleRuntimeError(
×
747
        context,
748
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
749
      );
750
    }
751

752
    const nVal = BigInt(n.value);
×
753

754
    if (nVal < 0) {
×
755
      handleRuntimeError(
×
756
        context,
757
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
758
      );
759
    }
760

761
    // 0! = 1
762
    if (nVal === BigInt(0)) {
×
763
      return { type: "bigint", value: BigInt(1) };
×
764
    }
765

766
    let result: bigint = BigInt(1);
×
767
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
768
      result *= i;
×
769
    }
770

771
    return { type: "bigint", value: result };
×
772
  }
773

774
  static math_gcd(
775
    args: Value[],
776
    source: string,
777
    command: ControlItem,
778
    context: Context,
779
  ): BigIntValue {
780
    if (args.length === 0) {
×
781
      return { type: "bigint", value: BigInt(0) };
×
782
    }
783

784
    const values = args.map(v => {
×
785
      if (v.type !== "bigint") {
×
786
        handleRuntimeError(
×
787
          context,
788
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
789
        );
790
      }
791
      return BigInt(v.value);
×
792
    });
793

794
    const allZero = values.every(val => val === BigInt(0));
×
795
    if (allZero) {
×
796
      return { type: "bigint", value: BigInt(0) };
×
797
    }
798

799
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
800
    for (let i = 1; i < values.length; i++) {
×
801
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
802
      if (currentGcd === BigInt(1)) {
×
803
        break;
×
804
      }
805
    }
806

807
    return { type: "bigint", value: currentGcd };
×
808
  }
809

810
  static gcdOfTwo(a: bigint, b: bigint): bigint {
811
    let x: bigint = a;
×
812
    let y: bigint = b;
×
813
    while (y !== BigInt(0)) {
×
814
      const temp = x % y;
×
815
      x = y;
×
816
      y = temp;
×
817
    }
818
    return x < 0 ? -x : x;
×
819
  }
820

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

836
    const n: bigint = nValObj.value;
×
837

838
    if (n < 0) {
×
839
      handleRuntimeError(
×
840
        context,
841
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
842
      );
843
    }
844

845
    if (n < 2) {
×
846
      return { type: "bigint", value: n };
×
847
    }
848

849
    let low: bigint = BigInt(1);
×
850
    let high: bigint = n;
×
851

852
    while (low < high) {
×
853
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
854
      const sq = mid * mid;
×
855
      if (sq <= n) {
×
856
        low = mid;
×
857
      } else {
858
        high = mid - BigInt(1);
×
859
      }
860
    }
861

862
    return { type: "bigint", value: low };
×
863
  }
864

865
  static math_lcm(
866
    args: Value[],
867
    source: string,
868
    command: ControlItem,
869
    context: Context,
870
  ): BigIntValue {
871
    if (args.length === 0) {
×
872
      return { type: "bigint", value: BigInt(1) };
×
873
    }
874

875
    const values = args.map(val => {
×
876
      if (val.type !== "bigint") {
×
877
        handleRuntimeError(
×
878
          context,
879
          new TypeError(source, command as ExprNS.Expr, context, val.type, "int"),
880
        );
881
      }
882
      return BigInt(val.value);
×
883
    });
884

885
    if (values.some(v => v === BigInt(0))) {
×
886
      return { type: "bigint", value: BigInt(0) };
×
887
    }
888

889
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
890
    for (let i = 1; i < values.length; i++) {
×
891
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
892
      if (currentLcm === BigInt(0)) {
×
893
        break;
×
894
      }
895
    }
896

897
    return { type: "bigint", value: currentLcm };
×
898
  }
899

900
  static lcmOfTwo(a: bigint, b: bigint): bigint {
901
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
902
    return BigInt((a / gcdVal) * b);
×
903
  }
904

905
  static absBigInt(x: bigint): bigint {
906
    return x < 0 ? -x : x;
×
907
  }
908

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

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

940
    if (n < 0 || k < 0) {
×
941
      handleRuntimeError(
×
942
        context,
943
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
944
      );
945
    }
946

947
    if (k > n) {
×
948
      return { type: "bigint", value: BigInt(0) };
×
949
    }
950

951
    let result: bigint = BigInt(1);
×
952
    for (let i: bigint = BigInt(0); i < k; i++) {
×
953
      result *= n - i;
×
954
    }
955

956
    return { type: "bigint", value: result };
×
957
  }
958

959
  @Validate(1, 1, "math_ceil", false)
960
  static math_ceil(
6✔
961
    args: Value[],
962
    source: string,
963
    command: ControlItem,
964
    context: Context,
965
  ): BigIntValue {
966
    const x = args[0];
×
967

968
    if (x.type === "bigint") {
×
969
      return x;
×
970
    }
971

972
    if (x.type === "number") {
×
973
      const numVal = x.value;
×
974
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
975
      return { type: "bigint", value: ceiled };
×
976
    }
977

978
    handleRuntimeError(
×
979
      context,
980
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
981
    );
982
  }
983

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

993
    if (x.type === "bigint") {
×
994
      const bigVal: bigint = BigInt(x.value);
×
995
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
996
      return { type: "number", value: absVal };
×
997
    }
998

999
    if (x.type === "number") {
×
1000
      const numVal: number = x.value;
×
1001
      if (typeof numVal !== "number") {
×
1002
        handleRuntimeError(
×
1003
          context,
1004
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1005
        );
1006
      }
1007
      const absVal: number = Math.abs(numVal);
×
1008
      return { type: "number", value: absVal };
×
1009
    }
1010

1011
    handleRuntimeError(
×
1012
      context,
1013
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1014
    );
1015
  }
1016

1017
  @Validate(1, 1, "math_floor", false)
1018
  static math_floor(
6✔
1019
    args: Value[],
1020
    source: string,
1021
    command: ControlItem,
1022
    context: Context,
1023
  ): BigIntValue {
1024
    const x = args[0];
×
1025

1026
    if (x.type === "bigint") {
×
1027
      return x;
×
1028
    }
1029

1030
    if (x.type === "number") {
×
1031
      const numVal: number = x.value;
×
1032
      if (typeof numVal !== "number") {
×
1033
        handleRuntimeError(
×
1034
          context,
1035
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1036
        );
1037
      }
1038
      const floored: bigint = BigInt(Math.floor(numVal));
×
1039
      return { type: "bigint", value: floored };
×
1040
    }
1041

1042
    handleRuntimeError(
×
1043
      context,
1044
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1045
    );
1046
  }
1047

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

1060
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
1061
  static twoSum(a: number, b: number): { sum: number; err: number } {
1062
    const sum = a + b;
×
1063
    const v = sum - a;
×
1064
    const err = a - (sum - v) + (b - v);
×
1065
    return { sum, err };
×
1066
  }
1067

1068
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1069
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1070
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1071
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1072
    const result = sum + (prodErr + sumErr);
×
1073
    return result;
×
1074
  }
1075

1076
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
1077
    if (val.type === "bigint") {
×
1078
      return Number(val.value);
×
1079
    } else if (val.type === "number") {
×
1080
      return val.value;
×
1081
    } else {
1082
      handleRuntimeError(
×
1083
        context,
1084
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
1085
      );
1086
    }
1087
  }
1088

1089
  @Validate(3, 3, "math_fma", false)
1090
  static math_fma(
6✔
1091
    args: Value[],
1092
    source: string,
1093
    command: ControlItem,
1094
    context: Context,
1095
  ): NumberValue {
1096
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1097
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1098
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1099

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

1112
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1113
    return { type: "number", value: result };
×
1114
  }
1115

1116
  @Validate(2, 2, "math_fmod", false)
1117
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1118
    // Convert inputs to numbers
1119
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1120
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1121

1122
    // Divisor cannot be zero
1123
    if (yVal === 0) {
×
1124
      handleRuntimeError(
×
1125
        context,
1126
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1127
      );
1128
    }
1129

1130
    // JavaScript's % operator behaves similarly to C's fmod
1131
    // in that the sign of the result is the same as the sign of x.
1132
    // For corner cases (NaN, Infinity), JavaScript remainder
1133
    // yields results consistent with typical C library fmod behavior.
1134
    const remainder = xVal % yVal;
×
1135

1136
    return { type: "number", value: remainder };
×
1137
  }
1138

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

1154
  @Validate(2, 2, "math_remainder", false)
1155
  static math_remainder(
6✔
1156
    args: Value[],
1157
    source: string,
1158
    command: ControlItem,
1159
    context: Context,
1160
  ): NumberValue {
1161
    const x = args[0];
×
1162
    const y = args[1];
×
1163

1164
    let xValue: number;
1165
    if (x.type === "bigint") {
×
1166
      xValue = Number(x.value);
×
1167
    } else if (x.type === "number") {
×
1168
      xValue = x.value;
×
1169
    } else {
1170
      handleRuntimeError(
×
1171
        context,
1172
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1173
      );
1174
    }
1175

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

1188
    if (yValue === 0) {
×
1189
      handleRuntimeError(
×
1190
        context,
1191
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1192
      );
1193
    }
1194

1195
    const quotient = xValue / yValue;
×
1196
    const n = BuiltInFunctions.roundToEven(quotient);
×
1197
    const remainder = xValue - n * yValue;
×
1198

1199
    return { type: "number", value: remainder };
×
1200
  }
1201

1202
  @Validate(1, 1, "math_trunc", false)
1203
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1204
    const x = args[0];
×
1205

1206
    if (x.type === "bigint") {
×
1207
      return x;
×
1208
    }
1209

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

1229
    handleRuntimeError(
×
1230
      context,
1231
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1232
    );
1233
  }
1234

1235
  @Validate(2, 2, "math_copysign", false)
1236
  static math_copysign(
6✔
1237
    args: Value[],
1238
    source: string,
1239
    command: ControlItem,
1240
    context: Context,
1241
  ): NumberValue {
1242
    const [x, y] = args;
×
1243

1244
    if (!isNumeric(x)) {
×
1245
      handleRuntimeError(
×
1246
        context,
1247
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1248
      );
1249
    } else if (!isNumeric(y)) {
×
1250
      handleRuntimeError(
×
1251
        context,
1252
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1253
      );
1254
    }
1255

1256
    const xVal = Number(x.value);
×
1257
    const yVal = Number(y.value);
×
1258

1259
    const absVal = Math.abs(xVal);
×
1260
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1261
    const result = isNegative ? -absVal : absVal;
×
1262

1263
    return { type: "number", value: Number(result) };
×
1264
  }
1265

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

1281
    const x = Number(xValObj.value);
×
1282
    const result: boolean = Number.isFinite(x);
×
1283

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

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

1302
    const x = Number(xValObj.value);
×
1303
    const result: boolean = x === Infinity || x === -Infinity;
×
1304

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

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

1323
    const x = Number(xValObj.value);
×
1324
    const result: boolean = Number.isNaN(x);
×
1325

1326
    return { type: "bool", value: result };
×
1327
  }
1328

1329
  @Validate(2, 2, "math_ldexp", false)
1330
  static math_ldexp(
6✔
1331
    args: Value[],
1332
    source: string,
1333
    command: ControlItem,
1334
    context: Context,
1335
  ): NumberValue {
1336
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1337

1338
    if (args[1].type !== "bigint") {
×
1339
      handleRuntimeError(
×
1340
        context,
1341
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1342
      );
1343
    }
1344
    const expVal = args[1].value;
×
1345

1346
    // Perform x * 2^expVal
1347
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1348
    // That behavior parallels typical C library rules for ldexp.
1349
    const result = xVal * Math.pow(2, Number(expVal));
×
1350

1351
    return { type: "number", value: result };
×
1352
  }
1353

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

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

1376
  @Validate(1, 1, "math_cbrt", false)
1377
  static math_cbrt(
6✔
1378
    args: Value[],
1379
    source: string,
1380
    command: ControlItem,
1381
    context: Context,
1382
  ): NumberValue {
1383
    const xVal = args[0];
×
1384
    let x: number;
1385

1386
    if (xVal.type !== "number") {
×
1387
      if (xVal.type === "bigint") {
×
1388
        x = Number(xVal.value);
×
1389
      } else {
1390
        handleRuntimeError(
×
1391
          context,
1392
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1393
        );
1394
      }
1395
    } else {
1396
      x = xVal.value;
×
1397
    }
1398

1399
    const result = Math.cbrt(x);
×
1400

1401
    return { type: "number", value: result };
×
1402
  }
1403

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

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

1427
    const result = Math.exp(x);
×
1428
    return { type: "number", value: result };
×
1429
  }
1430

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

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

1454
    const result = Math.pow(2, x);
×
1455
    return { type: "number", value: result };
×
1456
  }
1457

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

1473
    let num: number;
1474
    if (x.type === "number") {
×
1475
      num = x.value;
×
1476
    } else {
1477
      num = Number(x.value);
×
1478
    }
1479

1480
    const result = Math.expm1(num);
×
1481
    return { type: "number", value: result };
×
1482
  }
1483

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

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

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

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

1520
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1521
    const result = lgamma(z);
×
1522

1523
    return { type: "number", value: result };
×
1524
  }
1525

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

1547
    if (num <= 0) {
×
1548
      handleRuntimeError(
×
1549
        context,
1550
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1551
      );
1552
    }
1553

1554
    if (args.length === 1) {
×
1555
      return { type: "number", value: Math.log(num) };
×
1556
    }
1557

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

1578
    const result = Math.log(num) / Math.log(baseNum);
×
1579
    return { type: "number", value: result };
×
1580
  }
1581

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

1609
    const result = Math.log10(num);
×
1610
    return { type: "number", value: result };
×
1611
  }
1612

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

1640
    const result = Math.log1p(num);
×
1641
    return { type: "number", value: result };
×
1642
  }
1643

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

1671
    const result = Math.log2(num);
×
1672
    return { type: "number", value: result };
×
1673
  }
1674

1675
  @Validate(2, 2, "math_pow", false)
1676
  static math_pow(
6✔
1677
    args: Value[],
1678
    source: string,
1679
    command: ControlItem,
1680
    context: Context,
1681
  ): NumberValue {
1682
    const base = args[0];
×
1683
    const exp = args[1];
×
1684

1685
    if (!isNumeric(base)) {
×
1686
      handleRuntimeError(
×
1687
        context,
1688
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1689
      );
1690
    } else if (!isNumeric(exp)) {
×
1691
      handleRuntimeError(
×
1692
        context,
1693
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1694
      );
1695
    }
1696

1697
    let baseNum: number;
1698
    if (base.type === "number") {
×
1699
      baseNum = base.value;
×
1700
    } else {
1701
      baseNum = Number(base.value);
×
1702
    }
1703

1704
    let expNum: number;
1705
    if (exp.type === "number") {
×
1706
      expNum = exp.value;
×
1707
    } else {
1708
      expNum = Number(exp.value);
×
1709
    }
1710

1711
    const result = Math.pow(baseNum, expNum);
×
1712
    return { type: "number", value: result };
×
1713
  }
1714

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

1730
    let deg: number;
1731
    if (x.type === "number") {
×
1732
      deg = x.value;
×
1733
    } else {
1734
      deg = Number(x.value);
×
1735
    }
1736

1737
    const radians = (deg * Math.PI) / 180;
×
1738
    return { type: "number", value: radians };
×
1739
  }
1740

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

1751
    let num: number;
1752
    if (x.type === "number") {
4✔
1753
      num = x.value;
3✔
1754
    } else {
1755
      num = Number(x.value);
1✔
1756
    }
1757

1758
    const result = Math.sin(num);
4✔
1759
    return { type: "number", value: result };
4✔
1760
  }
1761

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

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

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

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

1793
    let num: number;
1794
    if (x.type === "number") {
×
1795
      num = x.value;
×
1796
    } else {
1797
      num = Number(x.value);
×
1798
    }
1799

1800
    const result = Math.tan(num);
×
1801
    return { type: "number", value: result };
×
1802
  }
1803

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

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

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

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

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

1852
    if (num < 0) {
×
1853
      handleRuntimeError(
×
1854
        context,
1855
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1856
      );
1857
    }
1858

1859
    const result = Math.sqrt(num);
×
1860
    return { type: "number", value: result };
×
1861
  }
1862

1863
  @Validate(2, null, "max", true)
1864
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
6✔
1865
    const numericTypes = ["bigint", "number"];
2✔
1866
    const firstType = args[0].type;
2✔
1867
    const isNumericValue = numericTypes.includes(firstType);
2✔
1868
    const isString = firstType === "string";
2✔
1869

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

1886
    let useFloat = false;
2✔
1887
    if (isNumericValue) {
2✔
1888
      for (const arg of args) {
2✔
1889
        if (arg.type === "number") {
7!
1890
          useFloat = true;
×
1891
          break;
×
1892
        }
1893
      }
1894
    }
1895

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

1970
    return args[maxIndex];
2✔
1971
  }
1972

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

1989
    const numericTypes = ["bigint", "number"];
×
1990
    const firstType = args[0].type;
×
1991
    const isNumericValue = numericTypes.includes(firstType);
×
1992
    const isString = firstType === "string";
×
1993

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

2010
    let useFloat = false;
×
2011
    if (isNumericValue) {
×
2012
      for (const arg of args) {
×
2013
        if (arg.type === "number") {
×
2014
          useFloat = true;
×
2015
          break;
×
2016
        }
2017
      }
2018
    }
2019

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

2094
    return args[maxIndex];
×
2095
  }
2096

2097
  @Validate(null, 0, "random_random", true)
2098
  static random_random(
6✔
2099
    _args: Value[],
2100
    _source: string,
2101
    _command: ControlItem,
2102
    _context: Context,
2103
  ): NumberValue {
2104
    const result = Math.random();
×
2105
    return { type: "number", value: result };
×
2106
  }
2107

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

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

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

2174
  @Validate(null, 0, "time_time", true)
2175
  static time_time(
6✔
2176
    _args: Value[],
2177
    _source: string,
2178
    _command: ControlItem,
2179
    _context: Context,
2180
  ): NumberValue {
2181
    const currentTime = Date.now();
×
2182
    return { type: "number", value: currentTime };
×
2183
  }
2184

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

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

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

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

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

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

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

2265
  static async input(
2266
    _args: Value[],
2267
    _source: string,
2268
    _command: ControlItem,
2269
    context: Context,
2270
  ): Promise<Value> {
2271
    const userInput = await receiveInput(context);
×
2272
    return { type: "string", value: userInput };
×
2273
  }
2274

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

2311
import { ExprNS } from "./ast-types";
2312
import { isFalsy } from "./cse-machine/operators";
6✔
2313
import { isNumeric } from "./cse-machine/utils";
6✔
2314
import py_s1_constants from "./stdlib/py_s1_constants.json";
6✔
2315
import { PyComplexNumber } from "./types";
6✔
2316

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

2321
/*
2322
    Create a map to hold built-in constants.
2323
    Each constant is stored with a string key and its corresponding value object.
2324
*/
2325
export const builtInConstants = new Map<string, Value>();
6✔
2326

2327
const constantMap = {
6✔
2328
  math_e: { type: "number", value: Math.E },
2329
  math_inf: { type: "number", value: Infinity },
2330
  math_nan: { type: "number", value: NaN },
2331
  math_pi: { type: "number", value: Math.PI },
2332
  math_tau: { type: "number", value: 2 * Math.PI },
2333
} as const;
2334

2335
for (const name of constants.constants) {
6✔
2336
  const valueObj = constantMap[name as keyof typeof constantMap];
30✔
2337
  if (!valueObj) {
30!
2338
    throw new Error(`Constant '${name}' is not implemented`);
×
2339
  }
2340
  builtInConstants.set(name, valueObj);
30✔
2341
}
2342

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

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

2384
  if (num === Infinity) {
2!
2385
    return "inf";
×
2386
  }
2387
  if (num === -Infinity) {
2!
2388
    return "-inf";
×
2389
  }
2390

2391
  if (Number.isNaN(num)) {
2!
2392
    return "nan";
×
2393
  }
2394

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