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

source-academy / py-slang / 23085365363

14 Mar 2026 09:37AM UTC coverage: 33.992% (-0.8%) from 34.84%
23085365363

Pull #88

github

web-flow
Merge 575ceae97 into d09c6d5b1
Pull Request #88: Various package improvements

387 of 1518 branches covered (25.49%)

Branch coverage included in aggregate %.

902 of 2566 new or added lines in 17 files covered. (35.15%)

11 existing lines in 4 files now uncovered.

1109 of 2883 relevant lines covered (38.47%)

169.44 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
593
    const erfnum = erf(num);
×
594

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

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

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

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

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

NEW
637
    const index = i.value;
×
638

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NEW
1794
    return args[maxIndex];
×
1795
  }
1796

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

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

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

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

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

NEW
1918
    return args[maxIndex];
×
1919
  }
1920

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2128
export function str(
4✔
2129
  args: Value[],
2130
  _source: string,
2131
  _command: ControlItem,
2132
  _context: Context,
2133
): Value {
NEW
2134
  if (args.length === 0) {
×
NEW
2135
    return { type: 'string', value: '' };
×
2136
  }
NEW
2137
  const obj = args[0];
×
NEW
2138
  const result = toPythonString(obj);
×
NEW
2139
  return { type: 'string', value: result };
×
2140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc