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

source-academy / py-slang / 23457975600

23 Mar 2026 08:14PM UTC coverage: 41.285% (-0.4%) from 41.678%
23457975600

Pull #107

github

web-flow
Merge cfb7ec4a4 into ccdeccb51
Pull Request #107: Use Conductor-based streams for StdIO

276 of 1029 branches covered (26.82%)

Branch coverage included in aggregate %.

7 of 37 new or added lines in 2 files covered. (18.92%)

24 existing lines in 1 file now uncovered.

1221 of 2597 relevant lines covered (47.02%)

51.95 hits per line

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

6.11
/src/stdlib.ts
1
import { erf, gamma, lgamma } from "mathjs";
3✔
2
import { Context } from "./cse-machine/context";
3
import { ControlItem } from "./cse-machine/control";
4
import { handleRuntimeError } from "./cse-machine/error";
3✔
5
import { Value } from "./cse-machine/stash";
6
import { displayOutput } from "./cse-machine/streams";
3✔
7
import {
3✔
8
  MissingRequiredPositionalError,
9
  SublanguageError,
10
  TooManyPositionalArgumentsError,
11
  TypeError,
12
  ValueError,
13
} from "./errors/errors";
14

15
export function Validate(
3✔
16
  minArgs: number | null,
17
  maxArgs: number | null,
18
  functionName: string,
19
  strict: boolean,
20
) {
21
  return function (
165✔
22
    _target: unknown,
23
    _propertyKey: string,
24
    descriptor: TypedPropertyDescriptor<
25
      (args: Value[], source: string, command: ControlItem, context: Context) => Value
26
    >,
27
  ): void {
28
    const originalMethod = descriptor.value!;
165✔
29

30
    descriptor.value = function (
165✔
31
      args: Value[],
32
      source: string,
33
      command: ControlItem,
34
      context: Context,
35
    ): Value {
36
      if (minArgs !== null && args.length < minArgs) {
×
37
        throw new MissingRequiredPositionalError(
×
38
          source,
39
          command as ExprNS.Expr,
40
          functionName,
41
          minArgs,
42
          args,
43
          strict,
44
        );
45
      }
46

47
      if (maxArgs !== null && args.length > maxArgs) {
×
48
        throw new TooManyPositionalArgumentsError(
×
49
          source,
50
          command as ExprNS.Expr,
51
          functionName,
52
          maxArgs,
53
          args,
54
          strict,
55
        );
56
      }
57

58
      return originalMethod.call(this, args, source, command, context);
×
59
    };
60
  };
61
}
62

63
export class BuiltInFunctions {
3✔
64
  @Validate(null, 1, "_int", true)
65
  static _int(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
66
    if (args.length === 0) {
×
67
      return { type: "bigint", value: BigInt(0) };
×
68
    }
69

70
    const arg = args[0];
×
71
    if (args.length === 1) {
×
72
      if (arg.type === "number") {
×
73
        const truncated = Math.trunc(arg.value);
×
74
        return { type: "bigint", value: BigInt(truncated) };
×
75
      }
76
      if (arg.type === "bigint") {
×
77
        return { type: "bigint", value: arg.value };
×
78
      }
79
      if (arg.type === "string") {
×
80
        const str = arg.value.trim().replace(/_/g, "");
×
81
        if (!/^[+-]?\d+$/.test(str)) {
×
82
          handleRuntimeError(
×
83
            context,
84
            new ValueError(source, command as ExprNS.Expr, context, "int"),
85
          );
86
        }
87
        return { type: "bigint", value: BigInt(str) };
×
88
      }
89
    } else if (args.length === 2) {
×
90
      const baseArg = args[1];
×
91
      if (arg.type !== "string") {
×
92
        handleRuntimeError(
×
93
          context,
94
          new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
95
        );
96
      }
97
      if (baseArg.type !== "bigint") {
×
98
        handleRuntimeError(
×
99
          context,
100
          new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
101
        );
102
      }
103

104
      let base = Number(baseArg.value);
×
105
      let str = arg.value.trim().replace(/_/g, "");
×
106

107
      const sign = str.startsWith("-") ? -1 : 1;
×
108
      if (str.startsWith("+") || str.startsWith("-")) {
×
109
        str = str.substring(1);
×
110
      }
111

112
      if (base === 0) {
×
113
        if (str.startsWith("0x") || str.startsWith("0X")) {
×
114
          base = 16;
×
115
          str = str.substring(2);
×
116
        } else if (str.startsWith("0o") || str.startsWith("0O")) {
×
117
          base = 8;
×
118
          str = str.substring(2);
×
119
        } else if (str.startsWith("0b") || str.startsWith("0B")) {
×
120
          base = 2;
×
121
          str = str.substring(2);
×
122
        } else {
123
          base = 10;
×
124
        }
125
      }
126

127
      if (base < 2 || base > 36) {
×
128
        handleRuntimeError(
×
129
          context,
130
          new ValueError(source, command as ExprNS.Expr, context, "float' or 'int"),
131
        );
132
      }
133

134
      const validChars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base);
×
135
      const regex = new RegExp(`^[${validChars}]+$`, "i");
×
136
      if (!regex.test(str)) {
×
137
        handleRuntimeError(
×
138
          context,
139
          new ValueError(source, command as ExprNS.Expr, context, "float' or 'int"),
140
        );
141
      }
142

143
      const parsed = parseInt(str, base);
×
144
      return { type: "bigint", value: BigInt(sign * parsed) };
×
145
    }
146
    handleRuntimeError(
×
147
      context,
148
      new TypeError(
149
        source,
150
        command as ExprNS.Expr,
151
        context,
152
        arg.type,
153
        "string, a bytes-like object or a real number",
154
      ),
155
    );
156
  }
157

158
  @Validate(1, 2, "_int_from_string", true)
159
  static _int_from_string(
3✔
160
    args: Value[],
161
    source: string,
162
    command: ControlItem,
163
    context: Context,
164
  ): Value {
165
    const strVal = args[0];
×
166
    if (strVal.type !== "string") {
×
167
      handleRuntimeError(
×
168
        context,
169
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
170
      );
171
    }
172

173
    let base: number = 10;
×
174
    if (args.length === 2) {
×
175
      // The second argument must be either a bigint or a number (it will be converted to a number for uniform processing).
176
      const baseVal = args[1];
×
177
      if (baseVal.type === "bigint") {
×
178
        base = Number(baseVal.value);
×
179
      } else {
180
        handleRuntimeError(
×
181
          context,
182
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "float' or 'int"),
183
        );
184
      }
185
    }
186

187
    // base should be in between 2 and 36
188
    if (base < 2 || base > 36) {
×
189
      handleRuntimeError(
×
190
        context,
191
        new ValueError(source, command as ExprNS.Expr, context, "_int_from_string"),
192
      );
193
    }
194

195
    let str = strVal.value;
×
196
    str = str.trim();
×
197
    str = str.replace(/_/g, "");
×
198

199
    // Parse the sign (determine if the value is positive or negative)
200
    let sign: bigint = BigInt(1);
×
201
    if (str.startsWith("+")) {
×
202
      str = str.slice(1);
×
203
    } else if (str.startsWith("-")) {
×
204
      sign = BigInt(-1);
×
205
      str = str.slice(1);
×
206
    }
207

208
    // The remaining portion must consist of valid characters for the specified base.
209
    const parsedNumber = parseInt(str, base);
×
210
    if (isNaN(parsedNumber)) {
×
211
      handleRuntimeError(
×
212
        context,
213
        new ValueError(source, command as ExprNS.Expr, context, "_int_from_string"),
214
      );
215
    }
216

217
    const result: bigint = sign * BigInt(parsedNumber);
×
218

219
    return { type: "bigint", value: result };
×
220
  }
221

222
  @Validate(1, 1, "abs", false)
223
  static abs(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
224
    const x = args[0];
×
225
    switch (x.type) {
×
226
      case "bigint": {
227
        const intVal = x.value;
×
228
        const result: bigint = intVal < 0 ? -intVal : intVal;
×
229
        return { type: "bigint", value: result };
×
230
      }
231
      case "number": {
232
        return { type: "number", value: Math.abs(x.value) };
×
233
      }
234
      case "complex": {
235
        // Calculate the modulus (absolute value) of a complex number.
236
        const real = x.value.real;
×
237
        const imag = x.value.imag;
×
238
        const modulus = Math.sqrt(real * real + imag * imag);
×
239
        return { type: "number", value: modulus };
×
240
      }
241
      default:
242
        handleRuntimeError(
×
243
          context,
244
          new TypeError(
245
            source,
246
            command as ExprNS.Expr,
247
            context,
248
            args[0].type,
249
            "float', 'int' or 'complex",
250
          ),
251
        );
252
    }
253
  }
254

255
  static toStr(val: Value): string {
256
    return toPythonString(val);
×
257
  }
258

259
  static error(args: Value[], _source: string, _command: ControlItem, _context: Context): Value {
260
    const output = "Error: " + args.map(arg => BuiltInFunctions.toStr(arg)).join(" ") + "\n";
×
261
    throw new Error(output);
×
262
  }
263

264
  @Validate(2, 2, "isinstance", false)
265
  static isinstance(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
266
    const obj = args[0];
×
267
    const classinfo = args[1];
×
268

269
    let expectedType: string;
270
    if (classinfo.type === "string") {
×
271
      switch (classinfo.value) {
×
272
        case "int":
273
          expectedType = "bigint";
×
274
          if (obj.type === "bool") {
×
275
            handleRuntimeError(
×
276
              context,
277
              new SublanguageError(
278
                source,
279
                command as ExprNS.Expr,
280
                context,
281
                "isinstance",
282
                "1",
283
                "Python §1 does not treat bool as a subtype of int",
284
              ),
285
            );
286
          }
287
          break;
×
288
        case "float":
289
          expectedType = "number";
×
290
          break;
×
291
        case "string":
292
          expectedType = "string";
×
293
          break;
×
294
        case "bool":
295
          expectedType = "bool";
×
296
          break;
×
297
        case "complex":
298
          expectedType = "complex";
×
299
          break;
×
300
        case "NoneType":
301
          expectedType = "NoneType";
×
302
          break;
×
303
        default:
304
          handleRuntimeError(
×
305
            context,
306
            new ValueError(source, command as ExprNS.Expr, context, "isinstance"),
307
          );
308
      }
309
    } else {
310
      handleRuntimeError(
×
311
        context,
312
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
313
      );
314
    }
315

316
    const result = obj.type === expectedType;
×
317

318
    return { type: "bool", value: result };
×
319
  }
320

321
  @Validate(1, 1, "math_acos", false)
322
  static math_acos(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
323
    const x = args[0];
×
324
    if (x.type !== "number" && x.type !== "bigint") {
×
325
      handleRuntimeError(
×
326
        context,
327
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
328
      );
329
    }
330

331
    let num: number;
332
    if (x.type === "number") {
×
333
      num = x.value;
×
334
    } else {
335
      num = Number(x.value);
×
336
    }
337

338
    if (num < -1 || num > 1) {
×
339
      handleRuntimeError(
×
340
        context,
341
        new ValueError(source, command as ExprNS.Expr, context, "math_acos"),
342
      );
343
    }
344

345
    const result = Math.acos(num);
×
346
    return { type: "number", value: result };
×
347
  }
348

349
  @Validate(1, 1, "math_acosh", false)
350
  static math_acosh(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
351
    const x = args[0];
×
352

353
    if (x.type !== "number" && x.type !== "bigint") {
×
354
      handleRuntimeError(
×
355
        context,
356
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
357
      );
358
    }
359

360
    let num: number;
361
    if (x.type === "number") {
×
362
      num = x.value;
×
363
    } else {
364
      num = Number(x.value);
×
365
    }
366

367
    if (num < 1) {
×
368
      handleRuntimeError(
×
369
        context,
370
        new ValueError(source, command as ExprNS.Expr, context, "math_acosh"),
371
      );
372
    }
373

374
    const result = Math.acosh(num);
×
375
    return { type: "number", value: result };
×
376
  }
377

378
  @Validate(1, 1, "math_asin", false)
379
  static math_asin(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
380
    const x = args[0];
×
381
    if (x.type !== "number" && x.type !== "bigint") {
×
382
      handleRuntimeError(
×
383
        context,
384
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
385
      );
386
    }
387

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

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

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

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

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

423
    const result = Math.asinh(num);
×
424
    return { type: "number", value: result };
×
425
  }
426

427
  @Validate(1, 1, "math_atan", false)
428
  static math_atan(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
429
    const x = args[0];
×
430
    if (x.type !== "number" && x.type !== "bigint") {
×
431
      handleRuntimeError(
×
432
        context,
433
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
434
      );
435
    }
436

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

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

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

464
    let yNum: number, xNum: number;
465
    if (y.type === "number") {
×
466
      yNum = y.value;
×
467
    } else {
468
      yNum = Number(y.value);
×
469
    }
470

471
    if (x.type === "number") {
×
472
      xNum = x.value;
×
473
    } else {
474
      xNum = Number(x.value);
×
475
    }
476

477
    const result = Math.atan2(yNum, xNum);
×
478
    return { type: "number", value: result };
×
479
  }
480

481
  @Validate(1, 1, "math_atanh", false)
482
  static math_atanh(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
483
    const x = args[0];
×
484
    if (x.type !== "number" && x.type !== "bigint") {
×
485
      handleRuntimeError(
×
486
        context,
487
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
488
      );
489
    }
490

491
    let num: number;
492
    if (x.type === "number") {
×
493
      num = x.value;
×
494
    } else {
495
      num = Number(x.value);
×
496
    }
497

498
    if (num <= -1 || num >= 1) {
×
499
      handleRuntimeError(
×
500
        context,
501
        new ValueError(source, command as ExprNS.Expr, context, "math_atanh"),
502
      );
503
    }
504

505
    const result = Math.atanh(num);
×
506
    return { type: "number", value: result };
×
507
  }
508

509
  @Validate(1, 1, "math_cos", false)
510
  static math_cos(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
511
    const x = args[0];
×
512
    if (x.type !== "number" && x.type !== "bigint") {
×
513
      handleRuntimeError(
×
514
        context,
515
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
516
      );
517
    }
518

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

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

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

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

547
    const result = Math.cosh(num);
×
548
    return { type: "number", value: result };
×
549
  }
550

551
  @Validate(1, 1, "math_degrees", false)
552
  static math_degrees(
3✔
553
    args: Value[],
554
    source: string,
555
    command: ControlItem,
556
    context: Context,
557
  ): Value {
558
    const x = args[0];
×
559
    if (x.type !== "number" && x.type !== "bigint") {
×
560
      handleRuntimeError(
×
561
        context,
562
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
563
      );
564
    }
565

566
    let num: number;
567
    if (x.type === "number") {
×
568
      num = x.value;
×
569
    } else {
570
      num = Number(x.value);
×
571
    }
572

573
    const result = (num * 180) / Math.PI;
×
574
    return { type: "number", value: result };
×
575
  }
576

577
  @Validate(1, 1, "math_erf", false)
578
  static math_erf(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
579
    const x = args[0];
×
580
    if (x.type !== "number" && x.type !== "bigint") {
×
581
      handleRuntimeError(
×
582
        context,
583
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
584
      );
585
    }
586

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

594
    const erfnum = erf(num);
×
595

596
    return { type: "number", value: erfnum };
×
597
  }
598

599
  @Validate(1, 1, "math_erfc", false)
600
  static math_erfc(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
601
    const x = args[0];
×
602
    if (x.type !== "number" && x.type !== "bigint") {
×
603
      handleRuntimeError(
×
604
        context,
605
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
606
      );
607
    }
608

609
    const num = BuiltInFunctions.math_erf(args, source, command, context);
×
610
    if (num.type !== "number") {
×
611
      handleRuntimeError(
×
612
        context,
613
        new TypeError(source, command as ExprNS.Expr, context, num.type, "float' or 'int"),
614
      );
615
    }
616
    const erfc = 1 - num.value;
×
617
    return { type: "number", value: erfc };
×
618
  }
619

620
  @Validate(2, 2, "char_at", false)
621
  static char_at(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
622
    const s = args[0];
×
623
    const i = args[1];
×
624

625
    if (s.type !== "string") {
×
626
      handleRuntimeError(
×
627
        context,
628
        new TypeError(source, command as ExprNS.Expr, context, s.type, "string"),
629
      );
630
    }
631
    if (i.type !== "number" && i.type !== "bigint") {
×
632
      handleRuntimeError(
×
633
        context,
634
        new TypeError(source, command as ExprNS.Expr, context, i.type, "float' or 'int"),
635
      );
636
    }
637

638
    const index = i.value;
×
639

640
    return { type: "string", value: s.value[Number(index)] };
×
641
  }
642

643
  @Validate(2, 2, "math_comb", false)
644
  static math_comb(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
645
    const n = args[0];
×
646
    const k = args[1];
×
647

648
    if (n.type !== "bigint") {
×
649
      handleRuntimeError(
×
650
        context,
651
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
652
      );
653
    } else if (k.type !== "bigint") {
×
654
      handleRuntimeError(
×
655
        context,
656
        new TypeError(source, command as ExprNS.Expr, context, k.type, "int"),
657
      );
658
    }
659

660
    const nVal = BigInt(n.value);
×
661
    const kVal = BigInt(k.value);
×
662

663
    if (nVal < 0 || kVal < 0) {
×
664
      handleRuntimeError(
×
665
        context,
666
        new ValueError(source, command as ExprNS.Expr, context, "math_comb"),
667
      );
668
    }
669

670
    if (kVal > nVal) {
×
671
      return { type: "bigint", value: BigInt(0) };
×
672
    }
673

674
    let result: bigint = BigInt(1);
×
675
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
676

677
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
678
      result = (result * (nVal - i)) / (i + BigInt(1));
×
679
    }
680

681
    return { type: "bigint", value: result };
×
682
  }
683

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

693
    if (n.type !== "bigint") {
×
694
      handleRuntimeError(
×
695
        context,
696
        new TypeError(source, command as ExprNS.Expr, context, n.type, "int"),
697
      );
698
    }
699

700
    const nVal = BigInt(n.value);
×
701

702
    if (nVal < 0) {
×
703
      handleRuntimeError(
×
704
        context,
705
        new ValueError(source, command as ExprNS.Expr, context, "math_factorial"),
706
      );
707
    }
708

709
    // 0! = 1
710
    if (nVal === BigInt(0)) {
×
711
      return { type: "bigint", value: BigInt(1) };
×
712
    }
713

714
    let result: bigint = BigInt(1);
×
715
    for (let i: bigint = BigInt(1); i <= nVal; i++) {
×
716
      result *= i;
×
717
    }
718

719
    return { type: "bigint", value: result };
×
720
  }
721

722
  static math_gcd(args: Value[], source: string, command: ControlItem, context: Context): Value {
723
    if (args.length === 0) {
×
724
      return { type: "bigint", value: BigInt(0) };
×
725
    }
726

727
    const values = args.map(v => {
×
728
      if (v.type !== "bigint") {
×
729
        handleRuntimeError(
×
730
          context,
731
          new TypeError(source, command as ExprNS.Expr, context, v.type, "int"),
732
        );
733
      }
734
      return BigInt(v.value);
×
735
    });
736

737
    const allZero = values.every(val => val === BigInt(0));
×
738
    if (allZero) {
×
739
      return { type: "bigint", value: BigInt(0) };
×
740
    }
741

742
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
743
    for (let i = 1; i < values.length; i++) {
×
744
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
745
      if (currentGcd === BigInt(1)) {
×
746
        break;
×
747
      }
748
    }
749

750
    return { type: "bigint", value: currentGcd };
×
751
  }
752

753
  static gcdOfTwo(a: bigint, b: bigint): bigint {
754
    let x: bigint = a;
×
755
    let y: bigint = b;
×
756
    while (y !== BigInt(0)) {
×
757
      const temp = x % y;
×
758
      x = y;
×
759
      y = temp;
×
760
    }
761
    return x < 0 ? -x : x;
×
762
  }
763

764
  @Validate(1, 1, "math_isqrt", false)
765
  static math_isqrt(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
766
    const nValObj = args[0];
×
767
    if (nValObj.type !== "bigint") {
×
768
      handleRuntimeError(
×
769
        context,
770
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
771
      );
772
    }
773

774
    const n: bigint = nValObj.value;
×
775

776
    if (n < 0) {
×
777
      handleRuntimeError(
×
778
        context,
779
        new ValueError(source, command as ExprNS.Expr, context, "math_isqrt"),
780
      );
781
    }
782

783
    if (n < 2) {
×
784
      return { type: "bigint", value: n };
×
785
    }
786

787
    let low: bigint = BigInt(1);
×
788
    let high: bigint = n;
×
789

790
    while (low < high) {
×
791
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
792
      const sq = mid * mid;
×
793
      if (sq <= n) {
×
794
        low = mid;
×
795
      } else {
796
        high = mid - BigInt(1);
×
797
      }
798
    }
799

800
    return { type: "bigint", value: low };
×
801
  }
802

803
  static math_lcm(args: Value[], source: string, command: ControlItem, context: Context): Value {
804
    if (args.length === 0) {
×
805
      return { type: "bigint", value: BigInt(1) };
×
806
    }
807

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

818
    if (values.some(v => v === BigInt(0))) {
×
819
      return { type: "bigint", value: BigInt(0) };
×
820
    }
821

822
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
823
    for (let i = 1; i < values.length; i++) {
×
824
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
825
      if (currentLcm === BigInt(0)) {
×
826
        break;
×
827
      }
828
    }
829

830
    return { type: "bigint", value: currentLcm };
×
831
  }
832

833
  static lcmOfTwo(a: bigint, b: bigint): bigint {
834
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
835
    return BigInt((a / gcdVal) * b);
×
836
  }
837

838
  static absBigInt(x: bigint): bigint {
839
    return x < 0 ? -x : x;
×
840
  }
841

842
  @Validate(1, 2, "math_perm", true)
843
  static math_perm(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
844
    const nValObj = args[0];
×
845
    if (nValObj.type !== "bigint") {
×
846
      handleRuntimeError(
×
847
        context,
848
        new TypeError(source, command as ExprNS.Expr, context, nValObj.type, "int"),
849
      );
850
    }
851
    const n = BigInt(nValObj.value);
×
852

853
    let k = n;
×
854
    if (args.length === 2) {
×
855
      const kValObj = args[1];
×
856
      if (kValObj.type === "none") {
×
857
        k = n;
×
858
      } else if (kValObj.type === "bigint") {
×
859
        k = BigInt(kValObj.value);
×
860
      } else {
861
        handleRuntimeError(
×
862
          context,
863
          new TypeError(source, command as ExprNS.Expr, context, kValObj.type, "int' or 'None"),
864
        );
865
      }
866
    }
867

868
    if (n < 0 || k < 0) {
×
869
      handleRuntimeError(
×
870
        context,
871
        new ValueError(source, command as ExprNS.Expr, context, "math_perm"),
872
      );
873
    }
874

875
    if (k > n) {
×
876
      return { type: "bigint", value: BigInt(0) };
×
877
    }
878

879
    let result: bigint = BigInt(1);
×
880
    for (let i: bigint = BigInt(0); i < k; i++) {
×
881
      result *= n - i;
×
882
    }
883

884
    return { type: "bigint", value: result };
×
885
  }
886

887
  @Validate(1, 1, "math_ceil", false)
888
  static math_ceil(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
889
    const x = args[0];
×
890

891
    if (x.type === "bigint") {
×
892
      return x;
×
893
    }
894

895
    if (x.type === "number") {
×
896
      const numVal = x.value;
×
897
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
898
      return { type: "bigint", value: ceiled };
×
899
    }
900

901
    handleRuntimeError(
×
902
      context,
903
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
904
    );
905
  }
906

907
  @Validate(1, 1, "math_fabs", false)
908
  static math_fabs(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
909
    const x = args[0];
×
910

911
    if (x.type === "bigint") {
×
912
      const bigVal: bigint = BigInt(x.value);
×
913
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
914
      return { type: "number", value: absVal };
×
915
    }
916

917
    if (x.type === "number") {
×
918
      const numVal: number = x.value;
×
919
      if (typeof numVal !== "number") {
×
920
        handleRuntimeError(
×
921
          context,
922
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
923
        );
924
      }
925
      const absVal: number = Math.abs(numVal);
×
926
      return { type: "number", value: absVal };
×
927
    }
928

929
    handleRuntimeError(
×
930
      context,
931
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
932
    );
933
  }
934

935
  @Validate(1, 1, "math_floor", false)
936
  static math_floor(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
937
    const x = args[0];
×
938

939
    if (x.type === "bigint") {
×
940
      return x;
×
941
    }
942

943
    if (x.type === "number") {
×
944
      const numVal: number = x.value;
×
945
      if (typeof numVal !== "number") {
×
946
        handleRuntimeError(
×
947
          context,
948
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
949
        );
950
      }
951
      const floored: bigint = BigInt(Math.floor(numVal));
×
952
      return { type: "bigint", value: floored };
×
953
    }
954

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

961
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
962
  static twoProd(a: number, b: number): { prod: number; err: number } {
963
    const prod = a * b;
×
964
    const c = 134217729; // 2^27 + 1
×
965
    const a_hi = a * c - (a * c - a);
×
966
    const a_lo = a - a_hi;
×
967
    const b_hi = b * c - (b * c - b);
×
968
    const b_lo = b - b_hi;
×
969
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
970
    return { prod, err };
×
971
  }
972

973
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
974
  static twoSum(a: number, b: number): { sum: number; err: number } {
975
    const sum = a + b;
×
976
    const v = sum - a;
×
977
    const err = a - (sum - v) + (b - v);
×
978
    return { sum, err };
×
979
  }
980

981
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
982
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
983
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
984
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
985
    const result = sum + (prodErr + sumErr);
×
986
    return result;
×
987
  }
988

989
  static toNumber(val: Value, source: string, command: ControlItem, context: Context): number {
990
    if (val.type === "bigint") {
×
991
      return Number(val.value);
×
992
    } else if (val.type === "number") {
×
993
      return val.value;
×
994
    } else {
995
      handleRuntimeError(
×
996
        context,
997
        new TypeError(source, command as ExprNS.Expr, context, val.type, "float' or 'int"),
998
      );
999
    }
1000
  }
1001

1002
  @Validate(3, 3, "math_fma", false)
1003
  static math_fma(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1004
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1005
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1006
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1007

1008
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1009
    // and fma(inf, 0, nan) should return NaN.
1010
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1011
      return { type: "number", value: NaN };
×
1012
    }
1013
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1014
      return { type: "number", value: NaN };
×
1015
    }
1016
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1017
      return { type: "number", value: NaN };
×
1018
    }
1019

1020
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1021
    return { type: "number", value: result };
×
1022
  }
1023

1024
  @Validate(2, 2, "math_fmod", false)
1025
  static math_fmod(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1026
    // Convert inputs to numbers
1027
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1028
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1029

1030
    // Divisor cannot be zero
1031
    if (yVal === 0) {
×
1032
      handleRuntimeError(
×
1033
        context,
1034
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1035
      );
1036
    }
1037

1038
    // JavaScript's % operator behaves similarly to C's fmod
1039
    // in that the sign of the result is the same as the sign of x.
1040
    // For corner cases (NaN, Infinity), JavaScript remainder
1041
    // yields results consistent with typical C library fmod behavior.
1042
    const remainder = xVal % yVal;
×
1043

1044
    return { type: "number", value: remainder };
×
1045
  }
1046

1047
  static roundToEven(num: number): number {
1048
    const floorVal = Math.floor(num);
×
1049
    const ceilVal = Math.ceil(num);
×
1050
    const diffFloor = num - floorVal;
×
1051
    const diffCeil = ceilVal - num;
×
1052
    if (diffFloor < diffCeil) {
×
1053
      return floorVal;
×
1054
    } else if (diffCeil < diffFloor) {
×
1055
      return ceilVal;
×
1056
    } else {
1057
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1058
    }
1059
  }
1060

1061
  @Validate(2, 2, "math_remainder", false)
1062
  static math_remainder(
3✔
1063
    args: Value[],
1064
    source: string,
1065
    command: ControlItem,
1066
    context: Context,
1067
  ): Value {
1068
    const x = args[0];
×
1069
    const y = args[1];
×
1070

1071
    let xValue: number;
1072
    if (x.type === "bigint") {
×
1073
      xValue = Number(x.value);
×
1074
    } else if (x.type === "number") {
×
1075
      xValue = x.value;
×
1076
    } else {
1077
      handleRuntimeError(
×
1078
        context,
1079
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1080
      );
1081
    }
1082

1083
    let yValue: number;
1084
    if (y.type === "bigint") {
×
1085
      yValue = Number(y.value);
×
1086
    } else if (y.type === "number") {
×
1087
      yValue = y.value;
×
1088
    } else {
1089
      handleRuntimeError(
×
1090
        context,
1091
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1092
      );
1093
    }
1094

1095
    if (yValue === 0) {
×
1096
      handleRuntimeError(
×
1097
        context,
1098
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1099
      );
1100
    }
1101

1102
    const quotient = xValue / yValue;
×
1103
    const n = BuiltInFunctions.roundToEven(quotient);
×
1104
    const remainder = xValue - n * yValue;
×
1105

1106
    return { type: "number", value: remainder };
×
1107
  }
1108

1109
  @Validate(1, 1, "math_trunc", false)
1110
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1111
    const x = args[0];
×
1112

1113
    if (x.type === "bigint") {
×
1114
      return x;
×
1115
    }
1116

1117
    if (x.type === "number") {
×
1118
      const numVal: number = x.value;
×
1119
      if (typeof numVal !== "number") {
×
1120
        handleRuntimeError(
×
1121
          context,
1122
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1123
        );
1124
      }
1125
      let truncated: number;
1126
      if (numVal === 0) {
×
1127
        truncated = 0;
×
1128
      } else if (numVal < 0) {
×
1129
        truncated = Math.ceil(numVal);
×
1130
      } else {
1131
        truncated = Math.floor(numVal);
×
1132
      }
1133
      return { type: "bigint", value: BigInt(truncated) };
×
1134
    }
1135

1136
    handleRuntimeError(
×
1137
      context,
1138
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1139
    );
1140
  }
1141

1142
  @Validate(2, 2, "math_copysign", false)
1143
  static math_copysign(
3✔
1144
    args: Value[],
1145
    source: string,
1146
    command: ControlItem,
1147
    context: Context,
1148
  ): Value {
1149
    const [x, y] = args;
×
1150

1151
    if (x.type !== "number" && x.type !== "bigint") {
×
1152
      handleRuntimeError(
×
1153
        context,
1154
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1155
      );
1156
    } else if (y.type !== "number" && y.type !== "bigint") {
×
1157
      handleRuntimeError(
×
1158
        context,
1159
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1160
      );
1161
    }
1162

1163
    const xVal = Number(x.value);
×
1164
    const yVal = Number(y.value);
×
1165

1166
    const absVal = Math.abs(xVal);
×
1167
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1168
    const result = isNegative ? -absVal : absVal;
×
1169

1170
    return { type: "number", value: Number(result) };
×
1171
  }
1172

1173
  @Validate(1, 1, "math_isfinite", false)
1174
  static math_isfinite(
3✔
1175
    args: Value[],
1176
    source: string,
1177
    command: ControlItem,
1178
    context: Context,
1179
  ): Value {
1180
    const xValObj = args[0];
×
1181
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1182
      handleRuntimeError(
×
1183
        context,
1184
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1185
      );
1186
    }
1187

1188
    const x = Number(xValObj.value);
×
1189
    const result: boolean = Number.isFinite(x);
×
1190

1191
    return { type: "bool", value: result };
×
1192
  }
1193

1194
  @Validate(1, 1, "math_isinf", false)
1195
  static math_isinf(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1196
    const xValObj = args[0];
×
1197
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1198
      handleRuntimeError(
×
1199
        context,
1200
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1201
      );
1202
    }
1203

1204
    const x = Number(xValObj.value);
×
1205
    const result: boolean = x === Infinity || x === -Infinity;
×
1206

1207
    return { type: "bool", value: result };
×
1208
  }
1209

1210
  @Validate(1, 1, "math_isnan", false)
1211
  static math_isnan(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1212
    const xValObj = args[0];
×
1213
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
1214
      handleRuntimeError(
×
1215
        context,
1216
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1217
      );
1218
    }
1219

1220
    const x = Number(xValObj.value);
×
1221
    const result: boolean = Number.isNaN(x);
×
1222

1223
    return { type: "bool", value: result };
×
1224
  }
1225

1226
  @Validate(2, 2, "math_ldexp", false)
1227
  static math_ldexp(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1228
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1229

1230
    if (args[1].type !== "bigint") {
×
1231
      handleRuntimeError(
×
1232
        context,
1233
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1234
      );
1235
    }
1236
    const expVal = args[1].value;
×
1237

1238
    // Perform x * 2^expVal
1239
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1240
    // That behavior parallels typical C library rules for ldexp.
1241
    const result = xVal * Math.pow(2, Number(expVal));
×
1242

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

1246
  static math_nextafter(
1247
    _args: Value[],
1248
    _source: string,
1249
    _command: ControlItem,
1250
    _context: Context,
1251
  ): Value {
1252
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1253
    throw new Error("math_nextafter not implemented");
×
1254
  }
1255

1256
  static math_ulp(
1257
    _args: Value[],
1258
    _source: string,
1259
    _command: ControlItem,
1260
    _context: Context,
1261
  ): Value {
1262
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1263
    throw new Error("math_ulp not implemented");
×
1264
  }
1265

1266
  @Validate(1, 1, "math_cbrt", false)
1267
  static math_cbrt(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1268
    const xVal = args[0];
×
1269
    let x: number;
1270

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

1284
    const result = Math.cbrt(x);
×
1285

1286
    return { type: "number", value: result };
×
1287
  }
1288

1289
  @Validate(1, 1, "math_exp", false)
1290
  static math_exp(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1291
    const xVal = args[0];
×
1292
    let x: number;
1293

1294
    if (xVal.type !== "number") {
×
1295
      if (xVal.type === "bigint") {
×
1296
        x = Number(xVal.value);
×
1297
      } else {
1298
        handleRuntimeError(
×
1299
          context,
1300
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1301
        );
1302
      }
1303
    } else {
1304
      x = xVal.value;
×
1305
    }
1306

1307
    const result = Math.exp(x);
×
1308
    return { type: "number", value: result };
×
1309
  }
1310

1311
  @Validate(1, 1, "math_exps", false)
1312
  static math_exp2(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1313
    const xVal = args[0];
×
1314
    let x: number;
1315

1316
    if (xVal.type !== "number") {
×
1317
      if (xVal.type === "bigint") {
×
1318
        x = Number(xVal.value);
×
1319
      } else {
1320
        handleRuntimeError(
×
1321
          context,
1322
          new TypeError(source, command as ExprNS.Expr, context, xVal.type, "float' or 'int"),
1323
        );
1324
      }
1325
    } else {
1326
      x = xVal.value;
×
1327
    }
1328

1329
    const result = Math.pow(2, x);
×
1330
    return { type: "number", value: result };
×
1331
  }
1332

1333
  @Validate(1, 1, "math_expm1", false)
1334
  static math_expm1(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1335
    const x = args[0];
×
1336
    if (x.type !== "number" && x.type !== "bigint") {
×
1337
      handleRuntimeError(
×
1338
        context,
1339
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1340
      );
1341
    }
1342

1343
    let num: number;
1344
    if (x.type === "number") {
×
1345
      num = x.value;
×
1346
    } else {
1347
      num = Number(x.value);
×
1348
    }
1349

1350
    const result = Math.expm1(num);
×
1351
    return { type: "number", value: result };
×
1352
  }
1353

1354
  @Validate(1, 1, "math_gamma", false)
1355
  static math_gamma(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1356
    const x = args[0];
×
1357
    if (x.type !== "number" && x.type !== "bigint") {
×
1358
      handleRuntimeError(
×
1359
        context,
1360
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1361
      );
1362
    }
1363

1364
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1365
    const result = gamma(z);
×
1366

1367
    return { type: "number", value: result };
×
1368
  }
1369

1370
  @Validate(1, 1, "math_lgamma", false)
1371
  static math_lgamma(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1372
    const x = args[0];
×
1373
    if (x.type !== "number" && x.type !== "bigint") {
×
1374
      handleRuntimeError(
×
1375
        context,
1376
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1377
      );
1378
    }
1379

1380
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1381
    const result = lgamma(z);
×
1382

1383
    return { type: "number", value: result };
×
1384
  }
1385

1386
  @Validate(1, 2, "math_log", true)
1387
  static math_log(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1388
    const x = args[0];
×
1389
    if (x.type !== "number" && x.type !== "bigint") {
×
1390
      handleRuntimeError(
×
1391
        context,
1392
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1393
      );
1394
    }
1395
    let num: number;
1396
    if (x.type === "number") {
×
1397
      num = x.value;
×
1398
    } else {
1399
      num = Number(x.value);
×
1400
    }
1401

1402
    if (num <= 0) {
×
1403
      handleRuntimeError(
×
1404
        context,
1405
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1406
      );
1407
    }
1408

1409
    if (args.length === 1) {
×
1410
      return { type: "number", value: Math.log(num) };
×
1411
    }
1412

1413
    const baseArg = args[1];
×
1414
    if (baseArg.type !== "number" && baseArg.type !== "bigint") {
×
1415
      handleRuntimeError(
×
1416
        context,
1417
        new TypeError(source, command as ExprNS.Expr, context, baseArg.type, "float' or 'int"),
1418
      );
1419
    }
1420
    let baseNum: number;
1421
    if (baseArg.type === "number") {
×
1422
      baseNum = baseArg.value;
×
1423
    } else {
1424
      baseNum = Number(baseArg.value);
×
1425
    }
1426
    if (baseNum <= 0) {
×
1427
      handleRuntimeError(
×
1428
        context,
1429
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1430
      );
1431
    }
1432

1433
    const result = Math.log(num) / Math.log(baseNum);
×
1434
    return { type: "number", value: result };
×
1435
  }
1436

1437
  @Validate(1, 1, "math_log10", false)
1438
  static math_log10(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1439
    const x = args[0];
×
1440
    if (x.type !== "number" && x.type !== "bigint") {
×
1441
      handleRuntimeError(
×
1442
        context,
1443
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1444
      );
1445
    }
1446
    let num: number;
1447
    if (x.type === "number") {
×
1448
      num = x.value;
×
1449
    } else {
1450
      num = Number(x.value);
×
1451
    }
1452
    if (num <= 0) {
×
1453
      handleRuntimeError(
×
1454
        context,
1455
        new ValueError(source, command as ExprNS.Expr, context, "math_log10"),
1456
      );
1457
    }
1458

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

1463
  @Validate(1, 1, "math_log1p", false)
1464
  static math_log1p(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1465
    const x = args[0];
×
1466
    if (x.type !== "number" && x.type !== "bigint") {
×
1467
      handleRuntimeError(
×
1468
        context,
1469
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1470
      );
1471
    }
1472
    let num: number;
1473
    if (x.type === "number") {
×
1474
      num = x.value;
×
1475
    } else {
1476
      num = Number(x.value);
×
1477
    }
1478
    if (1 + num <= 0) {
×
1479
      handleRuntimeError(
×
1480
        context,
1481
        new ValueError(source, command as ExprNS.Expr, context, "math_log1p"),
1482
      );
1483
    }
1484

1485
    const result = Math.log1p(num);
×
1486
    return { type: "number", value: result };
×
1487
  }
1488

1489
  @Validate(1, 1, "math_log2", false)
1490
  static math_log2(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1491
    const x = args[0];
×
1492
    if (x.type !== "number" && x.type !== "bigint") {
×
1493
      handleRuntimeError(
×
1494
        context,
1495
        new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1496
      );
1497
    }
1498
    let num: number;
1499
    if (x.type === "number") {
×
1500
      num = x.value;
×
1501
    } else {
1502
      num = Number(x.value);
×
1503
    }
1504
    if (num <= 0) {
×
1505
      handleRuntimeError(
×
1506
        context,
1507
        new ValueError(source, command as ExprNS.Expr, context, "math_log2"),
1508
      );
1509
    }
1510

1511
    const result = Math.log2(num);
×
1512
    return { type: "number", value: result };
×
1513
  }
1514

1515
  @Validate(2, 2, "math_pow", false)
1516
  static math_pow(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1517
    const base = args[0];
×
1518
    const exp = args[1];
×
1519

1520
    if (base.type !== "number" && base.type !== "bigint") {
×
1521
      handleRuntimeError(
×
1522
        context,
1523
        new TypeError(source, command as ExprNS.Expr, context, base.type, "float' or 'int"),
1524
      );
1525
    } else if (exp.type !== "number" && exp.type !== "bigint") {
×
1526
      handleRuntimeError(
×
1527
        context,
1528
        new TypeError(source, command as ExprNS.Expr, context, exp.type, "float' or 'int"),
1529
      );
1530
    }
1531

1532
    let baseNum: number;
1533
    if (base.type === "number") {
×
1534
      baseNum = base.value;
×
1535
    } else {
1536
      baseNum = Number(base.value);
×
1537
    }
1538

1539
    let expNum: number;
1540
    if (exp.type === "number") {
×
1541
      expNum = exp.value;
×
1542
    } else {
1543
      expNum = Number(exp.value);
×
1544
    }
1545

1546
    const result = Math.pow(baseNum, expNum);
×
1547
    return { type: "number", value: result };
×
1548
  }
1549

1550
  @Validate(1, 1, "math_radians", false)
1551
  static math_radians(
3✔
1552
    args: Value[],
1553
    source: string,
1554
    command: ControlItem,
1555
    context: Context,
1556
  ): Value {
1557
    const x = args[0];
×
1558
    if (x.type !== "number" && x.type !== "bigint") {
×
1559
      handleRuntimeError(
×
1560
        context,
1561
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1562
      );
1563
    }
1564

1565
    let deg: number;
1566
    if (x.type === "number") {
×
1567
      deg = x.value;
×
1568
    } else {
1569
      deg = Number(x.value);
×
1570
    }
1571

1572
    const radians = (deg * Math.PI) / 180;
×
1573
    return { type: "number", value: radians };
×
1574
  }
1575

1576
  @Validate(1, 1, "math_sin", false)
1577
  static math_sin(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1578
    const x = args[0];
×
1579
    if (x.type !== "number" && x.type !== "bigint") {
×
1580
      handleRuntimeError(
×
1581
        context,
1582
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1583
      );
1584
    }
1585

1586
    let num: number;
1587
    if (x.type === "number") {
×
1588
      num = x.value;
×
1589
    } else {
1590
      num = Number(x.value);
×
1591
    }
1592

1593
    const result = Math.sin(num);
×
1594
    return { type: "number", value: result };
×
1595
  }
1596

1597
  @Validate(1, 1, "math_sinh", false)
1598
  static math_sinh(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1599
    const x = args[0];
×
1600
    if (x.type !== "number" && x.type !== "bigint") {
×
1601
      handleRuntimeError(
×
1602
        context,
1603
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1604
      );
1605
    }
1606

1607
    let num: number;
1608
    if (x.type === "number") {
×
1609
      num = x.value;
×
1610
    } else {
1611
      num = Number(x.value);
×
1612
    }
1613

1614
    const result = Math.sinh(num);
×
1615
    return { type: "number", value: result };
×
1616
  }
1617

1618
  @Validate(1, 1, "math_tan", false)
1619
  static math_tan(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1620
    const x = args[0];
×
1621
    if (x.type !== "number" && x.type !== "bigint") {
×
1622
      handleRuntimeError(
×
1623
        context,
1624
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1625
      );
1626
    }
1627

1628
    let num: number;
1629
    if (x.type === "number") {
×
1630
      num = x.value;
×
1631
    } else {
1632
      num = Number(x.value);
×
1633
    }
1634

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

1639
  @Validate(1, 1, "math_tanh", false)
1640
  static math_tanh(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1641
    const x = args[0];
×
1642
    if (x.type !== "number" && x.type !== "bigint") {
×
1643
      handleRuntimeError(
×
1644
        context,
1645
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1646
      );
1647
    }
1648

1649
    let num: number;
1650
    if (x.type === "number") {
×
1651
      num = x.value;
×
1652
    } else {
1653
      num = Number(x.value);
×
1654
    }
1655

1656
    const result = Math.tanh(num);
×
1657
    return { type: "number", value: result };
×
1658
  }
1659

1660
  @Validate(1, 1, "math_sqrt", false)
1661
  static math_sqrt(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1662
    const x = args[0];
×
1663
    if (x.type !== "number" && x.type !== "bigint") {
×
1664
      handleRuntimeError(
×
1665
        context,
1666
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1667
      );
1668
    }
1669

1670
    let num: number;
1671
    if (x.type === "number") {
×
1672
      num = x.value;
×
1673
    } else {
1674
      num = Number(x.value);
×
1675
    }
1676

1677
    if (num < 0) {
×
1678
      handleRuntimeError(
×
1679
        context,
1680
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1681
      );
1682
    }
1683

1684
    const result = Math.sqrt(num);
×
1685
    return { type: "number", value: result };
×
1686
  }
1687

1688
  @Validate(2, null, "max", true)
1689
  static max(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1690
    const numericTypes = ["bigint", "number"];
×
1691
    const firstType = args[0].type;
×
1692
    const isNumeric = numericTypes.includes(firstType);
×
1693
    const isString = firstType === "string";
×
1694

1695
    for (let i = 1; i < args.length; i++) {
×
1696
      const t = args[i].type;
×
1697
      if (isNumeric && !numericTypes.includes(t)) {
×
1698
        handleRuntimeError(
×
1699
          context,
1700
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1701
        );
1702
      }
1703
      if (isString && t !== "string") {
×
1704
        handleRuntimeError(
×
1705
          context,
1706
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1707
        );
1708
      }
1709
    }
1710

1711
    let useFloat = false;
×
1712
    if (isNumeric) {
×
1713
      for (const arg of args) {
×
1714
        if (arg.type === "number") {
×
1715
          useFloat = true;
×
1716
          break;
×
1717
        }
1718
      }
1719
    }
1720

1721
    let maxIndex = 0;
×
1722
    if (isNumeric) {
×
1723
      if (useFloat) {
×
1724
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
1725
          handleRuntimeError(
×
1726
            context,
1727
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1728
          );
1729
        }
1730
        let maxVal: number = Number(args[0].value);
×
1731
        for (let i = 1; i < args.length; i++) {
×
1732
          const arg = args[i];
×
1733
          if (arg.type !== "number" && arg.type !== "bigint") {
×
1734
            handleRuntimeError(
×
1735
              context,
1736
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
1737
            );
1738
          }
1739
          const curr: number = Number(arg.value);
×
1740
          if (curr > maxVal) {
×
1741
            maxVal = curr;
×
1742
            maxIndex = i;
×
1743
          }
1744
        }
1745
      } else {
1746
        if (args[0].type !== "bigint") {
×
1747
          handleRuntimeError(
×
1748
            context,
1749
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
1750
          );
1751
        }
1752
        let maxVal: bigint = args[0].value;
×
1753
        for (let i = 1; i < args.length; i++) {
×
1754
          const arg = args[i];
×
1755
          if (arg.type !== "bigint") {
×
1756
            handleRuntimeError(
×
1757
              context,
1758
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
1759
            );
1760
          }
1761
          const curr: bigint = arg.value;
×
1762
          if (curr > maxVal) {
×
1763
            maxVal = curr;
×
1764
            maxIndex = i;
×
1765
          }
1766
        }
1767
      }
1768
    } else if (isString) {
×
1769
      if (args[0].type !== "string") {
×
1770
        handleRuntimeError(
×
1771
          context,
1772
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
1773
        );
1774
      }
1775
      let maxVal = args[0].value;
×
1776
      for (let i = 1; i < args.length; i++) {
×
1777
        const arg = args[i];
×
1778
        if (arg.type !== "string") {
×
1779
          handleRuntimeError(
×
1780
            context,
1781
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
1782
          );
1783
        }
1784
        const curr = arg.value;
×
1785
        if (curr > maxVal) {
×
1786
          maxVal = curr;
×
1787
          maxIndex = i;
×
1788
        }
1789
      }
1790
    } else {
1791
      // Won't happen
1792
      throw new Error(`max: unsupported type ${firstType}`);
×
1793
    }
1794

1795
    return args[maxIndex];
×
1796
  }
1797

1798
  @Validate(2, null, "min", true)
1799
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1800
    if (args.length < 2) {
×
1801
      handleRuntimeError(
×
1802
        context,
1803
        new MissingRequiredPositionalError(
1804
          source,
1805
          command as ExprNS.Expr,
1806
          "min",
1807
          Number(2),
1808
          args,
1809
          true,
1810
        ),
1811
      );
1812
    }
1813

1814
    const numericTypes = ["bigint", "number"];
×
1815
    const firstType = args[0].type;
×
1816
    const isNumeric = numericTypes.includes(firstType);
×
1817
    const isString = firstType === "string";
×
1818

1819
    for (let i = 1; i < args.length; i++) {
×
1820
      const t = args[i].type;
×
1821
      if (isNumeric && !numericTypes.includes(t)) {
×
1822
        handleRuntimeError(
×
1823
          context,
1824
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1825
        );
1826
      }
1827
      if (isString && t !== "string") {
×
1828
        handleRuntimeError(
×
1829
          context,
1830
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1831
        );
1832
      }
1833
    }
1834

1835
    let useFloat = false;
×
1836
    if (isNumeric) {
×
1837
      for (const arg of args) {
×
1838
        if (arg.type === "number") {
×
1839
          useFloat = true;
×
1840
          break;
×
1841
        }
1842
      }
1843
    }
1844

1845
    let maxIndex = 0;
×
1846
    if (isNumeric) {
×
1847
      if (useFloat) {
×
1848
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
1849
          handleRuntimeError(
×
1850
            context,
1851
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "float' or 'int"),
1852
          );
1853
        }
1854
        let maxVal: number = Number(args[0].value);
×
1855
        for (let i = 1; i < args.length; i++) {
×
1856
          const arg = args[i];
×
1857
          if (arg.type !== "number" && arg.type !== "bigint") {
×
1858
            handleRuntimeError(
×
1859
              context,
1860
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "float' or 'int"),
1861
            );
1862
          }
1863
          const curr: number = Number(arg.value);
×
1864
          if (curr < maxVal) {
×
1865
            maxVal = curr;
×
1866
            maxIndex = i;
×
1867
          }
1868
        }
1869
      } else {
1870
        if (args[0].type !== "bigint") {
×
1871
          handleRuntimeError(
×
1872
            context,
1873
            new TypeError(source, command as ExprNS.Expr, context, args[0].type, "int"),
1874
          );
1875
        }
1876
        let maxVal: bigint = args[0].value;
×
1877
        for (let i = 1; i < args.length; i++) {
×
1878
          const arg = args[i];
×
1879
          if (arg.type !== "bigint") {
×
1880
            handleRuntimeError(
×
1881
              context,
1882
              new TypeError(source, command as ExprNS.Expr, context, arg.type, "int"),
1883
            );
1884
          }
1885
          const curr: bigint = arg.value;
×
1886
          if (curr < maxVal) {
×
1887
            maxVal = curr;
×
1888
            maxIndex = i;
×
1889
          }
1890
        }
1891
      }
1892
    } else if (isString) {
×
1893
      if (args[0].type !== "string") {
×
1894
        handleRuntimeError(
×
1895
          context,
1896
          new TypeError(source, command as ExprNS.Expr, context, args[0].type, "string"),
1897
        );
1898
      }
1899
      let maxVal = args[0].value;
×
1900
      for (let i = 1; i < args.length; i++) {
×
1901
        const arg = args[i];
×
1902
        if (arg.type !== "string") {
×
1903
          handleRuntimeError(
×
1904
            context,
1905
            new TypeError(source, command as ExprNS.Expr, context, arg.type, "string"),
1906
          );
1907
        }
1908
        const curr = arg.value;
×
1909
        if (curr < maxVal) {
×
1910
          maxVal = curr;
×
1911
          maxIndex = i;
×
1912
        }
1913
      }
1914
    } else {
1915
      // Won't happen
1916
      throw new Error(`min: unsupported type ${firstType}`);
×
1917
    }
1918

1919
    return args[maxIndex];
×
1920
  }
1921

1922
  @Validate(null, 0, "random_random", true)
1923
  static random_random(
3✔
1924
    _args: Value[],
1925
    _source: string,
1926
    _command: ControlItem,
1927
    _context: Context,
1928
  ): Value {
1929
    const result = Math.random();
×
1930
    return { type: "number", value: result };
×
1931
  }
1932

1933
  @Validate(1, 2, "round", true)
1934
  static round(args: Value[], source: string, command: ControlItem, context: Context): Value {
3✔
1935
    const numArg = args[0];
×
1936
    if (numArg.type !== "number" && numArg.type !== "bigint") {
×
1937
      handleRuntimeError(
×
1938
        context,
1939
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
1940
      );
1941
    }
1942

1943
    let ndigitsArg = { type: "bigint", value: BigInt(0) };
×
1944
    if (args.length === 2 && args[1].type !== "none") {
×
1945
      if (args[1].type !== "bigint") {
×
1946
        handleRuntimeError(
×
1947
          context,
1948
          new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1949
        );
1950
      }
1951
      ndigitsArg = args[1];
×
1952
    }
1953

1954
    if (numArg.type === "number") {
×
1955
      const numberValue: number = numArg.value;
×
1956
      if (ndigitsArg.value > 0) {
×
1957
        const shifted = Number(numberValue.toFixed(Number(ndigitsArg.value)));
×
1958
        return { type: "number", value: shifted };
×
1959
      } else if (ndigitsArg.value === BigInt(0)) {
×
1960
        const shifted = Math.round(numArg.value);
×
1961
        return { type: "bigint", value: BigInt(shifted) };
×
1962
      } else {
1963
        const shifted =
1964
          Math.round(numArg.value / 10 ** -Number(ndigitsArg.value)) *
×
1965
          10 ** -Number(ndigitsArg.value);
1966
        return { type: "number", value: shifted };
×
1967
      }
1968
    } else {
1969
      if (ndigitsArg.value >= 0) {
×
1970
        return numArg;
×
1971
      } else {
1972
        const shifted: bigint =
1973
          (numArg.value / BigInt(10) ** -ndigitsArg.value) * BigInt(10) ** -ndigitsArg.value;
×
1974
        return { type: "bigint", value: shifted };
×
1975
      }
1976
    }
1977
  }
1978

1979
  @Validate(null, 0, "time_time", true)
1980
  static time_time(
3✔
1981
    _args: Value[],
1982
    _source: string,
1983
    _command: ControlItem,
1984
    _context: Context,
1985
  ): Value {
1986
    const currentTime = Date.now();
×
1987
    return { type: "number", value: currentTime };
×
1988
  }
1989

1990
  static input(_args: Value[], _source: string, _command: ControlItem, _context: Context): Value {
1991
    // TODO: : call conductor to receive user input
1992
    return { type: "string", value: "" };
×
1993
  }
1994

1995
  static print(args: Value[], _source: string, _command: ControlItem, context: Context) {
1996
    const output = args.map(arg => toPythonString(arg)).join(" ");
×
NEW
1997
    displayOutput(context, output);
×
UNCOV
1998
    return { type: "undefined" };
×
1999
  }
2000

2001
  static str(args: Value[], _source: string, _command: ControlItem, _context: Context): Value {
2002
    if (args.length === 0) {
×
2003
      return { type: "string", value: "" };
×
2004
    }
2005
    const obj = args[0];
×
2006
    const result = toPythonString(obj);
×
2007
    return { type: "string", value: result };
×
2008
  }
2009
}
2010

2011
import { ExprNS, StmtNS } from "./ast-types";
3✔
2012
import py_s1_constants from "./stdlib/py_s1_constants.json";
3✔
2013

2014
// NOTE: If we ever switch to another Python “chapter” (e.g. py_s2_constants),
2015
//       just change the variable below to switch to the set.
2016
const constants = py_s1_constants;
3✔
2017

2018
/*
2019
    Create a map to hold built-in constants.
2020
    Each constant is stored with a string key and its corresponding value object.
2021
*/
2022
export const builtInConstants = new Map<string, Value>();
3✔
2023

2024
const constantMap = {
3✔
2025
  math_e: { type: "number", value: Math.E },
2026
  math_inf: { type: "number", value: Infinity },
2027
  math_nan: { type: "number", value: NaN },
2028
  math_pi: { type: "number", value: Math.PI },
2029
  math_tau: { type: "number", value: 2 * Math.PI },
2030
} as const;
2031

2032
for (const name of constants.constants) {
3✔
2033
  const valueObj = constantMap[name as keyof typeof constantMap];
15✔
2034
  if (!valueObj) {
15!
UNCOV
2035
    throw new Error(`Constant '${name}' is not implemented`);
×
2036
  }
2037
  builtInConstants.set(name, valueObj);
15✔
2038
}
2039

2040
/*
2041
    Create a map to hold built-in functions.
2042
    The keys are strings (function names) and the values are functions that can take any arguments.
2043
*/
2044
export const builtIns = new Map<string, Value>();
3✔
2045
for (const name of constants.builtInFuncs) {
3✔
2046
  const impl = BuiltInFunctions[name as keyof BuiltInFunctions];
189✔
2047
  if (typeof impl !== "function") {
189!
UNCOV
2048
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
2049
  }
2050
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
189✔
2051
  builtIns.set(name, { type: "builtin", name: builtinName, func: impl });
189✔
2052
}
2053

2054
/**
2055
 * Converts a number to a string that mimics Python's float formatting behavior.
2056
 *
2057
 * In Python, float values are printed in scientific notation when their absolute value
2058
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
2059
 * so we explicitly enforce these formatting thresholds.
2060
 *
2061
 * The logic here is based on Python's internal `format_float_short` implementation
2062
 * in CPython's `pystrtod.c`:
2063
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
2064
 *
2065
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
2066
 * output matches Python’s display conventions.
2067
 */
2068
export function toPythonFloat(num: number): string {
3✔
UNCOV
2069
  if (Object.is(num, -0)) {
×
UNCOV
2070
    return "-0.0";
×
2071
  }
UNCOV
2072
  if (num === 0) {
×
UNCOV
2073
    return "0.0";
×
2074
  }
2075

UNCOV
2076
  if (num === Infinity) {
×
UNCOV
2077
    return "inf";
×
2078
  }
2079
  if (num === -Infinity) {
×
2080
    return "-inf";
×
2081
  }
2082

2083
  if (Number.isNaN(num)) {
×
UNCOV
2084
    return "nan";
×
2085
  }
2086

2087
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
×
UNCOV
2088
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2089
  }
2090
  if (Number.isInteger(num)) {
×
UNCOV
2091
    return num.toFixed(1).toString();
×
2092
  }
2093
  return num.toString();
×
2094
}
2095

2096
export function toPythonString(obj: Value): string {
3✔
2097
  let ret = "";
×
2098
  if (!obj) {
×
UNCOV
2099
    return "None";
×
2100
  }
2101
  if (obj.type == "builtin") {
×
UNCOV
2102
    return `<built-in function ${obj.name}>`;
×
2103
  }
UNCOV
2104
  if (obj.type === "bigint" || obj.type === "complex") {
×
UNCOV
2105
    ret = obj.value.toString();
×
UNCOV
2106
  } else if (obj.type === "number") {
×
2107
    ret = toPythonFloat(obj.value);
×
2108
  } else if (obj.type === "bool") {
×
2109
    if (obj.value === true) {
×
UNCOV
2110
      return "True";
×
2111
    } else {
2112
      return "False";
×
2113
    }
2114
  } else if (obj.type === "error") {
×
2115
    return obj.message;
×
2116
  } else if (obj.type === "closure") {
×
2117
    if (obj.closure.node instanceof StmtNS.FunctionDef) {
×
2118
      return `<function ${obj.closure.node.name.lexeme}>`;
×
2119
    }
2120
    return "<function (anonymous)>";
×
UNCOV
2121
  } else if (obj.type === "none") {
×
2122
    ret = "None";
×
UNCOV
2123
  } else if (obj.type === "string") {
×
2124
    ret = obj.value.toString();
×
2125
  }
2126
  return ret;
×
2127
}
2128

2129
export function str(
3✔
2130
  args: Value[],
2131
  _source: string,
2132
  _command: ControlItem,
2133
  _context: Context,
2134
): Value {
UNCOV
2135
  if (args.length === 0) {
×
2136
    return { type: "string", value: "" };
×
2137
  }
UNCOV
2138
  const obj = args[0];
×
UNCOV
2139
  const result = toPythonString(obj);
×
UNCOV
2140
  return { type: "string", value: result };
×
2141
}
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