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

source-academy / py-slang / 28707958055

04 Jul 2026 01:38PM UTC coverage: 78.628% (-0.02%) from 78.643%
28707958055

Pull #193

github

web-flow
Merge 6b84f2d5a into 966e5fa49
Pull Request #193: Renaming variables to match SICPy

2536 of 3492 branches covered (72.62%)

Branch coverage included in aggregate %.

50 of 53 new or added lines in 6 files covered. (94.34%)

46 existing lines in 2 files now uncovered.

6014 of 7382 relevant lines covered (81.47%)

13706.08 hits per line

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

84.18
/src/stdlib/misc.ts
1
import { ExprNS } from "../ast-types";
2
import { Context } from "../engines/cse/context";
3
import { ControlItem } from "../engines/cse/control";
4
import { handleRuntimeError } from "../engines/cse/error";
15✔
5
import { isFalsy } from "../engines/cse/operators";
15✔
6
import {
7
  BigIntValue,
8
  BoolValue,
9
  BuiltinValue,
10
  ComplexValue,
11
  NumberValue,
12
  StringValue,
13
  Value,
14
} from "../engines/cse/stash";
15
import { displayOutput, receiveInput } from "../engines/cse/streams";
15✔
16
import { isNumeric } from "../engines/cse/utils";
15✔
17
import { TypeError, UserError, ValueError } from "../errors";
15✔
18
import { PyComplexNumber } from "../types";
15✔
19
import { GroupName, minArgMap, toPythonString, Validate } from "./utils";
15✔
20

21
const miscBuiltins = new Map<string, BuiltinValue>();
15✔
22

23
export class MiscBuiltins {
15✔
24
  @Validate(1, 1, "arity", true)
25
  static arity(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
15✔
26
    const func = args[0];
98✔
27
    if (func.type !== "builtin" && func.type !== "closure") {
98✔
28
      handleRuntimeError(context, new TypeError(source, command, context, func.type, "function"));
7✔
29
    }
30
    if (func.type === "closure") {
91✔
31
      const variadicInstance = func.closure.node.parameters.findIndex(param => param.isStarred);
11✔
32
      if (variadicInstance !== -1) {
11✔
33
        return { type: "bigint", value: BigInt(variadicInstance) };
4✔
34
      }
35
      return { type: "bigint", value: BigInt(func.closure.node.parameters.length) };
7✔
36
    }
37
    return { type: "bigint", value: BigInt(func.minArgs) };
80✔
38
  }
39

40
  @Validate(null, 2, "int", true)
41
  static int(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
15✔
42
    if (args.length === 0) {
27✔
43
      return { type: "bigint", value: BigInt(0) };
1✔
44
    }
45
    const arg = args[0];
26✔
46
    if (!isNumeric(arg) && arg.type !== "string" && arg.type !== "bool") {
26✔
47
      handleRuntimeError(
4✔
48
        context,
49
        new TypeError(source, command, context, arg.type, "str, int, float or bool"),
50
      );
51
    }
52

53
    if (args.length === 1) {
22✔
54
      if (arg.type === "number") {
10✔
55
        const truncated = Math.trunc(arg.value);
2✔
56
        return { type: "bigint", value: BigInt(truncated) };
2✔
57
      }
58
      if (arg.type === "bigint") {
8✔
59
        return { type: "bigint", value: arg.value };
1✔
60
      }
61
      if (arg.type === "string") {
7✔
62
        const str = arg.value.trim().replace(/_/g, "");
6✔
63
        if (!/^[+-]?\d+$/.test(str)) {
6✔
64
          handleRuntimeError(context, new ValueError(source, command, context, "int"));
3✔
65
        }
66
        return { type: "bigint", value: BigInt(str) };
3✔
67
      }
68
      return { type: "bigint", value: arg.value ? BigInt(1) : BigInt(0) };
1!
69
    }
70
    const baseArg = args[1];
12✔
71
    if (arg.type !== "string") {
12✔
72
      handleRuntimeError(context, new TypeError(source, command, context, arg.type, "string"));
1✔
73
    }
74
    if (baseArg.type !== "bigint") {
11!
UNCOV
75
      handleRuntimeError(context, new TypeError(source, command, context, baseArg.type, "int"));
×
76
    }
77

78
    let base = Number(baseArg.value);
11✔
79
    let str = arg.value.trim().replace(/_/g, "");
11✔
80

81
    const sign = str.startsWith("-") ? -1 : 1;
11✔
82
    if (str.startsWith("+") || str.startsWith("-")) {
11✔
83
      str = str.substring(1);
1✔
84
    }
85

86
    if (base === 0) {
11✔
87
      if (str.startsWith("0x") || str.startsWith("0X")) {
7✔
88
        base = 16;
1✔
89
        str = str.substring(2);
1✔
90
      } else if (str.startsWith("0o") || str.startsWith("0O")) {
6✔
91
        base = 8;
4✔
92
        str = str.substring(2);
4✔
93
      } else if (str.startsWith("0b") || str.startsWith("0B")) {
2✔
94
        base = 2;
1✔
95
        str = str.substring(2);
1✔
96
      } else {
97
        base = 10;
1✔
98
      }
99
    }
100

101
    if (base < 2 || base > 36) {
11✔
102
      handleRuntimeError(context, new ValueError(source, command, context, "int"));
2✔
103
    }
104

105
    const validChars = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, base);
9✔
106
    const regex = new RegExp(`^[${validChars}]+$`, "i");
9✔
107
    if (!regex.test(str)) {
9✔
108
      handleRuntimeError(context, new ValueError(source, command, context, "int"));
3✔
109
    }
110

111
    let res = BigInt(0);
6✔
112
    for (const char of str) {
6✔
113
      res = res * BigInt(base) + BigInt(validChars.indexOf(char.toLowerCase()));
16✔
114
    }
115
    return { type: "bigint", value: BigInt(sign) * res };
6✔
116
  }
117

118
  @Validate(null, 1, "float", true)
119
  static float(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
15✔
120
    if (args.length === 0) {
25✔
121
      return { type: "number", value: 0 };
1✔
122
    }
123
    const val = args[0];
24✔
124
    if (val.type === "bigint") {
24✔
125
      return { type: "number", value: Number(val.value) };
1✔
126
    } else if (val.type === "number") {
23✔
127
      return { type: "number", value: val.value };
2✔
128
    } else if (val.type === "bool") {
21✔
129
      return { type: "number", value: val.value ? 1 : 0 };
1!
130
    } else if (val.type === "string") {
20✔
131
      const str = val.value.trim().replace(/_/g, "").toLowerCase();
16✔
132
      const mappings = {
16✔
133
        inf: Infinity,
134
        "+inf": Infinity,
135
        "-inf": -Infinity,
136
        infinity: Infinity,
137
        "+infinity": Infinity,
138
        "-infinity": -Infinity,
139
        nan: NaN,
140
        "+nan": NaN,
141
        "-nan": NaN,
142
      };
143
      if (str in mappings) {
16✔
144
        return { type: "number", value: mappings[str as keyof typeof mappings] };
8✔
145
      }
146
      const num = Number(str);
8✔
147
      if (isNaN(num)) {
8✔
148
        handleRuntimeError(context, new ValueError(source, command, context, "float"));
1✔
149
      }
150
      return { type: "number", value: num };
7✔
151
    }
152
    handleRuntimeError(
4✔
153
      context,
154
      new TypeError(source, command, context, val.type, "float', 'int', 'bool' or 'str"),
155
    );
156
  }
157

158
  @Validate(null, 2, "complex", true)
159
  static complex(
15✔
160
    args: Value[],
161
    source: string,
162
    command: ExprNS.Call,
163
    context: Context,
164
  ): ComplexValue {
165
    if (args.length === 0) {
36✔
166
      return { type: "complex", value: new PyComplexNumber(0, 0) };
1✔
167
    }
168
    if (args.length == 1) {
35✔
169
      const val = args[0];
27✔
170
      if (
27✔
171
        val.type !== "bigint" &&
103✔
172
        val.type !== "number" &&
173
        val.type !== "bool" &&
174
        val.type !== "string" &&
175
        val.type !== "complex"
176
      ) {
177
        handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
3✔
178
      }
179
      return {
24✔
180
        type: "complex",
181
        value: PyComplexNumber.fromValue(context, source, command, val.value),
182
      };
183
    }
184
    const invalidType = args.filter(
8✔
185
      val =>
186
        val.type !== "bigint" &&
16✔
187
        val.type !== "number" &&
188
        val.type !== "bool" &&
189
        val.type !== "complex",
190
    );
191
    if (invalidType.length > 0) {
8✔
192
      handleRuntimeError(
3✔
193
        context,
194
        new TypeError(
195
          source,
196
          command,
197
          context,
198
          invalidType[0].type,
199
          "'int', 'float', 'bool' or 'complex'",
200
        ),
201
      );
202
    }
203
    const [real, imag] = args as (BigIntValue | NumberValue | BoolValue | ComplexValue)[];
5✔
204
    const realPart = PyComplexNumber.fromValue(context, source, command, real.value);
5✔
205
    const imagPart = PyComplexNumber.fromValue(context, source, command, imag.value);
5✔
206
    return { type: "complex", value: realPart.add(imagPart.mul(new PyComplexNumber(0, 1))) };
5✔
207
  }
208

209
  @Validate(1, 1, "real", true)
210
  static real(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
15✔
UNCOV
211
    const val = args[0];
×
UNCOV
212
    if (val.type !== "complex") {
×
UNCOV
213
      handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
×
214
    }
UNCOV
215
    return { type: "number", value: val.value.real };
×
216
  }
217

218
  @Validate(1, 1, "imag", true)
219
  static imag(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
15✔
UNCOV
220
    const val = args[0];
×
221
    if (val.type !== "complex") {
×
222
      handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
×
223
    }
UNCOV
224
    return { type: "number", value: val.value.imag };
×
225
  }
226

227
  @Validate(null, 1, "bool", true)
228
  static bool(args: Value[], _source: string, _command: ControlItem, _context: Context): BoolValue {
15✔
229
    if (args.length === 0) {
13✔
230
      return { type: "bool", value: false };
1✔
231
    }
232
    const val = args[0];
12✔
233
    return { type: "bool", value: !isFalsy(val) };
12✔
234
  }
235

236
  @Validate(1, 1, "abs", false)
237
  static abs(
15✔
238
    args: Value[],
239
    source: string,
240
    command: ExprNS.Call,
241
    context: Context,
242
  ): BigIntValue | NumberValue {
243
    const x = args[0];
18✔
244
    switch (x.type) {
18!
245
      case "bigint": {
246
        const intVal = x.value;
14✔
247
        const result: bigint = intVal < 0 ? -intVal : intVal;
14✔
248
        return { type: "bigint", value: result };
14✔
249
      }
250
      case "number": {
251
        return { type: "number", value: Math.abs(x.value) };
2✔
252
      }
253
      case "complex": {
254
        // Calculate the modulus (absolute value) of a complex number.
UNCOV
255
        const real = x.value.real;
×
UNCOV
256
        const imag = x.value.imag;
×
UNCOV
257
        const modulus = Math.sqrt(real * real + imag * imag);
×
UNCOV
258
        return { type: "number", value: modulus };
×
259
      }
260
      default:
261
        handleRuntimeError(
2✔
262
          context,
263
          new TypeError(source, command, context, args[0].type, "float', 'int' or 'complex"),
264
        );
265
    }
266
  }
267

268
  @Validate(1, 1, "len", true)
269
  static len(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
15✔
270
    const val = args[0];
16✔
271
    if (val.type === "string" || val.type === "list") {
16✔
272
      // The spread operator is used to count the number of Unicode code points
273
      // in the string
274
      return { type: "bigint", value: BigInt([...val.value].length) };
9✔
275
    }
276
    handleRuntimeError(
7✔
277
      context,
278
      new TypeError(source, command, context, val.type, "object with length"),
279
    );
280
  }
281

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

287
  @Validate(2, null, "max", true)
288
  static max(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
15✔
289
    if (args.every(isNumeric) || args.every(arg => arg.type === "string")) {
4!
290
      let maxIndex = 0;
4✔
291
      for (let i = 1; i < args.length; i++) {
4✔
292
        if (args[i].value > args[maxIndex].value) {
9✔
293
          maxIndex = i;
7✔
294
        }
295
      }
296
      return args[maxIndex];
4✔
297
    }
UNCOV
298
    if (isNumeric(args[0])) {
×
UNCOV
299
      const invalidType = args.find(arg => !isNumeric(arg))!;
×
UNCOV
300
      handleRuntimeError(
×
301
        context,
302
        new TypeError(source, command, context, invalidType.type, "int' or 'float"),
303
      );
UNCOV
304
    } else if (args[0].type === "string") {
×
UNCOV
305
      const invalidType = args.find(arg => arg.type !== "string")!;
×
UNCOV
306
      handleRuntimeError(context, new TypeError(source, command, context, invalidType.type, "str"));
×
307
    } else {
UNCOV
308
      handleRuntimeError(
×
309
        context,
310
        new TypeError(source, command, context, args[0].type, "int', 'float' or 'str'"),
311
      );
312
    }
313
  }
314

315
  @Validate(2, null, "min", true)
316
  static min(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
15✔
317
    if (args.every(isNumeric) || args.every(arg => arg.type === "string")) {
1!
318
      let minIndex = 0;
1✔
319
      for (let i = 1; i < args.length; i++) {
1✔
320
        if (args[i].value < args[minIndex].value) {
2✔
321
          minIndex = i;
1✔
322
        }
323
      }
324
      return args[minIndex];
1✔
325
    }
UNCOV
326
    if (isNumeric(args[0])) {
×
UNCOV
327
      const invalidType = args.find(arg => !isNumeric(arg))!;
×
UNCOV
328
      handleRuntimeError(
×
329
        context,
330
        new TypeError(source, command, context, invalidType.type, "int' or 'float"),
331
      );
UNCOV
332
    } else if (args[0].type === "string") {
×
UNCOV
333
      const invalidType = args.find(arg => arg.type !== "string")!;
×
UNCOV
334
      handleRuntimeError(context, new TypeError(source, command, context, invalidType.type, "str"));
×
335
    } else {
UNCOV
336
      handleRuntimeError(
×
337
        context,
338
        new TypeError(source, command, context, args[0].type, "int', 'float' or 'str'"),
339
      );
340
    }
341
  }
342

343
  @Validate(null, 0, "random_random", true)
344
  static random_random(
15✔
345
    _args: Value[],
346
    _source: string,
347
    _command: ExprNS.Call,
348
    _context: Context,
349
  ): NumberValue {
350
    const result = Math.random();
×
351
    return { type: "number", value: result };
×
352
  }
353

354
  @Validate(1, 2, "round", true)
355
  static round(
15✔
356
    args: Value[],
357
    source: string,
358
    command: ExprNS.Call,
359
    context: Context,
360
  ): NumberValue | BigIntValue {
361
    const numArg = args[0];
15✔
362
    if (!isNumeric(numArg)) {
15✔
363
      handleRuntimeError(
2✔
364
        context,
365
        new TypeError(source, command, context, numArg.type, "float' or 'int"),
366
      );
367
    }
368

369
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
370
    if (args.length === 2 && args[1].type !== "none") {
13✔
371
      if (args[1].type !== "bigint") {
7✔
372
        handleRuntimeError(context, new TypeError(source, command, context, args[1].type, "int"));
1✔
373
      }
374
      ndigitsArg = args[1];
6✔
375
    } else {
376
      const shifted = Intl.NumberFormat("en-US", {
6✔
377
        roundingMode: "halfEven",
378
        useGrouping: false,
379
        maximumFractionDigits: 0,
380
      } as Intl.NumberFormatOptions).format(numArg.value);
381
      return { type: "bigint", value: BigInt(shifted) };
6✔
382
    }
383

384
    if (numArg.type === "number") {
6✔
385
      const numberValue: number = numArg.value;
4✔
386
      if (ndigitsArg.value >= 0) {
4✔
387
        const shifted = Intl.NumberFormat("en-US", {
3✔
388
          roundingMode: "halfEven",
389
          useGrouping: false,
390
          maximumFractionDigits: Number(ndigitsArg.value),
391
        } as Intl.NumberFormatOptions).format(numberValue);
392
        return { type: "number", value: Number(shifted) };
3✔
393
      } else {
394
        const shifted = Intl.NumberFormat("en-US", {
1✔
395
          roundingMode: "halfEven",
396
          useGrouping: false,
397
          maximumFractionDigits: 0,
398
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
399
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
400
      }
401
    } else {
402
      if (ndigitsArg.value >= 0) {
2!
403
        return numArg;
2✔
404
      } else {
UNCOV
405
        const shifted = Intl.NumberFormat("en-US", {
×
406
          roundingMode: "halfEven",
407
          useGrouping: false,
408
          maximumFractionDigits: 0,
409
        } as Intl.NumberFormatOptions).format(
410
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
411
        );
UNCOV
412
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
413
      }
414
    }
415
  }
416

417
  @Validate(null, 0, "time_time", true)
418
  static time_time(
15✔
419
    _args: Value[],
420
    _source: string,
421
    _command: ExprNS.Call,
422
    _context: Context,
423
  ): NumberValue {
UNCOV
424
    const currentTime = Date.now();
×
UNCOV
425
    return { type: "number", value: currentTime };
×
426
  }
427

428
  @Validate(1, 1, "is_none", true)
429
  static is_none(
15✔
430
    args: Value[],
431
    _source: string,
432
    _command: ExprNS.Call,
433
    _context: Context,
434
  ): BoolValue {
435
    const obj = args[0];
796✔
436
    return { type: "bool", value: obj.type === "none" };
796✔
437
  }
438

439
  @Validate(1, 1, "is_float", true)
440
  static is_float(
15✔
441
    args: Value[],
442
    _source: string,
443
    _command: ExprNS.Call,
444
    _context: Context,
445
  ): BoolValue {
446
    const obj = args[0];
16✔
447
    return { type: "bool", value: obj.type === "number" };
16✔
448
  }
449

450
  @Validate(1, 1, "is_string", true)
451
  static is_string(
15✔
452
    args: Value[],
453
    _source: string,
454
    _command: ExprNS.Call,
455
    _context: Context,
456
  ): BoolValue {
457
    const obj = args[0];
25✔
458
    return { type: "bool", value: obj.type === "string" };
25✔
459
  }
460

461
  @Validate(1, 1, "is_boolean", true)
462
  static is_boolean(
15✔
463
    args: Value[],
464
    _source: string,
465
    _command: ExprNS.Call,
466
    _context: Context,
467
  ): BoolValue {
468
    const obj = args[0];
17✔
469
    return { type: "bool", value: obj.type === "bool" };
17✔
470
  }
471

472
  @Validate(1, 1, "is_complex", true)
473
  static is_complex(
15✔
474
    args: Value[],
475
    _source: string,
476
    _command: ExprNS.Call,
477
    _context: Context,
478
  ): BoolValue {
UNCOV
479
    const obj = args[0];
×
UNCOV
480
    return { type: "bool", value: obj.type === "complex" };
×
481
  }
482

483
  @Validate(1, 1, "is_number", true)
484
  static is_number(
15✔
485
    args: Value[],
486
    _source: string,
487
    _command: ExprNS.Call,
488
    _context: Context,
489
  ): BoolValue {
490
    // Mirrors Scheme's `number?`: true for any number in the numeric tower
491
    // (integer, float or complex), but not for booleans.
492
    const obj = args[0];
9✔
493
    return {
9✔
494
      type: "bool",
495
      value: obj.type === "bigint" || obj.type === "number" || obj.type === "complex",
23✔
496
    };
497
  }
498

499
  @Validate(1, 1, "is_integer", true)
500
  static is_integer(
15✔
501
    args: Value[],
502
    _source: string,
503
    _command: ExprNS.Call,
504
    _context: Context,
505
  ): BoolValue {
506
    const obj = args[0];
386✔
507
    return { type: "bool", value: obj.type === "bigint" };
386✔
508
  }
509

510
  @Validate(1, 1, "is_function", true)
511
  static is_function(
15✔
512
    args: Value[],
513
    _source: string,
514
    _command: ExprNS.Call,
515
    _context: Context,
516
  ): BoolValue {
517
    const obj = args[0];
321✔
518
    return {
321✔
519
      type: "bool",
520
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
740✔
521
    };
522
  }
523

524
  static async input(
525
    _args: Value[],
526
    _source: string,
527
    _command: ExprNS.Call,
528
    context: Context,
529
  ): Promise<Value> {
UNCOV
530
    const userInput = await receiveInput(context);
×
UNCOV
531
    return { type: "string", value: userInput };
×
532
  }
533

534
  static async print(
535
    args: Value[],
536
    _source: string,
537
    _command: ExprNS.Call,
538
    context: Context,
539
  ): Promise<Value> {
540
    const output = args.map(arg => toPythonString(arg)).join(" ") + "\n";
162✔
541
    await displayOutput(context, output);
147✔
542
    return { type: "none" };
147✔
543
  }
544
  static str(
545
    args: Value[],
546
    _source: string,
547
    _command: ExprNS.Call,
548
    _context: Context,
549
  ): StringValue {
550
    if (args.length === 0) {
14!
UNCOV
551
      return { type: "string", value: "" };
×
552
    }
553
    const obj = args[0];
14✔
554
    const result = toPythonString(obj);
14✔
555
    return { type: "string", value: result };
14✔
556
  }
557
  @Validate(1, 1, "repr", true)
558
  static repr(
15✔
559
    args: Value[],
560
    _source: string,
561
    _command: ExprNS.Call,
562
    _context: Context,
563
  ): StringValue {
564
    const obj = args[0];
15✔
565
    const result = toPythonString(obj, true);
15✔
566
    return { type: "string", value: result };
15✔
567
  }
568
}
569
for (const builtin of Object.getOwnPropertyNames(MiscBuiltins)) {
15✔
570
  if (
450✔
571
    typeof MiscBuiltins[builtin as keyof typeof MiscBuiltins] === "function" &&
855✔
572
    !builtin.startsWith("_")
573
  ) {
574
    miscBuiltins.set(builtin, {
405✔
575
      type: "builtin",
576
      func: MiscBuiltins[builtin as keyof typeof MiscBuiltins] as BuiltinValue["func"],
577
      name: builtin,
578
      minArgs: minArgMap.get(builtin) || 0,
555✔
579
    });
580
  }
581
}
582

583
export default {
15✔
584
  name: GroupName.MISC,
585
  prelude: "",
586
  builtins: miscBuiltins,
587
};
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