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

source-academy / py-slang / 24658048223

20 Apr 2026 09:06AM UTC coverage: 71.503% (+1.8%) from 69.683%
24658048223

push

github

web-flow
General Fixes (#145)

* Use `.kind` instead of `.constructor.name` to allow function name terserification

* Fix the step limit

* Refactor type narrowing

* Remove unused code

* Fix some bugs (prelude functions not working + `==` not working for expanded equality + mismatched name in Python 4 stdlib)

* Add explicit types for every evaluator + fix test cases for parser

* Remove console.log + fix list functions not working at all

* Fix formatting

* Add stream test cases and fix function and variable hoisting

* Add pairmutator test cases + Add arrays as a possible value to check test cases

* Add some test cases for lists and add some test cases for the `int` function

* Format files

1470 of 2361 branches covered (62.26%)

Branch coverage included in aggregate %.

270 of 326 new or added lines in 27 files covered. (82.82%)

8 existing lines in 4 files now uncovered.

4429 of 5889 relevant lines covered (75.21%)

7636.98 hits per line

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

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

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

27
export function Validate<T extends Value | Promise<Value>>(
11✔
28
  minArgs: number | null,
29
  maxArgs: number | null,
30
  functionName: string,
31
  strict: boolean,
32
) {
33
  return function (
822✔
34
    _target: unknown,
35
    _propertyKey: string,
36
    descriptor: TypedPropertyDescriptor<
37
      (args: Value[], source: string, command: ExprNS.Call, context: Context) => T
38
    >,
39
  ): void {
40
    const originalMethod = descriptor.value!;
822✔
41
    minArgMap.set(functionName, minArgs || 0);
822✔
42
    descriptor.value = function (
822✔
43
      args: Value[],
44
      source: string,
45
      command: ExprNS.Call,
46
      context: Context,
47
    ): T {
48
      if (minArgs !== null && args.length < minArgs) {
5,708✔
49
        handleRuntimeError(
11✔
50
          context,
51
          new MissingRequiredPositionalError(source, command, functionName, minArgs, args, strict),
52
        );
53
      }
54

55
      if (maxArgs !== null && args.length > maxArgs) {
5,697✔
56
        handleRuntimeError(
6✔
57
          context,
58
          new TooManyPositionalArgumentsError(source, command, functionName, maxArgs, args, strict),
59
        );
60
      }
61

62
      return originalMethod.call(this, args, source, command, context);
5,691✔
63
    };
64
  };
65
}
66

67
export class BuiltInFunctions {
11✔
68
  @Validate(1, 1, "arity", true)
69
  static arity(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
11✔
70
    const func = args[0];
98✔
71
    if (func.type !== "builtin" && func.type !== "closure") {
98✔
72
      handleRuntimeError(context, new TypeError(source, command, context, func.type, "function"));
7✔
73
    }
74
    if (func.type === "closure") {
91✔
75
      const variadicInstance = func.closure.node.parameters.findIndex(param => param.isStarred);
11✔
76
      if (variadicInstance !== -1) {
11✔
77
        return { type: "bigint", value: BigInt(variadicInstance) };
4✔
78
      }
79
      return { type: "bigint", value: BigInt(func.closure.node.parameters.length) };
7✔
80
    }
81
    return { type: "bigint", value: BigInt(func.minArgs) };
80✔
82
  }
83

84
  @Validate(null, 2, "int", true)
85
  static int(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
11✔
86
    if (args.length === 0) {
27✔
87
      return { type: "bigint", value: BigInt(0) };
1✔
88
    }
89
    const arg = args[0];
26✔
90
    if (!isNumeric(arg) && arg.type !== "string" && arg.type !== "bool") {
26✔
91
      handleRuntimeError(
4✔
92
        context,
93
        new TypeError(source, command, context, arg.type, "str, int, float or bool"),
94
      );
95
    }
96

97
    if (args.length === 1) {
22✔
98
      if (arg.type === "number") {
10✔
99
        const truncated = Math.trunc(arg.value);
2✔
100
        return { type: "bigint", value: BigInt(truncated) };
2✔
101
      }
102
      if (arg.type === "bigint") {
8✔
103
        return { type: "bigint", value: arg.value };
1✔
104
      }
105
      if (arg.type === "string") {
7✔
106
        const str = arg.value.trim().replace(/_/g, "");
6✔
107
        if (!/^[+-]?\d+$/.test(str)) {
6✔
108
          handleRuntimeError(context, new ValueError(source, command, context, "int"));
3✔
109
        }
110
        return { type: "bigint", value: BigInt(str) };
3✔
111
      }
112
      return { type: "bigint", value: arg.value ? BigInt(1) : BigInt(0) };
1!
113
    }
114
    const baseArg = args[1];
12✔
115
    if (arg.type !== "string") {
12✔
116
      handleRuntimeError(context, new TypeError(source, command, context, arg.type, "string"));
1✔
117
    }
118
    if (baseArg.type !== "bigint") {
11!
NEW
119
      handleRuntimeError(context, new TypeError(source, command, context, baseArg.type, "int"));
×
120
    }
121

122
    let base = Number(baseArg.value);
11✔
123
    let str = arg.value.trim().replace(/_/g, "");
11✔
124

125
    const sign = str.startsWith("-") ? -1 : 1;
11✔
126
    if (str.startsWith("+") || str.startsWith("-")) {
11✔
127
      str = str.substring(1);
1✔
128
    }
129

130
    if (base === 0) {
11✔
131
      if (str.startsWith("0x") || str.startsWith("0X")) {
7✔
132
        base = 16;
1✔
133
        str = str.substring(2);
1✔
134
      } else if (str.startsWith("0o") || str.startsWith("0O")) {
6✔
135
        base = 8;
4✔
136
        str = str.substring(2);
4✔
137
      } else if (str.startsWith("0b") || str.startsWith("0B")) {
2✔
138
        base = 2;
1✔
139
        str = str.substring(2);
1✔
140
      } else {
141
        base = 10;
1✔
142
      }
143
    }
144

145
    if (base < 2 || base > 36) {
11✔
146
      handleRuntimeError(context, new ValueError(source, command, context, "int"));
2✔
147
    }
148

149
    const validChars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base);
9✔
150
    const regex = new RegExp(`^[${validChars}]+$`, "i");
9✔
151
    if (!regex.test(str)) {
9✔
152
      handleRuntimeError(context, new ValueError(source, command, context, "int"));
3✔
153
    }
154

155
    let res = BigInt(0);
6✔
156
    for (const char of str) {
6✔
157
      res = res * BigInt(base) + BigInt(validChars.indexOf(char.toLowerCase()));
16✔
158
    }
159
    return { type: "bigint", value: BigInt(sign) * res };
6✔
160
  }
161

162
  @Validate(null, 1, "float", true)
163
  static float(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
11✔
164
    if (args.length === 0) {
25✔
165
      return { type: "number", value: 0 };
1✔
166
    }
167
    const val = args[0];
24✔
168
    if (val.type === "bigint") {
24✔
169
      return { type: "number", value: Number(val.value) };
1✔
170
    } else if (val.type === "number") {
23✔
171
      return { type: "number", value: val.value };
2✔
172
    } else if (val.type === "bool") {
21✔
173
      return { type: "number", value: val.value ? 1 : 0 };
1!
174
    } else if (val.type === "string") {
20✔
175
      const str = val.value.trim().replace(/_/g, "").toLowerCase();
16✔
176
      const mappings = {
16✔
177
        inf: Infinity,
178
        "+inf": Infinity,
179
        "-inf": -Infinity,
180
        infinity: Infinity,
181
        "+infinity": Infinity,
182
        "-infinity": -Infinity,
183
        nan: NaN,
184
        "+nan": NaN,
185
        "-nan": NaN,
186
      };
187
      if (str in mappings) {
16✔
188
        return { type: "number", value: mappings[str as keyof typeof mappings] };
8✔
189
      }
190
      const num = Number(str);
8✔
191
      if (isNaN(num)) {
8✔
192
        handleRuntimeError(context, new ValueError(source, command, context, "float"));
1✔
193
      }
194
      return { type: "number", value: num };
7✔
195
    }
196
    handleRuntimeError(
4✔
197
      context,
198
      new TypeError(source, command, context, val.type, "'float', 'int', 'bool' or 'str'"),
199
    );
200
  }
201

202
  @Validate(null, 2, "complex", true)
203
  static complex(
11✔
204
    args: Value[],
205
    source: string,
206
    command: ExprNS.Call,
207
    context: Context,
208
  ): ComplexValue {
209
    if (args.length === 0) {
36✔
210
      return { type: "complex", value: new PyComplexNumber(0, 0) };
1✔
211
    }
212
    if (args.length == 1) {
35✔
213
      const val = args[0];
27✔
214
      if (
27✔
215
        val.type !== "bigint" &&
103✔
216
        val.type !== "number" &&
217
        val.type !== "bool" &&
218
        val.type !== "string" &&
219
        val.type !== "complex"
220
      ) {
221
        handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
3✔
222
      }
223
      return {
24✔
224
        type: "complex",
225
        value: PyComplexNumber.fromValue(context, source, command, val.value),
226
      };
227
    }
228
    const invalidType = args.filter(
8✔
229
      val =>
230
        val.type !== "bigint" &&
16✔
231
        val.type !== "number" &&
232
        val.type !== "bool" &&
233
        val.type !== "complex",
234
    );
235
    if (invalidType.length > 0) {
8✔
236
      handleRuntimeError(
3✔
237
        context,
238
        new TypeError(
239
          source,
240
          command,
241
          context,
242
          invalidType[0].type,
243
          "'int', 'float', 'bool' or 'complex'",
244
        ),
245
      );
246
    }
247
    const [real, imag] = args as (BigIntValue | NumberValue | BoolValue | ComplexValue)[];
5✔
248
    const realPart = PyComplexNumber.fromValue(context, source, command, real.value);
5✔
249
    const imagPart = PyComplexNumber.fromValue(context, source, command, imag.value);
5✔
250
    return { type: "complex", value: realPart.add(imagPart.mul(new PyComplexNumber(0, 1))) };
5✔
251
  }
252

253
  @Validate(1, 1, "real", true)
254
  static real(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
11✔
255
    const val = args[0];
×
256
    if (val.type !== "complex") {
×
NEW
257
      handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
×
258
    }
259
    return { type: "number", value: val.value.real };
×
260
  }
261

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

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

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

312
  @Validate(1, 1, "len", true)
313
  static len(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
11✔
314
    const val = args[0];
14✔
315
    if (val.type === "string" || val.type === "list") {
14✔
316
      // The spread operator is used to count the number of Unicode code points
317
      // in the string
318
      return { type: "bigint", value: BigInt([...val.value].length) };
7✔
319
    }
320
    handleRuntimeError(
7✔
321
      context,
322
      new TypeError(source, command, context, val.type, "object with length"),
323
    );
324
  }
325

326
  static error(args: Value[], _source: string, command: ExprNS.Call, context: Context): Value {
327
    const output = "Error: " + args.map(arg => toPythonString(arg)).join(" ") + "\n";
7✔
328
    handleRuntimeError(context, new UserError(output, command));
5✔
329
  }
330

331
  @Validate(1, 1, "math_acos", false)
332
  static math_acos(
11✔
333
    args: Value[],
334
    source: string,
335
    command: ExprNS.Call,
336
    context: Context,
337
  ): NumberValue {
338
    const x = args[0];
×
339
    if (!isNumeric(x)) {
×
340
      handleRuntimeError(
×
341
        context,
342
        new TypeError(source, command, context, x.type, "float' or 'int"),
343
      );
344
    }
345

346
    let num: number;
347
    if (x.type === "number") {
×
348
      num = x.value;
×
349
    } else {
350
      num = Number(x.value);
×
351
    }
352

353
    if (num < -1 || num > 1) {
×
NEW
354
      handleRuntimeError(context, new ValueError(source, command, context, "math_acos"));
×
355
    }
356

357
    const result = Math.acos(num);
×
358
    return { type: "number", value: result };
×
359
  }
360

361
  @Validate(1, 1, "math_acosh", false)
362
  static math_acosh(
11✔
363
    args: Value[],
364
    source: string,
365
    command: ExprNS.Call,
366
    context: Context,
367
  ): NumberValue {
368
    const x = args[0];
×
369

370
    if (!isNumeric(x)) {
×
371
      handleRuntimeError(
×
372
        context,
373
        new TypeError(source, command, context, x.type, "float' or 'int"),
374
      );
375
    }
376

377
    let num: number;
378
    if (x.type === "number") {
×
379
      num = x.value;
×
380
    } else {
381
      num = Number(x.value);
×
382
    }
383

384
    if (num < 1) {
×
NEW
385
      handleRuntimeError(context, new ValueError(source, command, context, "math_acosh"));
×
386
    }
387

388
    const result = Math.acosh(num);
×
389
    return { type: "number", value: result };
×
390
  }
391

392
  @Validate(1, 1, "math_asin", false)
393
  static math_asin(
11✔
394
    args: Value[],
395
    source: string,
396
    command: ExprNS.Call,
397
    context: Context,
398
  ): NumberValue {
399
    const x = args[0];
×
400
    if (!isNumeric(x)) {
×
401
      handleRuntimeError(
×
402
        context,
403
        new TypeError(source, command, context, x.type, "float' or 'int"),
404
      );
405
    }
406

407
    let num: number;
408
    if (x.type === "number") {
×
409
      num = x.value;
×
410
    } else {
411
      num = Number(x.value);
×
412
    }
413

414
    if (num < -1 || num > 1) {
×
NEW
415
      handleRuntimeError(context, new ValueError(source, command, context, "math_asin"));
×
416
    }
417

418
    const result = Math.asin(num);
×
419
    return { type: "number", value: result };
×
420
  }
421

422
  @Validate(1, 1, "math_asinh", false)
423
  static math_asinh(
11✔
424
    args: Value[],
425
    source: string,
426
    command: ExprNS.Call,
427
    context: Context,
428
  ): NumberValue {
429
    const x = args[0];
×
430
    if (!isNumeric(x)) {
×
431
      handleRuntimeError(
×
432
        context,
433
        new TypeError(source, command, context, x.type, "float' or 'int"),
434
      );
435
    }
436

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

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

448
  @Validate(1, 1, "math_atan", false)
449
  static math_atan(
11✔
450
    args: Value[],
451
    source: string,
452
    command: ExprNS.Call,
453
    context: Context,
454
  ): NumberValue {
455
    const x = args[0];
×
456
    if (!isNumeric(x)) {
×
457
      handleRuntimeError(
×
458
        context,
459
        new TypeError(source, command, context, x.type, "float' or 'int"),
460
      );
461
    }
462

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

470
    const result = Math.atan(num);
×
471
    return { type: "number", value: result };
×
472
  }
473

474
  @Validate(2, 2, "math_atan2", false)
475
  static math_atan2(
11✔
476
    args: Value[],
477
    source: string,
478
    command: ExprNS.Call,
479
    context: Context,
480
  ): NumberValue {
481
    const y = args[0];
×
482
    const x = args[1];
×
483
    if (!isNumeric(x)) {
×
484
      handleRuntimeError(
×
485
        context,
486
        new TypeError(source, command, context, x.type, "float' or 'int"),
487
      );
488
    } else if (!isNumeric(y)) {
×
489
      handleRuntimeError(
×
490
        context,
491
        new TypeError(source, command, context, y.type, "float' or 'int"),
492
      );
493
    }
494

495
    let yNum: number, xNum: number;
496
    if (y.type === "number") {
×
497
      yNum = y.value;
×
498
    } else {
499
      yNum = Number(y.value);
×
500
    }
501

502
    if (x.type === "number") {
×
503
      xNum = x.value;
×
504
    } else {
505
      xNum = Number(x.value);
×
506
    }
507

508
    const result = Math.atan2(yNum, xNum);
×
509
    return { type: "number", value: result };
×
510
  }
511

512
  @Validate(1, 1, "math_atanh", false)
513
  static math_atanh(
11✔
514
    args: Value[],
515
    source: string,
516
    command: ExprNS.Call,
517
    context: Context,
518
  ): NumberValue {
519
    const x = args[0];
×
520
    if (!isNumeric(x)) {
×
521
      handleRuntimeError(
×
522
        context,
523
        new TypeError(source, command, context, x.type, "float' or 'int"),
524
      );
525
    }
526

527
    let num: number;
528
    if (x.type === "number") {
×
529
      num = x.value;
×
530
    } else {
531
      num = Number(x.value);
×
532
    }
533

534
    if (num <= -1 || num >= 1) {
×
NEW
535
      handleRuntimeError(context, new ValueError(source, command, context, "math_atanh"));
×
536
    }
537

538
    const result = Math.atanh(num);
×
539
    return { type: "number", value: result };
×
540
  }
541

542
  @Validate(1, 1, "math_cos", false)
543
  static math_cos(
11✔
544
    args: Value[],
545
    source: string,
546
    command: ExprNS.Call,
547
    context: Context,
548
  ): NumberValue {
549
    const x = args[0];
7✔
550
    if (!isNumeric(x)) {
7✔
551
      handleRuntimeError(
3✔
552
        context,
553
        new TypeError(source, command, context, x.type, "float' or 'int"),
554
      );
555
    }
556

557
    let num: number;
558
    if (x.type === "number") {
4✔
559
      num = x.value;
3✔
560
    } else {
561
      num = Number(x.value);
1✔
562
    }
563

564
    const result = Math.cos(num);
4✔
565
    return { type: "number", value: result };
4✔
566
  }
567

568
  @Validate(1, 1, "math_cosh", false)
569
  static math_cosh(
11✔
570
    args: Value[],
571
    source: string,
572
    command: ExprNS.Call,
573
    context: Context,
574
  ): NumberValue {
575
    const x = args[0];
×
576
    if (!isNumeric(x)) {
×
577
      handleRuntimeError(
×
578
        context,
579
        new TypeError(source, command, context, x.type, "float' or 'int"),
580
      );
581
    }
582

583
    let num: number;
584
    if (x.type === "number") {
×
585
      num = x.value;
×
586
    } else {
587
      num = Number(x.value);
×
588
    }
589

590
    const result = Math.cosh(num);
×
591
    return { type: "number", value: result };
×
592
  }
593

594
  @Validate(1, 1, "math_degrees", false)
595
  static math_degrees(
11✔
596
    args: Value[],
597
    source: string,
598
    command: ExprNS.Call,
599
    context: Context,
600
  ): NumberValue {
601
    const x = args[0];
×
602
    if (!isNumeric(x)) {
×
603
      handleRuntimeError(
×
604
        context,
605
        new TypeError(source, command, context, x.type, "float' or 'int"),
606
      );
607
    }
608

609
    let num: number;
610
    if (x.type === "number") {
×
611
      num = x.value;
×
612
    } else {
613
      num = Number(x.value);
×
614
    }
615

616
    const result = (num * 180) / Math.PI;
×
617
    return { type: "number", value: result };
×
618
  }
619

620
  @Validate(1, 1, "math_erf", false)
621
  static math_erf(
11✔
622
    args: Value[],
623
    source: string,
624
    command: ExprNS.Call,
625
    context: Context,
626
  ): NumberValue {
627
    const x = args[0];
×
628
    if (!isNumeric(x)) {
×
629
      handleRuntimeError(
×
630
        context,
631
        new TypeError(source, command, context, x.type, "float' or 'int"),
632
      );
633
    }
634

635
    let num: number;
636
    if (x.type === "number") {
×
637
      num = x.value;
×
638
    } else {
639
      num = Number(x.value);
×
640
    }
641

642
    const erfnum = erf(num);
×
643

644
    return { type: "number", value: erfnum };
×
645
  }
646

647
  @Validate(1, 1, "math_erfc", false)
648
  static math_erfc(
11✔
649
    args: Value[],
650
    source: string,
651
    command: ExprNS.Call,
652
    context: Context,
653
  ): NumberValue {
654
    const x = args[0];
×
655
    if (!isNumeric(x)) {
×
656
      handleRuntimeError(
×
657
        context,
658
        new TypeError(source, command, context, x.type, "float' or 'int"),
659
      );
660
    }
661

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

664
    return { type: "number", value: erfc };
×
665
  }
666

667
  @Validate(2, 2, "math_comb", false)
668
  static math_comb(
11✔
669
    args: Value[],
670
    source: string,
671
    command: ExprNS.Call,
672
    context: Context,
673
  ): BigIntValue {
674
    const n = args[0];
×
675
    const k = args[1];
×
676

677
    if (n.type !== "bigint") {
×
NEW
678
      handleRuntimeError(context, new TypeError(source, command, context, n.type, "int"));
×
UNCOV
679
    } else if (k.type !== "bigint") {
×
NEW
680
      handleRuntimeError(context, new TypeError(source, command, context, k.type, "int"));
×
681
    }
682

683
    const nVal = BigInt(n.value);
×
684
    const kVal = BigInt(k.value);
×
685

686
    if (nVal < 0 || kVal < 0) {
×
NEW
687
      handleRuntimeError(context, new ValueError(source, command, context, "math_comb"));
×
688
    }
689

690
    if (kVal > nVal) {
×
691
      return { type: "bigint", value: BigInt(0) };
×
692
    }
693

694
    let result: bigint = BigInt(1);
×
695
    const kk = kVal > nVal - kVal ? nVal - kVal : kVal;
×
696

697
    for (let i: bigint = BigInt(0); i < kk; i++) {
×
698
      result = (result * (nVal - i)) / (i + BigInt(1));
×
699
    }
700

701
    return { type: "bigint", value: result };
×
702
  }
703

704
  @Validate(1, 1, "math_factorial", false)
705
  static math_factorial(
11✔
706
    args: Value[],
707
    source: string,
708
    command: ExprNS.Call,
709
    context: Context,
710
  ): BigIntValue {
711
    const n = args[0];
×
712

713
    if (n.type !== "bigint") {
×
NEW
714
      handleRuntimeError(context, new TypeError(source, command, context, n.type, "int"));
×
715
    }
716

717
    const nVal = BigInt(n.value);
×
718

719
    if (nVal < 0) {
×
NEW
720
      handleRuntimeError(context, new ValueError(source, command, context, "math_factorial"));
×
721
    }
722

723
    // 0! = 1
724
    if (nVal === BigInt(0)) {
×
725
      return { type: "bigint", value: BigInt(1) };
×
726
    }
727

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

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

736
  static math_gcd(
737
    args: Value[],
738
    source: string,
739
    command: ExprNS.Call,
740
    context: Context,
741
  ): BigIntValue {
742
    if (args.length === 0) {
×
743
      return { type: "bigint", value: BigInt(0) };
×
744
    }
745

746
    const values = args.map(v => {
×
747
      if (v.type !== "bigint") {
×
NEW
748
        handleRuntimeError(context, new TypeError(source, command, context, v.type, "int"));
×
749
      }
750
      return BigInt(v.value);
×
751
    });
752

753
    const allZero = values.every(val => val === BigInt(0));
×
754
    if (allZero) {
×
755
      return { type: "bigint", value: BigInt(0) };
×
756
    }
757

758
    let currentGcd: bigint = values[0] < 0 ? -values[0] : values[0];
×
759
    for (let i = 1; i < values.length; i++) {
×
760
      currentGcd = BuiltInFunctions.gcdOfTwo(currentGcd, values[i] < 0 ? -values[i] : values[i]);
×
761
      if (currentGcd === BigInt(1)) {
×
762
        break;
×
763
      }
764
    }
765

766
    return { type: "bigint", value: currentGcd };
×
767
  }
768

769
  static gcdOfTwo(a: bigint, b: bigint): bigint {
770
    let x: bigint = a;
×
771
    let y: bigint = b;
×
772
    while (y !== BigInt(0)) {
×
773
      const temp = x % y;
×
774
      x = y;
×
775
      y = temp;
×
776
    }
777
    return x < 0 ? -x : x;
×
778
  }
779

780
  @Validate(1, 1, "math_isqrt", false)
781
  static math_isqrt(
11✔
782
    args: Value[],
783
    source: string,
784
    command: ExprNS.Call,
785
    context: Context,
786
  ): BigIntValue {
787
    const nValObj = args[0];
×
788
    if (nValObj.type !== "bigint") {
×
NEW
789
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type, "int"));
×
790
    }
791

792
    const n: bigint = nValObj.value;
×
793

794
    if (n < 0) {
×
NEW
795
      handleRuntimeError(context, new ValueError(source, command, context, "math_isqrt"));
×
796
    }
797

798
    if (n < 2) {
×
799
      return { type: "bigint", value: n };
×
800
    }
801

802
    let low: bigint = BigInt(1);
×
803
    let high: bigint = n;
×
804

805
    while (low < high) {
×
806
      const mid = (low + high + BigInt(1)) >> BigInt(1);
×
807
      const sq = mid * mid;
×
808
      if (sq <= n) {
×
809
        low = mid;
×
810
      } else {
811
        high = mid - BigInt(1);
×
812
      }
813
    }
814

815
    return { type: "bigint", value: low };
×
816
  }
817

818
  static math_lcm(
819
    args: Value[],
820
    source: string,
821
    command: ExprNS.Call,
822
    context: Context,
823
  ): BigIntValue {
824
    if (args.length === 0) {
×
825
      return { type: "bigint", value: BigInt(1) };
×
826
    }
827

828
    const values = args.map(val => {
×
829
      if (val.type !== "bigint") {
×
NEW
830
        handleRuntimeError(context, new TypeError(source, command, context, val.type, "int"));
×
831
      }
832
      return BigInt(val.value);
×
833
    });
834

835
    if (values.some(v => v === BigInt(0))) {
×
836
      return { type: "bigint", value: BigInt(0) };
×
837
    }
838

839
    let currentLcm: bigint = BuiltInFunctions.absBigInt(values[0]);
×
840
    for (let i = 1; i < values.length; i++) {
×
841
      currentLcm = BuiltInFunctions.lcmOfTwo(currentLcm, BuiltInFunctions.absBigInt(values[i]));
×
842
      if (currentLcm === BigInt(0)) {
×
843
        break;
×
844
      }
845
    }
846

847
    return { type: "bigint", value: currentLcm };
×
848
  }
849

850
  static lcmOfTwo(a: bigint, b: bigint): bigint {
851
    const gcdVal: bigint = BuiltInFunctions.gcdOfTwo(a, b);
×
852
    return BigInt((a / gcdVal) * b);
×
853
  }
854

855
  static absBigInt(x: bigint): bigint {
856
    return x < 0 ? -x : x;
×
857
  }
858

859
  @Validate(1, 2, "math_perm", true)
860
  static math_perm(
11✔
861
    args: Value[],
862
    source: string,
863
    command: ExprNS.Call,
864
    context: Context,
865
  ): BigIntValue {
866
    const nValObj = args[0];
×
867
    if (nValObj.type !== "bigint") {
×
NEW
868
      handleRuntimeError(context, new TypeError(source, command, context, nValObj.type, "int"));
×
869
    }
870
    const n = BigInt(nValObj.value);
×
871

872
    let k = n;
×
873
    if (args.length === 2) {
×
874
      const kValObj = args[1];
×
875
      if (kValObj.type === "none") {
×
876
        k = n;
×
877
      } else if (kValObj.type === "bigint") {
×
878
        k = BigInt(kValObj.value);
×
879
      } else {
880
        handleRuntimeError(
×
881
          context,
882
          new TypeError(source, command, context, kValObj.type, "int' or 'None"),
883
        );
884
      }
885
    }
886

887
    if (n < 0 || k < 0) {
×
NEW
888
      handleRuntimeError(context, new ValueError(source, command, context, "math_perm"));
×
889
    }
890

891
    if (k > n) {
×
892
      return { type: "bigint", value: BigInt(0) };
×
893
    }
894

895
    let result: bigint = BigInt(1);
×
896
    for (let i: bigint = BigInt(0); i < k; i++) {
×
897
      result *= n - i;
×
898
    }
899

900
    return { type: "bigint", value: result };
×
901
  }
902

903
  @Validate(1, 1, "math_ceil", false)
904
  static math_ceil(
11✔
905
    args: Value[],
906
    source: string,
907
    command: ExprNS.Call,
908
    context: Context,
909
  ): BigIntValue {
910
    const x = args[0];
×
911

912
    if (x.type === "bigint") {
×
913
      return x;
×
914
    }
915

916
    if (x.type === "number") {
×
917
      const numVal = x.value;
×
918
      const ceiled: bigint = BigInt(Math.ceil(numVal));
×
919
      return { type: "bigint", value: ceiled };
×
920
    }
921

NEW
922
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
923
  }
924

925
  @Validate(1, 1, "math_fabs", false)
926
  static math_fabs(
11✔
927
    args: Value[],
928
    source: string,
929
    command: ExprNS.Call,
930
    context: Context,
931
  ): NumberValue {
932
    const x = args[0];
×
933

934
    if (x.type === "bigint") {
×
935
      const bigVal: bigint = BigInt(x.value);
×
936
      const absVal: number = bigVal < 0 ? -Number(bigVal) : Number(bigVal);
×
937
      return { type: "number", value: absVal };
×
938
    }
939

940
    if (x.type === "number") {
×
941
      const numVal: number = x.value;
×
942
      if (typeof numVal !== "number") {
×
943
        handleRuntimeError(
×
944
          context,
945
          new TypeError(source, command, context, x.type, "float' or 'int"),
946
        );
947
      }
948
      const absVal: number = Math.abs(numVal);
×
949
      return { type: "number", value: absVal };
×
950
    }
951

NEW
952
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
953
  }
954

955
  @Validate(1, 1, "math_floor", false)
956
  static math_floor(
11✔
957
    args: Value[],
958
    source: string,
959
    command: ExprNS.Call,
960
    context: Context,
961
  ): BigIntValue {
962
    const x = args[0];
×
963

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

968
    if (x.type === "number") {
×
969
      const numVal: number = x.value;
×
970
      if (typeof numVal !== "number") {
×
971
        handleRuntimeError(
×
972
          context,
973
          new TypeError(source, command, context, x.type, "float' or 'int"),
974
        );
975
      }
976
      const floored: bigint = BigInt(Math.floor(numVal));
×
977
      return { type: "bigint", value: floored };
×
978
    }
979

NEW
980
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
981
  }
982

983
  // Computes the product of a and b along with the rounding error using Dekker's algorithm.
984
  static twoProd(a: number, b: number): { prod: number; err: number } {
985
    const prod = a * b;
×
986
    const c = 134217729; // 2^27 + 1
×
987
    const a_hi = a * c - (a * c - a);
×
988
    const a_lo = a - a_hi;
×
989
    const b_hi = b * c - (b * c - b);
×
990
    const b_lo = b - b_hi;
×
991
    const err = a_lo * b_lo - (prod - a_hi * b_hi - a_lo * b_hi - a_hi * b_lo);
×
992
    return { prod, err };
×
993
  }
994

995
  // Computes the sum of a and b along with the rounding error using Fast TwoSum.
996
  static twoSum(a: number, b: number): { sum: number; err: number } {
997
    const sum = a + b;
×
998
    const v = sum - a;
×
999
    const err = a - (sum - v) + (b - v);
×
1000
    return { sum, err };
×
1001
  }
1002

1003
  // Performs a fused multiply-add operation: computes (x * y) + z with a single rounding.
1004
  static fusedMultiplyAdd(x: number, y: number, z: number): number {
1005
    const { prod, err: prodErr } = BuiltInFunctions.twoProd(x, y);
×
1006
    const { sum, err: sumErr } = BuiltInFunctions.twoSum(prod, z);
×
1007
    const result = sum + (prodErr + sumErr);
×
1008
    return result;
×
1009
  }
1010

1011
  static toNumber(val: Value, source: string, command: ExprNS.Call, context: Context): number {
1012
    if (val.type === "bigint") {
×
1013
      return Number(val.value);
×
1014
    } else if (val.type === "number") {
×
1015
      return val.value;
×
1016
    } else {
1017
      handleRuntimeError(
×
1018
        context,
1019
        new TypeError(source, command, context, val.type, "float' or 'int"),
1020
      );
1021
    }
1022
  }
1023

1024
  @Validate(3, 3, "math_fma", false)
1025
  static math_fma(
11✔
1026
    args: Value[],
1027
    source: string,
1028
    command: ExprNS.Call,
1029
    context: Context,
1030
  ): NumberValue {
1031
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1032
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1033
    const zVal = BuiltInFunctions.toNumber(args[2], source, command, context);
×
1034

1035
    // Special-case handling: According to the IEEE 754 standard, fma(0, inf, nan)
1036
    // and fma(inf, 0, nan) should return NaN.
1037
    if (isNaN(xVal) || isNaN(yVal) || isNaN(zVal)) {
×
1038
      return { type: "number", value: NaN };
×
1039
    }
1040
    if (xVal === 0 && !isFinite(yVal) && isNaN(zVal)) {
×
1041
      return { type: "number", value: NaN };
×
1042
    }
1043
    if (yVal === 0 && !isFinite(xVal) && isNaN(zVal)) {
×
1044
      return { type: "number", value: NaN };
×
1045
    }
1046

1047
    const result = BuiltInFunctions.fusedMultiplyAdd(xVal, yVal, zVal);
×
1048
    return { type: "number", value: result };
×
1049
  }
1050

1051
  @Validate(2, 2, "math_fmod", false)
1052
  static math_fmod(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1053
    // Convert inputs to numbers
1054
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1055
    const yVal = BuiltInFunctions.toNumber(args[1], source, command, context);
×
1056

1057
    // Divisor cannot be zero
1058
    if (yVal === 0) {
×
NEW
1059
      handleRuntimeError(context, new ValueError(source, command, context, "math_fmod"));
×
1060
    }
1061

1062
    // JavaScript's % operator behaves similarly to C's fmod
1063
    // in that the sign of the result is the same as the sign of x.
1064
    // For corner cases (NaN, Infinity), JavaScript remainder
1065
    // yields results consistent with typical C library fmod behavior.
1066
    const remainder = xVal % yVal;
×
1067

1068
    return { type: "number", value: remainder };
×
1069
  }
1070

1071
  static roundToEven(num: number): number {
1072
    //uses Banker's Rounding as per Python's Round() function
1073
    const floorVal = Math.floor(num);
×
1074
    const ceilVal = Math.ceil(num);
×
1075
    const diffFloor = num - floorVal;
×
1076
    const diffCeil = ceilVal - num;
×
1077
    if (diffFloor < diffCeil) {
×
1078
      return floorVal;
×
1079
    } else if (diffCeil < diffFloor) {
×
1080
      return ceilVal;
×
1081
    } else {
1082
      return floorVal % 2 === 0 ? floorVal : ceilVal;
×
1083
    }
1084
  }
1085

1086
  @Validate(2, 2, "math_remainder", false)
1087
  static math_remainder(
11✔
1088
    args: Value[],
1089
    source: string,
1090
    command: ExprNS.Call,
1091
    context: Context,
1092
  ): NumberValue {
1093
    const x = args[0];
×
1094
    const y = args[1];
×
1095

1096
    let xValue: number;
1097
    if (x.type === "bigint") {
×
1098
      xValue = Number(x.value);
×
1099
    } else if (x.type === "number") {
×
1100
      xValue = x.value;
×
1101
    } else {
1102
      handleRuntimeError(
×
1103
        context,
1104
        new TypeError(source, command, context, x.type, "float' or 'int"),
1105
      );
1106
    }
1107

1108
    let yValue: number;
1109
    if (y.type === "bigint") {
×
1110
      yValue = Number(y.value);
×
1111
    } else if (y.type === "number") {
×
1112
      yValue = y.value;
×
1113
    } else {
1114
      handleRuntimeError(
×
1115
        context,
1116
        new TypeError(source, command, context, y.type, "float' or 'int"),
1117
      );
1118
    }
1119

1120
    if (yValue === 0) {
×
NEW
1121
      handleRuntimeError(context, new ValueError(source, command, context, "math_remainder"));
×
1122
    }
1123

1124
    const quotient = xValue / yValue;
×
1125
    const n = BuiltInFunctions.roundToEven(quotient);
×
1126
    const remainder = xValue - n * yValue;
×
1127

1128
    return { type: "number", value: remainder };
×
1129
  }
1130

1131
  @Validate(1, 1, "math_trunc", false)
1132
  static math_trunc(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1133
    const x = args[0];
×
1134

1135
    if (x.type === "bigint") {
×
1136
      return x;
×
1137
    }
1138

1139
    if (x.type === "number") {
×
1140
      const numVal: number = x.value;
×
1141
      if (typeof numVal !== "number") {
×
1142
        handleRuntimeError(
×
1143
          context,
1144
          new TypeError(source, command, context, x.type, "float' or 'int"),
1145
        );
1146
      }
1147
      let truncated: number;
1148
      if (numVal === 0) {
×
1149
        truncated = 0;
×
1150
      } else if (numVal < 0) {
×
1151
        truncated = Math.ceil(numVal);
×
1152
      } else {
1153
        truncated = Math.floor(numVal);
×
1154
      }
1155
      return { type: "bigint", value: BigInt(truncated) };
×
1156
    }
1157

NEW
1158
    handleRuntimeError(context, new TypeError(source, command, context, x.type, "float' or 'int"));
×
1159
  }
1160

1161
  @Validate(2, 2, "math_copysign", false)
1162
  static math_copysign(
11✔
1163
    args: Value[],
1164
    source: string,
1165
    command: ExprNS.Call,
1166
    context: Context,
1167
  ): NumberValue {
1168
    const [x, y] = args;
×
1169

1170
    if (!isNumeric(x)) {
×
1171
      handleRuntimeError(
×
1172
        context,
1173
        new TypeError(source, command, context, x.type, "float' or 'int"),
1174
      );
1175
    } else if (!isNumeric(y)) {
×
1176
      handleRuntimeError(
×
1177
        context,
1178
        new TypeError(source, command, context, y.type, "float' or 'int"),
1179
      );
1180
    }
1181

1182
    const xVal = Number(x.value);
×
1183
    const yVal = Number(y.value);
×
1184

1185
    const absVal = Math.abs(xVal);
×
1186
    const isNegative = yVal < 0 || Object.is(yVal, -0);
×
1187
    const result = isNegative ? -absVal : absVal;
×
1188

1189
    return { type: "number", value: Number(result) };
×
1190
  }
1191

1192
  @Validate(1, 1, "math_isfinite", false)
1193
  static math_isfinite(
11✔
1194
    args: Value[],
1195
    source: string,
1196
    command: ExprNS.Call,
1197
    context: Context,
1198
  ): BoolValue {
1199
    const xValObj = args[0];
×
1200
    if (!isNumeric(xValObj)) {
×
1201
      handleRuntimeError(
×
1202
        context,
1203
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
1204
      );
1205
    }
1206

1207
    const x = Number(xValObj.value);
×
1208
    const result: boolean = Number.isFinite(x);
×
1209

1210
    return { type: "bool", value: result };
×
1211
  }
1212

1213
  @Validate(1, 1, "math_isinf", false)
1214
  static math_isinf(
11✔
1215
    args: Value[],
1216
    source: string,
1217
    command: ExprNS.Call,
1218
    context: Context,
1219
  ): BoolValue {
1220
    const xValObj = args[0];
×
1221
    if (!isNumeric(xValObj)) {
×
1222
      handleRuntimeError(
×
1223
        context,
1224
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
1225
      );
1226
    }
1227

1228
    const x = Number(xValObj.value);
×
1229
    const result: boolean = x === Infinity || x === -Infinity;
×
1230

1231
    return { type: "bool", value: result };
×
1232
  }
1233

1234
  @Validate(1, 1, "math_isnan", false)
1235
  static math_isnan(
11✔
1236
    args: Value[],
1237
    source: string,
1238
    command: ExprNS.Call,
1239
    context: Context,
1240
  ): BoolValue {
1241
    const xValObj = args[0];
×
1242
    if (!isNumeric(xValObj)) {
×
1243
      handleRuntimeError(
×
1244
        context,
1245
        new TypeError(source, command, context, xValObj.type, "float' or 'int"),
1246
      );
1247
    }
1248

1249
    const x = Number(xValObj.value);
×
1250
    const result: boolean = Number.isNaN(x);
×
1251

1252
    return { type: "bool", value: result };
×
1253
  }
1254

1255
  @Validate(2, 2, "math_ldexp", false)
1256
  static math_ldexp(
11✔
1257
    args: Value[],
1258
    source: string,
1259
    command: ExprNS.Call,
1260
    context: Context,
1261
  ): NumberValue {
1262
    const xVal = BuiltInFunctions.toNumber(args[0], source, command, context);
×
1263

1264
    if (args[1].type !== "bigint") {
×
NEW
1265
      handleRuntimeError(context, new TypeError(source, command, context, args[1].type, "int"));
×
1266
    }
1267
    const expVal = args[1].value;
×
1268

1269
    // Perform x * 2^expVal
1270
    // In JavaScript, 2**expVal may overflow or underflow, yielding Infinity or 0 respectively.
1271
    // That behavior parallels typical C library rules for ldexp.
1272
    const result = xVal * Math.pow(2, Number(expVal));
×
1273

1274
    return { type: "number", value: result };
×
1275
  }
1276

1277
  @Validate(2, 2, "math_nextafter", false)
1278
  static math_nextafter(
11✔
1279
    _args: Value[],
1280
    _source: string,
1281
    _command: ExprNS.Call,
1282
    _context: Context,
1283
  ): Value {
1284
    // TODO: Implement math_nextafter using proper bit-level manipulation and handling special cases (NaN, Infinity, steps, etc.)
1285
    throw new Error("math_nextafter not implemented");
×
1286
  }
1287

1288
  @Validate(1, 1, "math_ulp", false)
1289
  static math_ulp(
11✔
1290
    _args: Value[],
1291
    _source: string,
1292
    _command: ExprNS.Call,
1293
    _context: Context,
1294
  ): Value {
1295
    // TODO: Implement math_ulp to return the unit in the last place (ULP) of the given floating-point number.
1296
    throw new Error("math_ulp not implemented");
×
1297
  }
1298

1299
  @Validate(1, 1, "math_cbrt", false)
1300
  static math_cbrt(
11✔
1301
    args: Value[],
1302
    source: string,
1303
    command: ExprNS.Call,
1304
    context: Context,
1305
  ): NumberValue {
1306
    const xVal = args[0];
×
1307
    let x: number;
1308

1309
    if (xVal.type !== "number") {
×
1310
      if (xVal.type === "bigint") {
×
1311
        x = Number(xVal.value);
×
1312
      } else {
1313
        handleRuntimeError(
×
1314
          context,
1315
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1316
        );
1317
      }
1318
    } else {
1319
      x = xVal.value;
×
1320
    }
1321

1322
    const result = Math.cbrt(x);
×
1323

1324
    return { type: "number", value: result };
×
1325
  }
1326

1327
  @Validate(1, 1, "math_exp", false)
1328
  static math_exp(
11✔
1329
    args: Value[],
1330
    source: string,
1331
    command: ExprNS.Call,
1332
    context: Context,
1333
  ): NumberValue {
1334
    const xVal = args[0];
×
1335
    let x: number;
1336

1337
    if (xVal.type !== "number") {
×
1338
      if (xVal.type === "bigint") {
×
1339
        x = Number(xVal.value);
×
1340
      } else {
1341
        handleRuntimeError(
×
1342
          context,
1343
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1344
        );
1345
      }
1346
    } else {
1347
      x = xVal.value;
×
1348
    }
1349

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

1354
  @Validate(1, 1, "math_exp2", false)
1355
  static math_exp2(
11✔
1356
    args: Value[],
1357
    source: string,
1358
    command: ExprNS.Call,
1359
    context: Context,
1360
  ): NumberValue {
1361
    const xVal = args[0];
×
1362
    let x: number;
1363

1364
    if (xVal.type !== "number") {
×
1365
      if (xVal.type === "bigint") {
×
1366
        x = Number(xVal.value);
×
1367
      } else {
1368
        handleRuntimeError(
×
1369
          context,
1370
          new TypeError(source, command, context, xVal.type, "float' or 'int"),
1371
        );
1372
      }
1373
    } else {
1374
      x = xVal.value;
×
1375
    }
1376

1377
    const result = Math.pow(2, x);
×
1378
    return { type: "number", value: result };
×
1379
  }
1380

1381
  @Validate(1, 1, "math_expm1", false)
1382
  static math_expm1(
11✔
1383
    args: Value[],
1384
    source: string,
1385
    command: ExprNS.Call,
1386
    context: Context,
1387
  ): NumberValue {
1388
    const x = args[0];
×
1389
    if (!isNumeric(x)) {
×
1390
      handleRuntimeError(
×
1391
        context,
1392
        new TypeError(source, command, context, x.type, "float' or 'int"),
1393
      );
1394
    }
1395

1396
    let num: number;
1397
    if (x.type === "number") {
×
1398
      num = x.value;
×
1399
    } else {
1400
      num = Number(x.value);
×
1401
    }
1402

1403
    const result = Math.expm1(num);
×
1404
    return { type: "number", value: result };
×
1405
  }
1406

1407
  @Validate(1, 1, "math_gamma", false)
1408
  static math_gamma(
11✔
1409
    args: Value[],
1410
    source: string,
1411
    command: ExprNS.Call,
1412
    context: Context,
1413
  ): NumberValue {
1414
    const x = args[0];
×
1415
    if (!isNumeric(x)) {
×
1416
      handleRuntimeError(
×
1417
        context,
1418
        new TypeError(source, command, context, x.type, "float' or 'int"),
1419
      );
1420
    }
1421

1422
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1423
    const result = gamma(z);
×
1424

1425
    return { type: "number", value: result };
×
1426
  }
1427

1428
  @Validate(1, 1, "math_lgamma", false)
1429
  static math_lgamma(
11✔
1430
    args: Value[],
1431
    source: string,
1432
    command: ExprNS.Call,
1433
    context: Context,
1434
  ): NumberValue {
1435
    const x = args[0];
×
1436
    if (!isNumeric(x)) {
×
1437
      handleRuntimeError(
×
1438
        context,
1439
        new TypeError(source, command, context, x.type, "float' or 'int"),
1440
      );
1441
    }
1442

1443
    const z = BuiltInFunctions.toNumber(x, source, command, context);
×
1444
    const result = lgamma(z);
×
1445

1446
    return { type: "number", value: result };
×
1447
  }
1448

1449
  @Validate(1, 2, "math_log", true)
1450
  static math_log(
11✔
1451
    args: Value[],
1452
    source: string,
1453
    command: ExprNS.Call,
1454
    context: Context,
1455
  ): NumberValue {
1456
    const x = args[0];
×
1457
    if (!isNumeric(x)) {
×
1458
      handleRuntimeError(
×
1459
        context,
1460
        new TypeError(source, command, context, x.type, "float' or 'int"),
1461
      );
1462
    }
1463
    let num: number;
1464
    if (x.type === "number") {
×
1465
      num = x.value;
×
1466
    } else {
1467
      num = Number(x.value);
×
1468
    }
1469

1470
    if (num <= 0) {
×
NEW
1471
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1472
    }
1473

1474
    if (args.length === 1) {
×
1475
      return { type: "number", value: Math.log(num) };
×
1476
    }
1477

1478
    const baseArg = args[1];
×
1479
    if (!isNumeric(baseArg)) {
×
1480
      handleRuntimeError(
×
1481
        context,
1482
        new TypeError(source, command, context, baseArg.type, "float' or 'int"),
1483
      );
1484
    }
1485
    let baseNum: number;
1486
    if (baseArg.type === "number") {
×
1487
      baseNum = baseArg.value;
×
1488
    } else {
1489
      baseNum = Number(baseArg.value);
×
1490
    }
1491
    if (baseNum <= 0) {
×
NEW
1492
      handleRuntimeError(context, new ValueError(source, command, context, "math_log"));
×
1493
    }
1494

1495
    const result = Math.log(num) / Math.log(baseNum);
×
1496
    return { type: "number", value: result };
×
1497
  }
1498

1499
  @Validate(1, 1, "math_log10", false)
1500
  static math_log10(
11✔
1501
    args: Value[],
1502
    source: string,
1503
    command: ExprNS.Call,
1504
    context: Context,
1505
  ): NumberValue {
1506
    const x = args[0];
×
1507
    if (!isNumeric(x)) {
×
1508
      handleRuntimeError(
×
1509
        context,
1510
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1511
      );
1512
    }
1513
    let num: number;
1514
    if (x.type === "number") {
×
1515
      num = x.value;
×
1516
    } else {
1517
      num = Number(x.value);
×
1518
    }
1519
    if (num <= 0) {
×
NEW
1520
      handleRuntimeError(context, new ValueError(source, command, context, "math_log10"));
×
1521
    }
1522

1523
    const result = Math.log10(num);
×
1524
    return { type: "number", value: result };
×
1525
  }
1526

1527
  @Validate(1, 1, "math_log1p", false)
1528
  static math_log1p(
11✔
1529
    args: Value[],
1530
    source: string,
1531
    command: ExprNS.Call,
1532
    context: Context,
1533
  ): NumberValue {
1534
    const x = args[0];
×
1535
    if (!isNumeric(x)) {
×
1536
      handleRuntimeError(
×
1537
        context,
1538
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1539
      );
1540
    }
1541
    let num: number;
1542
    if (x.type === "number") {
×
1543
      num = x.value;
×
1544
    } else {
1545
      num = Number(x.value);
×
1546
    }
1547
    if (1 + num <= 0) {
×
NEW
1548
      handleRuntimeError(context, new ValueError(source, command, context, "math_log1p"));
×
1549
    }
1550

1551
    const result = Math.log1p(num);
×
1552
    return { type: "number", value: result };
×
1553
  }
1554

1555
  @Validate(1, 1, "math_log2", false)
1556
  static math_log2(
11✔
1557
    args: Value[],
1558
    source: string,
1559
    command: ExprNS.Call,
1560
    context: Context,
1561
  ): NumberValue {
1562
    const x = args[0];
×
1563
    if (!isNumeric(x)) {
×
1564
      handleRuntimeError(
×
1565
        context,
1566
        new TypeError(source, command, context, args[0].type, "float' or 'int"),
1567
      );
1568
    }
1569
    let num: number;
1570
    if (x.type === "number") {
×
1571
      num = x.value;
×
1572
    } else {
1573
      num = Number(x.value);
×
1574
    }
1575
    if (num <= 0) {
×
NEW
1576
      handleRuntimeError(context, new ValueError(source, command, context, "math_log2"));
×
1577
    }
1578

1579
    const result = Math.log2(num);
×
1580
    return { type: "number", value: result };
×
1581
  }
1582

1583
  @Validate(2, 2, "math_pow", false)
1584
  static math_pow(
11✔
1585
    args: Value[],
1586
    source: string,
1587
    command: ExprNS.Call,
1588
    context: Context,
1589
  ): NumberValue {
1590
    const base = args[0];
×
1591
    const exp = args[1];
×
1592

1593
    if (!isNumeric(base)) {
×
1594
      handleRuntimeError(
×
1595
        context,
1596
        new TypeError(source, command, context, base.type, "float' or 'int"),
1597
      );
1598
    } else if (!isNumeric(exp)) {
×
1599
      handleRuntimeError(
×
1600
        context,
1601
        new TypeError(source, command, context, exp.type, "float' or 'int"),
1602
      );
1603
    }
1604

1605
    let baseNum: number;
1606
    if (base.type === "number") {
×
1607
      baseNum = base.value;
×
1608
    } else {
1609
      baseNum = Number(base.value);
×
1610
    }
1611

1612
    let expNum: number;
1613
    if (exp.type === "number") {
×
1614
      expNum = exp.value;
×
1615
    } else {
1616
      expNum = Number(exp.value);
×
1617
    }
1618

1619
    const result = Math.pow(baseNum, expNum);
×
1620
    return { type: "number", value: result };
×
1621
  }
1622

1623
  @Validate(1, 1, "math_radians", false)
1624
  static math_radians(
11✔
1625
    args: Value[],
1626
    source: string,
1627
    command: ExprNS.Call,
1628
    context: Context,
1629
  ): NumberValue {
1630
    const x = args[0];
×
1631
    if (!isNumeric(x)) {
×
1632
      handleRuntimeError(
×
1633
        context,
1634
        new TypeError(source, command, context, x.type, "float' or 'int"),
1635
      );
1636
    }
1637

1638
    let deg: number;
1639
    if (x.type === "number") {
×
1640
      deg = x.value;
×
1641
    } else {
1642
      deg = Number(x.value);
×
1643
    }
1644

1645
    const radians = (deg * Math.PI) / 180;
×
1646
    return { type: "number", value: radians };
×
1647
  }
1648

1649
  @Validate(1, 1, "math_sin", false)
1650
  static math_sin(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1651
    const x = args[0];
7✔
1652
    if (!isNumeric(x)) {
7✔
1653
      handleRuntimeError(
3✔
1654
        context,
1655
        new TypeError(source, command, context, x.type, "float' or 'int"),
1656
      );
1657
    }
1658

1659
    let num: number;
1660
    if (x.type === "number") {
4✔
1661
      num = x.value;
3✔
1662
    } else {
1663
      num = Number(x.value);
1✔
1664
    }
1665

1666
    const result = Math.sin(num);
4✔
1667
    return { type: "number", value: result };
4✔
1668
  }
1669

1670
  @Validate(1, 1, "math_sinh", false)
1671
  static math_sinh(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1672
    const x = args[0];
×
1673
    if (!isNumeric(x)) {
×
1674
      handleRuntimeError(
×
1675
        context,
1676
        new TypeError(source, command, context, x.type, "float' or 'int"),
1677
      );
1678
    }
1679

1680
    let num: number;
1681
    if (x.type === "number") {
×
1682
      num = x.value;
×
1683
    } else {
1684
      num = Number(x.value);
×
1685
    }
1686

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

1691
  @Validate(1, 1, "math_tan", false)
1692
  static math_tan(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1693
    const x = args[0];
×
1694
    if (!isNumeric(x)) {
×
1695
      handleRuntimeError(
×
1696
        context,
1697
        new TypeError(source, command, context, x.type, "float' or 'int"),
1698
      );
1699
    }
1700

1701
    let num: number;
1702
    if (x.type === "number") {
×
1703
      num = x.value;
×
1704
    } else {
1705
      num = Number(x.value);
×
1706
    }
1707

1708
    const result = Math.tan(num);
×
1709
    return { type: "number", value: result };
×
1710
  }
1711

1712
  @Validate(1, 1, "math_tanh", false)
1713
  static math_tanh(
11✔
1714
    args: Value[],
1715
    source: string,
1716
    command: ExprNS.Call,
1717
    context: Context,
1718
  ): NumberValue {
1719
    const x = args[0];
×
1720
    if (!isNumeric(x)) {
×
1721
      handleRuntimeError(
×
1722
        context,
1723
        new TypeError(source, command, context, x.type, "float' or 'int"),
1724
      );
1725
    }
1726

1727
    let num: number;
1728
    if (x.type === "number") {
×
1729
      num = x.value;
×
1730
    } else {
1731
      num = Number(x.value);
×
1732
    }
1733

1734
    const result = Math.tanh(num);
×
1735
    return { type: "number", value: result };
×
1736
  }
1737

1738
  @Validate(1, 1, "math_sqrt", false)
1739
  static math_sqrt(
11✔
1740
    args: Value[],
1741
    source: string,
1742
    command: ExprNS.Call,
1743
    context: Context,
1744
  ): NumberValue {
1745
    const x = args[0];
×
1746
    if (!isNumeric(x)) {
×
1747
      handleRuntimeError(
×
1748
        context,
1749
        new TypeError(source, command, context, x.type, "float' or 'int"),
1750
      );
1751
    }
1752

1753
    let num: number;
1754
    if (x.type === "number") {
×
1755
      num = x.value;
×
1756
    } else {
1757
      num = Number(x.value);
×
1758
    }
1759

1760
    if (num < 0) {
×
NEW
1761
      handleRuntimeError(context, new ValueError(source, command, context, "math_sqrt"));
×
1762
    }
1763

1764
    const result = Math.sqrt(num);
×
1765
    return { type: "number", value: result };
×
1766
  }
1767

1768
  @Validate(2, null, "max", true)
1769
  static max(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1770
    const numericTypes = ["bigint", "number"];
4✔
1771
    const firstType = args[0].type;
4✔
1772
    const isNumericValue = numericTypes.includes(firstType);
4✔
1773
    const isString = firstType === "string";
4✔
1774

1775
    for (let i = 1; i < args.length; i++) {
4✔
1776
      const t = args[i].type;
9✔
1777
      if (isNumericValue && !numericTypes.includes(t)) {
9!
1778
        handleRuntimeError(
×
1779
          context,
1780
          new TypeError(source, command, context, args[i].type, "float' or 'int"),
1781
        );
1782
      }
1783
      if (isString && t !== "string") {
9!
1784
        handleRuntimeError(
×
1785
          context,
1786
          new TypeError(source, command, context, args[i].type, "string"),
1787
        );
1788
      }
1789
    }
1790

1791
    let useFloat = false;
4✔
1792
    if (isNumericValue) {
4✔
1793
      for (const arg of args) {
4✔
1794
        if (arg.type === "number") {
13!
1795
          useFloat = true;
×
1796
          break;
×
1797
        }
1798
      }
1799
    }
1800

1801
    let maxIndex = 0;
4✔
1802
    if (isNumericValue) {
4!
1803
      if (useFloat) {
4!
1804
        if (args[0].type !== "number" && args[0].type !== "bigint") {
×
1805
          handleRuntimeError(
×
1806
            context,
1807
            new TypeError(source, command, context, args[0].type, "float' or 'int"),
1808
          );
1809
        }
1810
        let maxVal: number = Number(args[0].value);
×
1811
        for (let i = 1; i < args.length; i++) {
×
1812
          const arg = args[i];
×
1813
          if (!isNumeric(arg)) {
×
1814
            handleRuntimeError(
×
1815
              context,
1816
              new TypeError(source, command, context, arg.type, "float' or 'int"),
1817
            );
1818
          }
1819
          const curr: number = Number(arg.value);
×
1820
          if (curr > maxVal) {
×
1821
            maxVal = curr;
×
1822
            maxIndex = i;
×
1823
          }
1824
        }
1825
      } else {
1826
        if (args[0].type !== "bigint") {
4!
NEW
1827
          handleRuntimeError(context, new TypeError(source, command, context, args[0].type, "int"));
×
1828
        }
1829
        let maxVal: bigint = args[0].value;
4✔
1830
        for (let i = 1; i < args.length; i++) {
4✔
1831
          const arg = args[i];
9✔
1832
          if (arg.type !== "bigint") {
9!
NEW
1833
            handleRuntimeError(context, new TypeError(source, command, context, arg.type, "int"));
×
1834
          }
1835
          const curr: bigint = arg.value;
9✔
1836
          if (curr > maxVal) {
9✔
1837
            maxVal = curr;
7✔
1838
            maxIndex = i;
7✔
1839
          }
1840
        }
1841
      }
1842
    } else if (isString) {
×
1843
      if (args[0].type !== "string") {
×
1844
        handleRuntimeError(
×
1845
          context,
1846
          new TypeError(source, command, context, args[0].type, "string"),
1847
        );
1848
      }
1849
      let maxVal = args[0].value;
×
1850
      for (let i = 1; i < args.length; i++) {
×
1851
        const arg = args[i];
×
1852
        if (arg.type !== "string") {
×
NEW
1853
          handleRuntimeError(context, new TypeError(source, command, context, arg.type, "string"));
×
1854
        }
1855
        const curr = arg.value;
×
1856
        if (curr > maxVal) {
×
1857
          maxVal = curr;
×
1858
          maxIndex = i;
×
1859
        }
1860
      }
1861
    } else {
1862
      // Won't happen
1863
      throw new Error(`max: unsupported type ${firstType}`);
×
1864
    }
1865

1866
    return args[maxIndex];
4✔
1867
  }
1868

1869
  @Validate(2, null, "min", true)
1870
  static min(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
11✔
1871
    if (args.length < 2) {
1!
1872
      handleRuntimeError(
×
1873
        context,
1874
        new MissingRequiredPositionalError(source, command, "min", Number(2), args, true),
1875
      );
1876
    }
1877

1878
    const numericTypes = ["bigint", "number"];
1✔
1879
    const firstType = args[0].type;
1✔
1880
    const isNumericValue = numericTypes.includes(firstType);
1✔
1881
    const isString = firstType === "string";
1✔
1882

1883
    for (let i = 1; i < args.length; i++) {
1✔
1884
      const t = args[i].type;
2✔
1885
      if (isNumericValue && !numericTypes.includes(t)) {
2!
1886
        handleRuntimeError(
×
1887
          context,
1888
          new TypeError(source, command, context, args[i].type, "float' or 'int"),
1889
        );
1890
      }
1891
      if (isString && t !== "string") {
2!
1892
        handleRuntimeError(
×
1893
          context,
1894
          new TypeError(source, command, context, args[i].type, "string"),
1895
        );
1896
      }
1897
    }
1898

1899
    let useFloat = false;
1✔
1900
    if (isNumericValue) {
1✔
1901
      for (const arg of args) {
1✔
1902
        if (arg.type === "number") {
3!
1903
          useFloat = true;
×
1904
          break;
×
1905
        }
1906
      }
1907
    }
1908

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

1974
    return args[maxIndex];
1✔
1975
  }
1976

1977
  @Validate(null, 0, "random_random", true)
1978
  static random_random(
11✔
1979
    _args: Value[],
1980
    _source: string,
1981
    _command: ExprNS.Call,
1982
    _context: Context,
1983
  ): NumberValue {
1984
    const result = Math.random();
×
1985
    return { type: "number", value: result };
×
1986
  }
1987

1988
  @Validate(1, 2, "round", true)
1989
  static round(
11✔
1990
    args: Value[],
1991
    source: string,
1992
    command: ExprNS.Call,
1993
    context: Context,
1994
  ): NumberValue | BigIntValue {
1995
    const numArg = args[0];
15✔
1996
    if (!isNumeric(numArg)) {
15✔
1997
      handleRuntimeError(
2✔
1998
        context,
1999
        new TypeError(source, command, context, numArg.type, "float' or 'int"),
2000
      );
2001
    }
2002

2003
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
2004
    if (args.length === 2 && args[1].type !== "none") {
13✔
2005
      if (args[1].type !== "bigint") {
7✔
2006
        handleRuntimeError(context, new TypeError(source, command, context, args[1].type, "int"));
1✔
2007
      }
2008
      ndigitsArg = args[1];
6✔
2009
    } else {
2010
      const shifted = Intl.NumberFormat("en-US", {
6✔
2011
        roundingMode: "halfEven",
2012
        useGrouping: false,
2013
        maximumFractionDigits: 0,
2014
      } as Intl.NumberFormatOptions).format(numArg.value);
2015
      return { type: "bigint", value: BigInt(shifted) };
6✔
2016
    }
2017

2018
    if (numArg.type === "number") {
6✔
2019
      const numberValue: number = numArg.value;
4✔
2020
      if (ndigitsArg.value >= 0) {
4✔
2021
        const shifted = Intl.NumberFormat("en-US", {
3✔
2022
          roundingMode: "halfEven",
2023
          useGrouping: false,
2024
          maximumFractionDigits: Number(ndigitsArg.value),
2025
        } as Intl.NumberFormatOptions).format(numberValue);
2026
        return { type: "number", value: Number(shifted) };
3✔
2027
      } else {
2028
        const shifted = Intl.NumberFormat("en-US", {
1✔
2029
          roundingMode: "halfEven",
2030
          useGrouping: false,
2031
          maximumFractionDigits: 0,
2032
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
2033
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
2034
      }
2035
    } else {
2036
      if (ndigitsArg.value >= 0) {
2!
2037
        return numArg;
2✔
2038
      } else {
2039
        const shifted = Intl.NumberFormat("en-US", {
×
2040
          roundingMode: "halfEven",
2041
          useGrouping: false,
2042
          maximumFractionDigits: 0,
2043
        } as Intl.NumberFormatOptions).format(
2044
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
2045
        );
2046
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
2047
      }
2048
    }
2049
  }
2050

2051
  @Validate(null, 0, "time_time", true)
2052
  static time_time(
11✔
2053
    _args: Value[],
2054
    _source: string,
2055
    _command: ExprNS.Call,
2056
    _context: Context,
2057
  ): NumberValue {
2058
    const currentTime = Date.now();
×
2059
    return { type: "number", value: currentTime };
×
2060
  }
2061

2062
  @Validate(1, 1, "is_none", true)
2063
  static is_none(
11✔
2064
    args: Value[],
2065
    _source: string,
2066
    _command: ExprNS.Call,
2067
    _context: Context,
2068
  ): BoolValue {
2069
    const obj = args[0];
796✔
2070
    return { type: "bool", value: obj.type === "none" };
796✔
2071
  }
2072

2073
  @Validate(1, 1, "is_float", true)
2074
  static is_float(
11✔
2075
    args: Value[],
2076
    _source: string,
2077
    _command: ExprNS.Call,
2078
    _context: Context,
2079
  ): BoolValue {
2080
    const obj = args[0];
16✔
2081
    return { type: "bool", value: obj.type === "number" };
16✔
2082
  }
2083

2084
  @Validate(1, 1, "is_string", true)
2085
  static is_string(
11✔
2086
    args: Value[],
2087
    _source: string,
2088
    _command: ExprNS.Call,
2089
    _context: Context,
2090
  ): BoolValue {
2091
    const obj = args[0];
25✔
2092
    return { type: "bool", value: obj.type === "string" };
25✔
2093
  }
2094

2095
  @Validate(1, 1, "is_boolean", true)
2096
  static is_boolean(
11✔
2097
    args: Value[],
2098
    _source: string,
2099
    _command: ExprNS.Call,
2100
    _context: Context,
2101
  ): BoolValue {
2102
    const obj = args[0];
17✔
2103
    return { type: "bool", value: obj.type === "bool" };
17✔
2104
  }
2105

2106
  @Validate(1, 1, "is_complex", true)
2107
  static is_complex(
11✔
2108
    args: Value[],
2109
    _source: string,
2110
    _command: ExprNS.Call,
2111
    _context: Context,
2112
  ): BoolValue {
2113
    const obj = args[0];
×
2114
    return { type: "bool", value: obj.type === "complex" };
×
2115
  }
2116

2117
  @Validate(1, 1, "is_int", true)
2118
  static is_int(
11✔
2119
    args: Value[],
2120
    _source: string,
2121
    _command: ExprNS.Call,
2122
    _context: Context,
2123
  ): BoolValue {
2124
    const obj = args[0];
386✔
2125
    return { type: "bool", value: obj.type === "bigint" };
386✔
2126
  }
2127

2128
  @Validate(1, 1, "is_function", true)
2129
  static is_function(
11✔
2130
    args: Value[],
2131
    _source: string,
2132
    _command: ExprNS.Call,
2133
    _context: Context,
2134
  ): BoolValue {
2135
    const obj = args[0];
321✔
2136
    return {
321✔
2137
      type: "bool",
2138
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
740✔
2139
    };
2140
  }
2141

2142
  static async input(
2143
    _args: Value[],
2144
    _source: string,
2145
    _command: ExprNS.Call,
2146
    context: Context,
2147
  ): Promise<Value> {
2148
    const userInput = await receiveInput(context);
×
2149
    return { type: "string", value: userInput };
×
2150
  }
2151

2152
  static async print(
2153
    args: Value[],
2154
    _source: string,
2155
    _command: ExprNS.Call,
2156
    context: Context,
2157
  ): Promise<Value> {
2158
    const output = args.map(arg => toPythonString(arg)).join(" ");
81✔
2159
    await displayOutput(context, output);
81✔
2160
    return { type: "none" };
81✔
2161
  }
2162
  static str(
2163
    args: Value[],
2164
    _source: string,
2165
    _command: ExprNS.Call,
2166
    _context: Context,
2167
  ): StringValue {
2168
    if (args.length === 0) {
14!
2169
      return { type: "string", value: "" };
×
2170
    }
2171
    const obj = args[0];
14✔
2172
    const result = toPythonString(obj);
14✔
2173
    return { type: "string", value: result };
14✔
2174
  }
2175
  @Validate(1, 1, "repr", true)
2176
  static repr(
11✔
2177
    args: Value[],
2178
    _source: string,
2179
    _command: ExprNS.Call,
2180
    _context: Context,
2181
  ): StringValue {
2182
    const obj = args[0];
15✔
2183
    const result = toPythonString(obj, true);
15✔
2184
    return { type: "string", value: result };
15✔
2185
  }
2186
}
2187

2188
import { ExprNS } from "./ast-types";
2189
import { isFalsy } from "./engines/cse/operators";
11✔
2190
import { isNumeric } from "./engines/cse/utils";
11✔
2191
import py_s1_constants from "./stdlib/py_s1_constants.json";
11✔
2192
import { PyComplexNumber } from "./types";
11✔
2193

2194
// NOTE: If we ever switch to another Python “chapter” (e.g. py_s2_constants),
2195
//       just change the variable below to switch to the set.
2196
const constants = py_s1_constants;
11✔
2197

2198
/*
2199
    Create a map to hold built-in constants.
2200
    Each constant is stored with a string key and its corresponding value object.
2201
*/
2202
export const builtInConstants = new Map<string, Value>();
11✔
2203

2204
const constantMap = {
11✔
2205
  math_e: { type: "number", value: Math.E },
2206
  math_inf: { type: "number", value: Infinity },
2207
  math_nan: { type: "number", value: NaN },
2208
  math_pi: { type: "number", value: Math.PI },
2209
  math_tau: { type: "number", value: 2 * Math.PI },
2210
} as const;
2211

2212
for (const name of constants.constants) {
11✔
2213
  const valueObj = constantMap[name as keyof typeof constantMap];
55✔
2214
  if (!valueObj) {
55!
2215
    throw new Error(`Constant '${name}' is not implemented`);
×
2216
  }
2217
  builtInConstants.set(name, valueObj);
55✔
2218
}
2219

2220
/*
2221
    Create a map to hold built-in functions.
2222
    The keys are strings (function names) and the values are functions that can take any arguments.
2223
*/
2224
export const builtIns = new Map<string, BuiltinValue>();
11✔
2225
for (const name of constants.builtInFuncs) {
11✔
2226
  const impl = BuiltInFunctions[name as keyof BuiltInFunctions];
825✔
2227
  if (typeof impl !== "function") {
825!
2228
    throw new Error(`BuiltInFunctions.${name} is not implemented`);
×
2229
  }
2230
  const builtinName = name.startsWith("_") ? name.substring(1) : name;
825!
2231
  builtIns.set(name, {
825✔
2232
    type: "builtin",
2233
    name: builtinName,
2234
    func: impl,
2235
    minArgs: minArgMap.get(name) || 0,
957✔
2236
  });
2237
}
2238

2239
/**
2240
 * Converts a number to a string that mimics Python's float formatting behavior.
2241
 *
2242
 * In Python, float values are printed in scientific notation when their absolute value
2243
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
2244
 * so we explicitly enforce these formatting thresholds.
2245
 *
2246
 * The logic here is based on Python's internal `format_float_short` implementation
2247
 * in CPython's `pystrtod.c`:
2248
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
2249
 *
2250
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
2251
 * output matches Python’s display conventions.
2252
 */
2253
export function toPythonFloat(num: number): string {
11✔
2254
  if (Object.is(num, -0)) {
2!
2255
    return "-0.0";
×
2256
  }
2257
  if (num === 0) {
2!
2258
    return "0.0";
×
2259
  }
2260

2261
  if (num === Infinity) {
2!
2262
    return "inf";
×
2263
  }
2264
  if (num === -Infinity) {
2!
2265
    return "-inf";
×
2266
  }
2267

2268
  if (Number.isNaN(num)) {
2!
2269
    return "nan";
×
2270
  }
2271

2272
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
2273
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
2274
  }
2275
  if (Number.isInteger(num)) {
2!
2276
    return num.toFixed(1).toString();
×
2277
  }
2278
  return num.toString();
2✔
2279
}
2280
function escape(str: string): string {
2281
  let escaped = JSON.stringify(str);
6✔
2282
  if (!(str.includes("'") && !str.includes('"'))) {
6✔
2283
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
6✔
2284
  }
2285
  return escaped;
6✔
2286
}
2287
function toPythonList(obj: Value): string {
2288
  return stringify(obj);
75✔
2289
}
2290

2291
export function toPythonString(obj: Value, repr: boolean = false): string {
11✔
2292
  let ret: string = "";
133✔
2293
  if (obj.type == "builtin") {
133!
2294
    return `<built-in function ${obj.name}>`;
×
2295
  }
2296
  if (obj.type === "bigint" || obj.type === "complex") {
133✔
2297
    ret = obj.value.toString();
34✔
2298
  } else if (obj.type === "number") {
99✔
2299
    ret = toPythonFloat(obj.value);
2✔
2300
  } else if (obj.type === "bool") {
97✔
2301
    if (obj.value) {
2!
2302
      return "True";
2✔
2303
    } else {
2304
      return "False";
×
2305
    }
2306
  } else if (obj.type === "error") {
95!
2307
    return obj.message;
×
2308
  } else if (obj.type === "closure") {
95✔
2309
    if (obj.closure.node) {
2✔
2310
      const funcName =
2311
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
2312
      return `<function ${funcName}>`;
2✔
2313
    }
2314
  } else if (obj.type === "none") {
93✔
2315
    ret = "None";
3✔
2316
  } else if (obj.type === "string") {
90✔
2317
    ret = repr ? escape(obj.value) : obj.value;
15✔
2318
  } else if (obj.type === "function") {
75!
2319
    const funcName = obj.name || "(anonymous)";
×
2320
    ret = `<function ${funcName}>`;
×
2321
  } else if (obj.type === "list") {
75!
2322
    ret = toPythonList(obj);
75✔
2323
  } else {
2324
    ret = `<${obj.type} object>`;
×
2325
  }
2326
  return ret;
129✔
2327
}
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