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

source-academy / py-slang / 23249736440

18 Mar 2026 02:28PM UTC coverage: 34.925% (+0.09%) from 34.836%
23249736440

push

github

web-flow
Package improvements follow up (#99)

* Revert "Silence lint for now"

This reverts commit 11994b3bf.

* Revert "Undo ESLint fixes temporarily"

This reverts commit bde87b0de.

* Clean up import

* Remove more unused dependencies

* Deduplicate dependencies

* Remove even more unused packages

* Remove unused args + Fix error typing

* Upgrade Yarn v4 and add TS SDK

* Fix formatting of `stdlib.ts` and `interpreter.ts`

* Fix CI changes

* Move corepack enable before setup-node

* Fix checksums

* Add missing Yarn config

* Commit loader

* Delete wrong files

* Fix VSCode settings

* Simplify and ignore PnP files

* Fix phantom dependencies

* Reformat

* Set jsdoc binary path

* Use Yarn to run scripts

* Go back to node linker

* Remove taffy

* Remove ZipFS recommendation

---------

Co-authored-by: Aarav Malani <aarav.malani@gmail.com>

266 of 1054 branches covered (25.24%)

Branch coverage included in aggregate %.

7 of 107 new or added lines in 7 files covered. (6.54%)

3 existing lines in 1 file now uncovered.

1109 of 2883 relevant lines covered (38.47%)

84.72 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

593
    const erfnum = erf(num);
×
594

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

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

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

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

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

637
    const index = i.value;
×
638

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1794
    return args[maxIndex];
×
1795
  }
1796

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

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

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

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

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

1918
    return args[maxIndex];
×
1919
  }
1920

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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