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

source-academy / py-slang / 23097039458

14 Mar 2026 09:47PM UTC coverage: 34.836% (-0.004%) from 34.84%
23097039458

push

github

web-flow
Various package improvements (#88)

* Add ESLint and Prettier + make formatting changes

* Add linting to the workflow

* Add format to CI

* Fix the remaining types + make Value types stricter + temporarily remove tests from ESLint

* Add prettierignore + Extend prettier to entire repository + fix ESLint not working on tests + Move linter + formatters to `devDependencies`

* Fix formatting of `eslint.config.mjs`

* update package-lock.json;

* adding back rollup-plugin-sourcemaps to fix build failure

* Restore Coveralls workflow

* Update .prettierrc

* Reformat

* Add missing newline at EOF

* Sort imports

* Add VSCode config

* Migrate to Yarn as package manager

* Update workflows to use Yarn

* Fix inconsistent versions

* Remove useless comment

* Clean up migrated Yarn lockfile

* Fix incorrect merge deleting coverage script

* Remove unnecessary flag

* Remove unnecessary items from prettierignore

* Add VSCode settings for NL at EOF

* Clean up tsconfig

* Add coverage to ESLint ignore

* Fix mistakes from previous merge resolution

* Clean up dependencies

* Switch to double quotes

* Revert "Switch to double quotes"

This reverts commit 830c26c72.

* Switch to double quotes properly

* Undo ESLint fixes temporarily

* Silence lint for now

---------

Co-authored-by: Lucas Ng <152241991+lucasnyc@users.noreply.github.com>
Co-authored-by: Lucas <nyc@yuchengng.com>
Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>

267 of 1065 branches covered (25.07%)

Branch coverage included in aggregate %.

877 of 2551 new or added lines in 20 files covered. (34.38%)

15 existing lines in 5 files now uncovered.

1124 of 2928 relevant lines covered (38.39%)

83.43 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

256
  static toStr(val: Value): string {
NEW
257
    return String(val.value);
×
258
  }
259

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

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

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

NEW
319
    const result = obj.type === expectedType;
×
320

NEW
321
    return { type: "bool", value: result };
×
322
  }
323

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

334
    let num: number;
NEW
335
    if (x.type === "number") {
×
NEW
336
      num = x.value;
×
337
    } else {
NEW
338
      num = Number(x.value);
×
339
    }
340

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

NEW
348
    const result = Math.acos(num);
×
NEW
349
    return { type: "number", value: result };
×
350
  }
351

352
  @Validate(1, 1, "math_acosh", false)
353
  static math_acosh(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
354
    const x = args[0];
×
355

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

363
    let num: number;
NEW
364
    if (x.type === "number") {
×
NEW
365
      num = x.value;
×
366
    } else {
NEW
367
      num = Number(x.value);
×
368
    }
369

NEW
370
    if (num < 1) {
×
NEW
371
      handleRuntimeError(
×
372
        context,
373
        new ValueError(source, command as ExprNS.Expr, context, "math_acosh"),
374
      );
375
    }
376

NEW
377
    const result = Math.acosh(num);
×
NEW
378
    return { type: "number", value: result };
×
379
  }
380

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

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

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

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

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

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

NEW
426
    const result = Math.asinh(num);
×
NEW
427
    return { type: "number", value: result };
×
428
  }
429

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

440
    let num: number;
NEW
441
    if (x.type === "number") {
×
NEW
442
      num = x.value;
×
443
    } else {
NEW
444
      num = Number(x.value);
×
445
    }
446

NEW
447
    const result = Math.atan(num);
×
NEW
448
    return { type: "number", value: result };
×
449
  }
450

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

467
    let yNum: number, xNum: number;
NEW
468
    if (y.type === "number") {
×
NEW
469
      yNum = y.value;
×
470
    } else {
NEW
471
      yNum = Number(y.value);
×
472
    }
473

NEW
474
    if (x.type === "number") {
×
NEW
475
      xNum = x.value;
×
476
    } else {
NEW
477
      xNum = Number(x.value);
×
478
    }
479

NEW
480
    const result = Math.atan2(yNum, xNum);
×
NEW
481
    return { type: "number", value: result };
×
482
  }
483

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

494
    let num: number;
NEW
495
    if (x.type === "number") {
×
NEW
496
      num = x.value;
×
497
    } else {
NEW
498
      num = Number(x.value);
×
499
    }
500

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

NEW
508
    const result = Math.atanh(num);
×
NEW
509
    return { type: "number", value: result };
×
510
  }
511

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

522
    let num: number;
NEW
523
    if (x.type === "number") {
×
NEW
524
      num = x.value;
×
525
    } else {
NEW
526
      num = Number(x.value);
×
527
    }
528

NEW
529
    const result = Math.cos(num);
×
NEW
530
    return { type: "number", value: result };
×
531
  }
532

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

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

NEW
550
    const result = Math.cosh(num);
×
NEW
551
    return { type: "number", value: result };
×
552
  }
553

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

569
    let num: number;
NEW
570
    if (x.type === "number") {
×
NEW
571
      num = x.value;
×
572
    } else {
NEW
573
      num = Number(x.value);
×
574
    }
575

NEW
576
    const result = (num * 180) / Math.PI;
×
NEW
577
    return { type: "number", value: result };
×
578
  }
579

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

590
    let num: number;
NEW
591
    if (x.type === "number") {
×
NEW
592
      num = x.value;
×
593
    } else {
NEW
594
      num = Number(x.value);
×
595
    }
596

NEW
597
    const erfnum = erf(num);
×
598

NEW
599
    return { type: "number", value: erfnum };
×
600
  }
601

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

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

NEW
614
    return { type: "number", value: erfc };
×
615
  }
616

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

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

NEW
635
    const index = i.value;
×
636

NEW
637
    return { type: "string", value: s.value[index] };
×
638
  }
639

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

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

NEW
657
    const nVal = BigInt(n.value);
×
NEW
658
    const kVal = BigInt(k.value);
×
659

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

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

NEW
671
    let result: bigint = BigInt(1);
×
NEW
672
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
673

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

NEW
678
    return { type: "bigint", value: result };
×
679
  }
680

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

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

NEW
697
    const nVal = BigInt(n.value);
×
698

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

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

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

NEW
716
    return { type: "bigint", value: result };
×
717
  }
718

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

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

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

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

NEW
747
    return { type: "bigint", value: currentGcd };
×
748
  }
749

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

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

NEW
771
    const n: bigint = nValObj.value;
×
772

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

NEW
780
    if (n < 2) {
×
NEW
781
      return { type: "bigint", value: n };
×
782
    }
783

NEW
784
    let low: bigint = BigInt(1);
×
NEW
785
    let high: bigint = n;
×
786

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

NEW
797
    return { type: "bigint", value: low };
×
798
  }
799

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

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

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

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

NEW
827
    return { type: "bigint", value: currentLcm };
×
828
  }
829

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

835
  static absBigInt(x: bigint): bigint {
NEW
836
    return x < 0 ? -x : x;
×
837
  }
838

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

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

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

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

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

NEW
881
    return { type: "bigint", value: result };
×
882
  }
883

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

NEW
888
    if (x.type === "bigint") {
×
NEW
889
      return x;
×
890
    }
891

NEW
892
    if (x.type === "number") {
×
NEW
893
      const numVal = x.value;
×
NEW
894
      if (typeof numVal !== "number") {
×
NEW
895
        handleRuntimeError(
×
896
          context,
897
          new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
898
        );
899
      }
NEW
900
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
NEW
901
      return { type: "bigint", value: ceiled };
×
902
    }
903

NEW
904
    handleRuntimeError(
×
905
      context,
906
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
907
    );
908
  }
909

910
  @Validate(1, 1, "math_fabs", false)
911
  static math_fabs(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
912
    const x = args[0];
×
913

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

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

NEW
932
    handleRuntimeError(
×
933
      context,
934
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
935
    );
936
  }
937

938
  @Validate(1, 1, "math_floor", false)
939
  static math_floor(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
940
    const x = args[0];
×
941

NEW
942
    if (x.type === "bigint") {
×
NEW
943
      return x;
×
944
    }
945

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

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

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

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

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

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

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

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

NEW
1024
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
NEW
1025
    return { type: "number", value: result };
×
1026
  }
1027

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

1034
    // Divisor cannot be zero
NEW
1035
    if (yVal === 0) {
×
NEW
1036
      handleRuntimeError(
×
1037
        context,
1038
        new ValueError(source, command as ExprNS.Expr, context, "math_fmod"),
1039
      );
1040
    }
1041

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

NEW
1048
    return { type: "number", value: remainder };
×
1049
  }
1050

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

1065
  @Validate(2, 2, "math_remainder", false)
1066
  static math_remainder(
2✔
1067
    args: Value[],
1068
    source: string,
1069
    command: ControlItem,
1070
    context: Context,
1071
  ): Value {
NEW
1072
    const x = args[0];
×
NEW
1073
    const y = args[1];
×
1074

1075
    let xValue: number;
NEW
1076
    if (x.type === "bigint") {
×
NEW
1077
      xValue = Number(x.value);
×
NEW
1078
    } else if (x.type === "number") {
×
NEW
1079
      xValue = x.value;
×
1080
    } else {
NEW
1081
      handleRuntimeError(
×
1082
        context,
1083
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1084
      );
NEW
1085
      return;
×
1086
    }
1087

1088
    let yValue: number;
NEW
1089
    if (y.type === "bigint") {
×
NEW
1090
      yValue = Number(y.value);
×
NEW
1091
    } else if (y.type === "number") {
×
NEW
1092
      yValue = y.value;
×
1093
    } else {
NEW
1094
      handleRuntimeError(
×
1095
        context,
1096
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1097
      );
NEW
1098
      return;
×
1099
    }
1100

NEW
1101
    if (yValue === 0) {
×
NEW
1102
      handleRuntimeError(
×
1103
        context,
1104
        new ValueError(source, command as ExprNS.Expr, context, "math_remainder"),
1105
      );
1106
    }
1107

NEW
1108
    const quotient = xValue / yValue;
×
NEW
1109
    const n = BuiltInFunctions.roundToEven(quotient);
×
NEW
1110
    const remainder = xValue - n * yValue;
×
1111

NEW
1112
    return { type: "number", value: remainder };
×
1113
  }
1114

1115
  @Validate(1, 1, "math_trunc", false)
1116
  static math_trunc(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1117
    const x = args[0];
×
1118

NEW
1119
    if (x.type === "bigint") {
×
NEW
1120
      return x;
×
1121
    }
1122

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

NEW
1142
    handleRuntimeError(
×
1143
      context,
1144
      new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1145
    );
1146
  }
1147

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

NEW
1157
    if (x.type !== "number" && x.type !== "bigint") {
×
NEW
1158
      handleRuntimeError(
×
1159
        context,
1160
        new TypeError(source, command as ExprNS.Expr, context, x.type, "float' or 'int"),
1161
      );
NEW
1162
    } else if (y.type !== "number" && y.type !== "bigint") {
×
NEW
1163
      handleRuntimeError(
×
1164
        context,
1165
        new TypeError(source, command as ExprNS.Expr, context, y.type, "float' or 'int"),
1166
      );
1167
    }
1168

NEW
1169
    const xVal = Number(x.value);
×
NEW
1170
    const yVal = Number(y.value);
×
1171

NEW
1172
    const absVal = Math.abs(xVal);
×
NEW
1173
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
NEW
1174
    const result = isNegative ? -absVal : absVal;
×
1175

NEW
1176
    return { type: "number", value: Number(result) };
×
1177
  }
1178

1179
  @Validate(1, 1, "math_isfinite", false)
1180
  static math_isfinite(
2✔
1181
    args: Value[],
1182
    source: string,
1183
    command: ControlItem,
1184
    context: Context,
1185
  ): Value {
NEW
1186
    const xValObj = args[0];
×
NEW
1187
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
NEW
1188
      handleRuntimeError(
×
1189
        context,
1190
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1191
      );
1192
    }
1193

NEW
1194
    const x = Number(xValObj.value);
×
NEW
1195
    const result: boolean = Number.isFinite(x);
×
1196

NEW
1197
    return { type: "bool", value: result };
×
1198
  }
1199

1200
  @Validate(1, 1, "math_isinf", false)
1201
  static math_isinf(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1202
    const xValObj = args[0];
×
NEW
1203
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
NEW
1204
      handleRuntimeError(
×
1205
        context,
1206
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1207
      );
1208
    }
1209

NEW
1210
    const x = Number(xValObj.value);
×
NEW
1211
    const result: boolean = x === Infinity || x === -Infinity;
×
1212

NEW
1213
    return { type: "bool", value: result };
×
1214
  }
1215

1216
  @Validate(1, 1, "math_isnan", false)
1217
  static math_isnan(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1218
    const xValObj = args[0];
×
NEW
1219
    if (xValObj.type !== "number" && xValObj.type !== "bigint") {
×
NEW
1220
      handleRuntimeError(
×
1221
        context,
1222
        new TypeError(source, command as ExprNS.Expr, context, xValObj.type, "float' or 'int"),
1223
      );
1224
    }
1225

NEW
1226
    const x = Number(xValObj.value);
×
NEW
1227
    const result: boolean = Number.isNaN(x);
×
1228

NEW
1229
    return { type: "bool", value: result };
×
1230
  }
1231

1232
  @Validate(2, 2, "math_ldexp", false)
1233
  static math_ldexp(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1234
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1235

NEW
1236
    if (args[1].type !== "bigint") {
×
NEW
1237
      handleRuntimeError(
×
1238
        context,
1239
        new TypeError(source, command as ExprNS.Expr, context, args[1].type, "int"),
1240
      );
1241
    }
NEW
1242
    const expVal = args[1].value;
×
1243

1244
    // Perform x * 2^expVal
1245
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1246
    // That behavior parallels typical C library rules for ldexp.
NEW
1247
    const result = xVal * Math.pow(2, Number(expVal));
×
1248

NEW
1249
    return { type: "number", value: result };
×
1250
  }
1251

1252
  static math_nextafter(
1253
    args: Value[],
1254
    source: string,
1255
    command: ControlItem,
1256
    context: Context,
1257
  ): Value {
1258
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
NEW
1259
    throw new Error("math_nextafter not implemented");
×
1260
  }
1261

1262
  static math_ulp(args: Value[], source: string, command: ControlItem, context: Context): Value {
1263
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
NEW
1264
    throw new Error("math_ulp not implemented");
×
1265
  }
1266

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

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

NEW
1286
    const result = Math.cbrt(x);
×
1287

NEW
1288
    return { type: "number", value: result };
×
1289
  }
1290

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

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

NEW
1310
    const result = Math.exp(x);
×
NEW
1311
    return { type: "number", value: result };
×
1312
  }
1313

1314
  @Validate(1, 1, "math_exps", false)
1315
  static math_exp2(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1316
    const xVal = args[0];
×
1317
    let x: number;
1318

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

NEW
1333
    const result = Math.pow(2, x);
×
NEW
1334
    return { type: "number", value: result };
×
1335
  }
1336

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

1347
    let num: number;
NEW
1348
    if (x.type === "number") {
×
NEW
1349
      num = x.value;
×
1350
    } else {
NEW
1351
      num = Number(x.value);
×
1352
    }
1353

NEW
1354
    const result = Math.expm1(num);
×
NEW
1355
    return { type: "number", value: result };
×
1356
  }
1357

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

NEW
1368
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
NEW
1369
    const result = gamma(z);
×
1370

NEW
1371
    return { type: "number", value: result };
×
1372
  }
1373

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

NEW
1384
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
NEW
1385
    const result = lgamma(z);
×
1386

NEW
1387
    return { type: "number", value: result };
×
1388
  }
1389

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

NEW
1406
    if (num <= 0) {
×
NEW
1407
      handleRuntimeError(
×
1408
        context,
1409
        new ValueError(source, command as ExprNS.Expr, context, "math_log"),
1410
      );
1411
    }
1412

NEW
1413
    if (args.length === 1) {
×
NEW
1414
      return { type: "number", value: Math.log(num) };
×
1415
    }
1416

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

NEW
1437
    const result = Math.log(num) / Math.log(baseNum);
×
NEW
1438
    return { type: "number", value: result };
×
1439
  }
1440

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

NEW
1463
    const result = Math.log10(num);
×
NEW
1464
    return { type: "number", value: result };
×
1465
  }
1466

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

NEW
1489
    const result = Math.log1p(num);
×
NEW
1490
    return { type: "number", value: result };
×
1491
  }
1492

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

NEW
1515
    const result = Math.log2(num);
×
NEW
1516
    return { type: "number", value: result };
×
1517
  }
1518

1519
  @Validate(2, 2, "math_pow", false)
1520
  static math_pow(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1521
    const base = args[0];
×
NEW
1522
    const exp = args[1];
×
1523

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

1536
    let baseNum: number;
NEW
1537
    if (base.type === "number") {
×
NEW
1538
      baseNum = base.value;
×
1539
    } else {
NEW
1540
      baseNum = Number(base.value);
×
1541
    }
1542

1543
    let expNum: number;
NEW
1544
    if (exp.type === "number") {
×
NEW
1545
      expNum = exp.value;
×
1546
    } else {
NEW
1547
      expNum = Number(exp.value);
×
1548
    }
1549

NEW
1550
    const result = Math.pow(baseNum, expNum);
×
NEW
1551
    return { type: "number", value: result };
×
1552
  }
1553

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

1569
    let deg: number;
NEW
1570
    if (x.type === "number") {
×
NEW
1571
      deg = x.value;
×
1572
    } else {
NEW
1573
      deg = Number(x.value);
×
1574
    }
1575

NEW
1576
    const radians = (deg * Math.PI) / 180;
×
NEW
1577
    return { type: "number", value: radians };
×
1578
  }
1579

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

1590
    let num: number;
NEW
1591
    if (x.type === "number") {
×
NEW
1592
      num = x.value;
×
1593
    } else {
NEW
1594
      num = Number(x.value);
×
1595
    }
1596

NEW
1597
    const result = Math.sin(num);
×
NEW
1598
    return { type: "number", value: result };
×
1599
  }
1600

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

1611
    let num: number;
NEW
1612
    if (x.type === "number") {
×
NEW
1613
      num = x.value;
×
1614
    } else {
NEW
1615
      num = Number(x.value);
×
1616
    }
1617

NEW
1618
    const result = Math.sinh(num);
×
NEW
1619
    return { type: "number", value: result };
×
1620
  }
1621

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

1632
    let num: number;
NEW
1633
    if (x.type === "number") {
×
NEW
1634
      num = x.value;
×
1635
    } else {
NEW
1636
      num = Number(x.value);
×
1637
    }
1638

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

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

1653
    let num: number;
NEW
1654
    if (x.type === "number") {
×
NEW
1655
      num = x.value;
×
1656
    } else {
NEW
1657
      num = Number(x.value);
×
1658
    }
1659

NEW
1660
    const result = Math.tanh(num);
×
NEW
1661
    return { type: "number", value: result };
×
1662
  }
1663

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

1674
    let num: number;
NEW
1675
    if (x.type === "number") {
×
NEW
1676
      num = x.value;
×
1677
    } else {
NEW
1678
      num = Number(x.value);
×
1679
    }
1680

NEW
1681
    if (num < 0) {
×
NEW
1682
      handleRuntimeError(
×
1683
        context,
1684
        new ValueError(source, command as ExprNS.Expr, context, "math_sqrt"),
1685
      );
1686
    }
1687

NEW
1688
    const result = Math.sqrt(num);
×
NEW
1689
    return { type: "number", value: result };
×
1690
  }
1691

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

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

NEW
1715
    let useFloat = false;
×
NEW
1716
    if (isNumeric) {
×
NEW
1717
      for (const arg of args) {
×
NEW
1718
        if (arg.type === "number") {
×
NEW
1719
          useFloat = true;
×
NEW
1720
          break;
×
1721
        }
1722
      }
1723
    }
1724

NEW
1725
    let maxIndex = 0;
×
NEW
1726
    if (isNumeric) {
×
NEW
1727
      if (useFloat) {
×
NEW
1728
        let maxVal: number = Number(args[0].value);
×
NEW
1729
        for (let i = 1; i < args.length; i++) {
×
NEW
1730
          const curr: number = Number(args[i].value);
×
NEW
1731
          if (curr > maxVal) {
×
NEW
1732
            maxVal = curr;
×
NEW
1733
            maxIndex = i;
×
1734
          }
1735
        }
1736
      } else {
NEW
1737
        let maxVal: bigint = args[0].value;
×
NEW
1738
        for (let i = 1; i < args.length; i++) {
×
NEW
1739
          const curr: bigint = args[i].value;
×
NEW
1740
          if (curr > maxVal) {
×
NEW
1741
            maxVal = curr;
×
NEW
1742
            maxIndex = i;
×
1743
          }
1744
        }
1745
      }
NEW
1746
    } else if (isString) {
×
NEW
1747
      let maxVal = args[0].value as string;
×
NEW
1748
      for (let i = 1; i < args.length; i++) {
×
NEW
1749
        const curr = args[i].value as string;
×
NEW
1750
        if (curr > maxVal) {
×
NEW
1751
          maxVal = curr;
×
NEW
1752
          maxIndex = i;
×
1753
        }
1754
      }
1755
    } else {
1756
      // Won't happen
NEW
1757
      throw new Error(`max: unsupported type ${firstType}`);
×
1758
    }
1759

NEW
1760
    return args[maxIndex];
×
1761
  }
1762

1763
  @Validate(2, null, "min", true)
1764
  static min(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1765
    if (args.length < 2) {
×
NEW
1766
      handleRuntimeError(
×
1767
        context,
1768
        new MissingRequiredPositionalError(
1769
          source,
1770
          command as ExprNS.Expr,
1771
          "min",
1772
          Number(2),
1773
          args,
1774
          true,
1775
        ),
1776
      );
1777
    }
1778

NEW
1779
    const numericTypes = ["bigint", "number"];
×
NEW
1780
    const firstType = args[0].type;
×
NEW
1781
    const isNumeric = numericTypes.includes(firstType);
×
NEW
1782
    const isString = firstType === "string";
×
1783

NEW
1784
    for (let i = 1; i < args.length; i++) {
×
NEW
1785
      const t = args[i].type;
×
NEW
1786
      if (isNumeric && !numericTypes.includes(t)) {
×
NEW
1787
        handleRuntimeError(
×
1788
          context,
1789
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "float' or 'int"),
1790
        );
1791
      }
NEW
1792
      if (isString && t !== "string") {
×
NEW
1793
        handleRuntimeError(
×
1794
          context,
1795
          new TypeError(source, command as ExprNS.Expr, context, args[i].type, "string"),
1796
        );
1797
      }
1798
    }
1799

NEW
1800
    let useFloat = false;
×
NEW
1801
    if (isNumeric) {
×
NEW
1802
      for (const arg of args) {
×
NEW
1803
        if (arg.type === "number") {
×
NEW
1804
          useFloat = true;
×
NEW
1805
          break;
×
1806
        }
1807
      }
1808
    }
1809

NEW
1810
    let maxIndex = 0;
×
NEW
1811
    if (isNumeric) {
×
NEW
1812
      if (useFloat) {
×
NEW
1813
        let maxVal: number = Number(args[0].value);
×
1814
        for (let i = 1; i < args.length; i++) {
×
NEW
1815
          const curr: number = Number(args[i].value);
×
NEW
1816
          if (curr < maxVal) {
×
NEW
1817
            maxVal = curr;
×
NEW
1818
            maxIndex = i;
×
1819
          }
1820
        }
1821
      } else {
NEW
1822
        let maxVal: bigint = args[0].value;
×
1823
        for (let i = 1; i < args.length; i++) {
×
NEW
1824
          const curr: bigint = args[i].value;
×
NEW
1825
          if (curr < maxVal) {
×
NEW
1826
            maxVal = curr;
×
NEW
1827
            maxIndex = i;
×
1828
          }
1829
        }
1830
      }
NEW
1831
    } else if (isString) {
×
NEW
1832
      let maxVal = args[0].value as string;
×
NEW
1833
      for (let i = 1; i < args.length; i++) {
×
NEW
1834
        const curr = args[i].value as string;
×
NEW
1835
        if (curr < maxVal) {
×
NEW
1836
          maxVal = curr;
×
NEW
1837
          maxIndex = i;
×
1838
        }
1839
      }
1840
    } else {
1841
      // Won't happen
NEW
1842
      throw new Error(`min: unsupported type ${firstType}`);
×
1843
    }
1844

NEW
1845
    return args[maxIndex];
×
1846
  }
1847

1848
  @Validate(null, 0, "random_random", true)
1849
  static random_random(
2✔
1850
    args: Value[],
1851
    source: string,
1852
    command: ControlItem,
1853
    context: Context,
1854
  ): Value {
NEW
1855
    const result = Math.random();
×
NEW
1856
    return { type: "number", value: result };
×
1857
  }
1858

1859
  @Validate(1, 2, "round", true)
1860
  static round(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1861
    const numArg = args[0];
×
NEW
1862
    if (numArg.type !== "number" && numArg.type !== "bigint") {
×
NEW
1863
      handleRuntimeError(
×
1864
        context,
1865
        new TypeError(source, command as ExprNS.Expr, context, numArg.type, "float' or 'int"),
1866
      );
1867
    }
1868

NEW
1869
    let ndigitsArg = { type: "bigint", value: BigInt(0) };
×
NEW
1870
    if (args.length === 2 && args[1].type !== "NoneType") {
×
NEW
1871
      ndigitsArg = args[1];
×
1872
    }
1873

NEW
1874
    if (numArg.type === "number") {
×
NEW
1875
      const numberValue: number = numArg.value;
×
NEW
1876
      if (ndigitsArg.value > 0) {
×
NEW
1877
        const shifted = Number(numberValue.toFixed(Number(ndigitsArg.value)));
×
NEW
1878
        return { type: "number", value: shifted };
×
NEW
1879
      } else if (ndigitsArg.value === BigInt(0)) {
×
NEW
1880
        const shifted = Math.round(numArg.value);
×
NEW
1881
        return { type: "bigint", value: BigInt(shifted) };
×
1882
      } else {
1883
        const shifted =
NEW
1884
          Math.round(numArg.value / 10 ** -Number(ndigitsArg.value)) *
×
1885
          10 ** -Number(ndigitsArg.value);
NEW
1886
        return { type: "number", value: shifted };
×
1887
      }
1888
    } else {
NEW
1889
      if (ndigitsArg.value >= 0) {
×
NEW
1890
        return numArg;
×
1891
      } else {
1892
        const shifted: bigint =
NEW
1893
          (numArg.value / BigInt(10) ** -ndigitsArg.value) * BigInt(10) ** -ndigitsArg.value;
×
NEW
1894
        return { type: "bigint", value: shifted };
×
1895
      }
1896
    }
1897
  }
1898

1899
  @Validate(null, 0, "time_time", true)
1900
  static time_time(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
1901
    const currentTime = Date.now();
×
NEW
1902
    return { type: "number", value: currentTime };
×
1903
  }
1904

1905
  static input(args: Value[], source: string, command: ControlItem, context: Context): Value {
1906
    // TODO: : call conductor to receive user input
1907
  }
1908

1909
  static print(args: Value[], source: string, command: ControlItem, context: Context) {
NEW
1910
    const output = args.map(arg => toPythonString(arg)).join(" ");
×
NEW
1911
    context.output += output + "\n";
×
NEW
1912
    return { type: "undefined" };
×
1913
  }
1914

1915
  static str(args: Value[], source: string, command: ControlItem, context: Context): Value {
NEW
1916
    if (args.length === 0) {
×
NEW
1917
      return { type: "string", value: "" };
×
1918
    }
NEW
1919
    const obj = args[0];
×
NEW
1920
    const result = toPythonString(obj);
×
NEW
1921
    return { type: "string", value: result };
×
1922
  }
1923
}
1924

1925
import { ExprNS } from "./ast-types";
1926
import py_s1_constants from "./stdlib/py_s1_constants.json";
2✔
1927

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

1932
/*
1933
    Create a map to hold built-in constants.
1934
    Each constant is stored with a string key and its corresponding value object.
1935
*/
1936
export const builtInConstants = new Map<string, any>();
2✔
1937

1938
const constantMap = {
2✔
1939
  math_e: { type: "number", value: Math.E },
1940
  math_inf: { type: "number", value: Infinity },
1941
  math_nan: { type: "number", value: NaN },
1942
  math_pi: { type: "number", value: Math.PI },
1943
  math_tau: { type: "number", value: 2 * Math.PI },
1944
} as const;
1945

1946
for (const name of constants.constants) {
2✔
1947
  const valueObj = constantMap[name as keyof typeof constantMap];
10✔
1948
  if (!valueObj) {
10!
NEW
1949
    throw new Error(`Constant '${name}' is not implemented`);
×
1950
  }
1951
  builtInConstants.set(name, valueObj);
10✔
1952
}
1953

1954
/*
1955
    Create a map to hold built-in functions.
1956
    The keys are strings (function names) and the values are functions that can take any arguments.
1957
*/
1958
export const builtIns = new Map<string, Value>();
2✔
1959
for (const name of constants.builtInFuncs) {
2✔
1960
  const impl = (BuiltInFunctions as any)[name];
126✔
1961
  if (typeof impl !== "function") {
126!
NEW
1962
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
1963
  }
1964
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
126✔
1965
  builtIns.set(name, { type: "builtin", name: builtinName, func: impl });
126✔
1966
}
1967

1968
/**
1969
 * Converts a number to a string that mimics Python's float formatting behavior.
1970
 *
1971
 * In Python, float values are printed in scientific notation when their absolute value
1972
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
1973
 * so we explicitly enforce these formatting thresholds.
1974
 *
1975
 * The logic here is based on Python's internal `format_float_short` implementation
1976
 * in CPython's `pystrtod.c`:
1977
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
1978
 *
1979
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
1980
 * output matches Python’s display conventions.
1981
 */
1982
export function toPythonFloat(num: number): string {
2✔
NEW
1983
  if (Object.is(num, -0)) {
×
NEW
1984
    return "-0.0";
×
1985
  }
NEW
1986
  if (num === 0) {
×
NEW
1987
    return "0.0";
×
1988
  }
1989

NEW
1990
  if (num === Infinity) {
×
NEW
1991
    return "inf";
×
1992
  }
NEW
1993
  if (num === -Infinity) {
×
NEW
1994
    return "-inf";
×
1995
  }
1996

NEW
1997
  if (Number.isNaN(num)) {
×
NEW
1998
    return "nan";
×
1999
  }
2000

NEW
2001
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
×
NEW
2002
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2003
  }
NEW
2004
  if (Number.isInteger(num)) {
×
NEW
2005
    return num.toFixed(1).toString();
×
2006
  }
NEW
2007
  return num.toString();
×
2008
}
2009

2010
export function toPythonString(obj: Value): string {
2✔
2011
  let ret: any;
NEW
2012
  if (!obj) {
×
NEW
2013
    return "None";
×
2014
  }
NEW
2015
  if (obj.type == "builtin") {
×
NEW
2016
    return `<built-in function ${(obj as any).name}>`;
×
2017
  }
NEW
2018
  if (obj.type === "bigint" || obj.type === "complex") {
×
NEW
2019
    ret = obj.value.toString();
×
NEW
2020
  } else if (obj.type === "number") {
×
NEW
2021
    ret = toPythonFloat(obj.value);
×
NEW
2022
  } else if (obj.type === "bool") {
×
NEW
2023
    if (obj.value === true) {
×
NEW
2024
      return "True";
×
2025
    } else {
NEW
2026
      return "False";
×
2027
    }
NEW
2028
  } else if (obj.type === "error") {
×
NEW
2029
    return obj.message;
×
NEW
2030
  } else if (obj instanceof Closure) {
×
NEW
2031
    if (obj.node) {
×
NEW
2032
      const funcName = (obj.node as any).name?.lexeme || "(anonymous)";
×
NEW
2033
      return `<function ${funcName}>`;
×
2034
    }
NEW
2035
  } else if (obj === undefined || obj.value === undefined) {
×
NEW
2036
    ret = "None";
×
2037
  } else {
NEW
2038
    ret = obj.value.toString();
×
2039
  }
NEW
2040
  return ret;
×
2041
}
2042

2043
export function str(args: Value[], source: string, command: ControlItem, context: Context): Value {
2✔
NEW
2044
  if (args.length === 0) {
×
NEW
2045
    return { type: "string", value: "" };
×
2046
  }
NEW
2047
  const obj = args[0];
×
NEW
2048
  const result = toPythonString(obj);
×
NEW
2049
  return { type: "string", value: result };
×
2050
}
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