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

source-academy / py-slang / 29024235996

09 Jul 2026 02:09PM UTC coverage: 77.536% (-0.02%) from 77.559%
29024235996

push

github

web-flow
Remove equal now that == is structurally equal (#247)

* revamping equality and identity

* wording improved

* taking care of the PVML aspects of this spec change

* Restrict the native Pynter pathway to Python §3, and free up EQP/NEQP for it

Pynter's target is Python (SICPy) §3 specifically (see pynter/README.md,
updated separately in the pynter repo) -- it has no runtime notion of
"chapter" to gate narrower/wider rules for §1/§2/§4, so it implements
§3's semantics unconditionally. Enforce that pathway boundary instead
of silently running other chapters with the wrong rules:

- runCodePvmlDetailed (pvml-runner.ts) and generateNativePynterTestCases
  (tests/utils.ts) now throw a clear error for variant !== 3.
- PyPvmlPynterEvaluator's hardcoded chapter (previously 4, inconsistent
  with Pynter's actual scope) is now 3.
- Every native-Pynter test call site across the suite now declares §3.
  Most are still-valid §3 programs nominally written for another
  chapter (linked-list/stream/pairmutator/list/parser-stdlib), so only
  the declared variant changes. One exception: stdlib.test.ts's
  "Chapter 1 Builtins" miscTests asserts §1-specific restrictions (bool/
  function excluded from ==/!=/ordering, list literals rejected) that
  are genuinely false at §3, not just untested there -- that sweep is
  removed rather than mislabeled.
- operator-conformance-pynter.test.ts's chapter loop is now just [3],
  matching its now-single-chapter scope.

Renumbered EQP/NEQP (added for is/is not's pointer-identity semantics,
distinct from ==/!=' structural equality) from 89/90 down to 85/86, so
PYNTER_OPCODE_MAX can become a single contiguous threshold (0x56) now
that Pynter implements them -- the prior gap (85-88 unimplemented,
89-90 implemented) couldn't be expressed as one cutoff without also
wrongly admitting the still-unimplemented FLOORDIVG/NEWITER/FOR_ITER.

Verified via yarn pynter:report against a Pynter build with the
matching VM-side changes (separate PR in... (continued)

2644 of 3690 branches covered (71.65%)

Branch coverage included in aggregate %.

6147 of 7648 relevant lines covered (80.37%)

12450.71 hits per line

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

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

19
const miscBuiltins = new Map<string, BuiltinValue>();
18✔
20

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

38
  @Validate(null, 2, "complex", true)
39
  static complex(
18✔
40
    args: Value[],
41
    source: string,
42
    command: ExprNS.Call,
43
    context: Context,
44
  ): ComplexValue {
45
    if (args.length === 0) {
45✔
46
      return { type: "complex", value: new PyComplexNumber(0, 0) };
1✔
47
    }
48
    if (args.length == 1) {
44✔
49
      const val = args[0];
27✔
50
      if (
27✔
51
        val.type !== "bigint" &&
103✔
52
        val.type !== "number" &&
53
        val.type !== "bool" &&
54
        val.type !== "string" &&
55
        val.type !== "complex"
56
      ) {
57
        handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
3✔
58
      }
59
      return {
24✔
60
        type: "complex",
61
        value: PyComplexNumber.fromValue(context, source, command, val.value),
62
      };
63
    }
64
    const invalidType = args.filter(
17✔
65
      val =>
66
        val.type !== "bigint" &&
34✔
67
        val.type !== "number" &&
68
        val.type !== "bool" &&
69
        val.type !== "complex",
70
    );
71
    if (invalidType.length > 0) {
17✔
72
      handleRuntimeError(
3✔
73
        context,
74
        new TypeError(
75
          source,
76
          command,
77
          context,
78
          invalidType[0].type,
79
          "'int', 'float', 'bool' or 'complex'",
80
        ),
81
      );
82
    }
83
    const [real, imag] = args as (BigIntValue | NumberValue | BoolValue | ComplexValue)[];
14✔
84
    const realPart = PyComplexNumber.fromValue(context, source, command, real.value);
14✔
85
    const imagPart = PyComplexNumber.fromValue(context, source, command, imag.value);
14✔
86
    return { type: "complex", value: realPart.add(imagPart.mul(new PyComplexNumber(0, 1))) };
14✔
87
  }
88

89
  @Validate(1, 1, "real", true)
90
  static real(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
18✔
91
    const val = args[0];
×
92
    if (val.type !== "complex") {
×
93
      handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
×
94
    }
95
    return { type: "number", value: val.value.real };
×
96
  }
97

98
  @Validate(1, 1, "imag", true)
99
  static imag(args: Value[], source: string, command: ExprNS.Call, context: Context): NumberValue {
18✔
100
    const val = args[0];
×
101
    if (val.type !== "complex") {
×
102
      handleRuntimeError(context, new TypeError(source, command, context, val.type, "complex"));
×
103
    }
104
    return { type: "number", value: val.value.imag };
×
105
  }
106

107
  @Validate(1, 1, "abs", false)
108
  static abs(
18✔
109
    args: Value[],
110
    source: string,
111
    command: ExprNS.Call,
112
    context: Context,
113
  ): BigIntValue | NumberValue {
114
    const x = args[0];
18✔
115
    switch (x.type) {
18!
116
      case "bigint": {
117
        const intVal = x.value;
14✔
118
        const result: bigint = intVal < 0 ? -intVal : intVal;
14✔
119
        return { type: "bigint", value: result };
14✔
120
      }
121
      case "number": {
122
        return { type: "number", value: Math.abs(x.value) };
2✔
123
      }
124
      case "complex": {
125
        // Calculate the modulus (absolute value) of a complex number.
126
        const real = x.value.real;
×
127
        const imag = x.value.imag;
×
128
        const modulus = Math.sqrt(real * real + imag * imag);
×
129
        return { type: "number", value: modulus };
×
130
      }
131
      default:
132
        handleRuntimeError(
2✔
133
          context,
134
          new TypeError(source, command, context, args[0].type, "float', 'int' or 'complex"),
135
        );
136
    }
137
  }
138

139
  @Validate(1, 1, "len", true)
140
  static len(args: Value[], source: string, command: ExprNS.Call, context: Context): BigIntValue {
18✔
141
    const val = args[0];
16✔
142
    if (val.type === "string" || val.type === "list") {
16✔
143
      // The spread operator is used to count the number of Unicode code points
144
      // in the string
145
      return { type: "bigint", value: BigInt([...val.value].length) };
9✔
146
    }
147
    handleRuntimeError(
7✔
148
      context,
149
      new TypeError(source, command, context, val.type, "object with length"),
150
    );
151
  }
152

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

158
  @Validate(2, null, "max", true)
159
  static max(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
18✔
160
    if (args.every(isNumeric) || args.every(arg => arg.type === "string")) {
4!
161
      let maxIndex = 0;
4✔
162
      for (let i = 1; i < args.length; i++) {
4✔
163
        if (args[i].value > args[maxIndex].value) {
9✔
164
          maxIndex = i;
7✔
165
        }
166
      }
167
      return args[maxIndex];
4✔
168
    }
169
    if (isNumeric(args[0])) {
×
170
      const invalidType = args.find(arg => !isNumeric(arg))!;
×
171
      handleRuntimeError(
×
172
        context,
173
        new TypeError(source, command, context, invalidType.type, "int' or 'float"),
174
      );
175
    } else if (args[0].type === "string") {
×
176
      const invalidType = args.find(arg => arg.type !== "string")!;
×
177
      handleRuntimeError(context, new TypeError(source, command, context, invalidType.type, "str"));
×
178
    } else {
179
      handleRuntimeError(
×
180
        context,
181
        new TypeError(source, command, context, args[0].type, "int', 'float' or 'str'"),
182
      );
183
    }
184
  }
185

186
  @Validate(2, null, "min", true)
187
  static min(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
18✔
188
    if (args.every(isNumeric) || args.every(arg => arg.type === "string")) {
1!
189
      let minIndex = 0;
1✔
190
      for (let i = 1; i < args.length; i++) {
1✔
191
        if (args[i].value < args[minIndex].value) {
2✔
192
          minIndex = i;
1✔
193
        }
194
      }
195
      return args[minIndex];
1✔
196
    }
197
    if (isNumeric(args[0])) {
×
198
      const invalidType = args.find(arg => !isNumeric(arg))!;
×
199
      handleRuntimeError(
×
200
        context,
201
        new TypeError(source, command, context, invalidType.type, "int' or 'float"),
202
      );
203
    } else if (args[0].type === "string") {
×
204
      const invalidType = args.find(arg => arg.type !== "string")!;
×
205
      handleRuntimeError(context, new TypeError(source, command, context, invalidType.type, "str"));
×
206
    } else {
207
      handleRuntimeError(
×
208
        context,
209
        new TypeError(source, command, context, args[0].type, "int', 'float' or 'str'"),
210
      );
211
    }
212
  }
213

214
  @Validate(null, 0, "random_random", true)
215
  static random_random(
18✔
216
    _args: Value[],
217
    _source: string,
218
    _command: ExprNS.Call,
219
    _context: Context,
220
  ): NumberValue {
221
    const result = Math.random();
×
222
    return { type: "number", value: result };
×
223
  }
224

225
  @Validate(1, 2, "round", true)
226
  static round(
18✔
227
    args: Value[],
228
    source: string,
229
    command: ExprNS.Call,
230
    context: Context,
231
  ): NumberValue | BigIntValue {
232
    const numArg = args[0];
15✔
233
    if (!isNumeric(numArg)) {
15✔
234
      handleRuntimeError(
2✔
235
        context,
236
        new TypeError(source, command, context, numArg.type, "float' or 'int"),
237
      );
238
    }
239

240
    let ndigitsArg: BigIntValue = { type: "bigint", value: BigInt(0) };
13✔
241
    if (args.length === 2 && args[1].type !== "none") {
13✔
242
      if (args[1].type !== "bigint") {
7✔
243
        handleRuntimeError(context, new TypeError(source, command, context, args[1].type, "int"));
1✔
244
      }
245
      ndigitsArg = args[1];
6✔
246
    } else {
247
      const shifted = Intl.NumberFormat("en-US", {
6✔
248
        roundingMode: "halfEven",
249
        useGrouping: false,
250
        maximumFractionDigits: 0,
251
      } as Intl.NumberFormatOptions).format(numArg.value);
252
      return { type: "bigint", value: BigInt(shifted) };
6✔
253
    }
254

255
    if (numArg.type === "number") {
6✔
256
      const numberValue: number = numArg.value;
4✔
257
      if (ndigitsArg.value >= 0) {
4✔
258
        const shifted = Intl.NumberFormat("en-US", {
3✔
259
          roundingMode: "halfEven",
260
          useGrouping: false,
261
          maximumFractionDigits: Number(ndigitsArg.value),
262
        } as Intl.NumberFormatOptions).format(numberValue);
263
        return { type: "number", value: Number(shifted) };
3✔
264
      } else {
265
        const shifted = Intl.NumberFormat("en-US", {
1✔
266
          roundingMode: "halfEven",
267
          useGrouping: false,
268
          maximumFractionDigits: 0,
269
        } as Intl.NumberFormatOptions).format(numArg.value / 10 ** -Number(ndigitsArg.value));
270
        return { type: "number", value: Number(shifted) * 10 ** -Number(ndigitsArg.value) };
1✔
271
      }
272
    } else {
273
      if (ndigitsArg.value >= 0) {
2!
274
        return numArg;
2✔
275
      } else {
276
        const shifted = Intl.NumberFormat("en-US", {
×
277
          roundingMode: "halfEven",
278
          useGrouping: false,
279
          maximumFractionDigits: 0,
280
        } as Intl.NumberFormatOptions).format(
281
          Number(numArg.value) / 10 ** -Number(ndigitsArg.value),
282
        );
283
        return { type: "bigint", value: BigInt(shifted) * 10n ** -ndigitsArg.value };
×
284
      }
285
    }
286
  }
287

288
  @Validate(null, 0, "time_time", true)
289
  static time_time(
18✔
290
    _args: Value[],
291
    _source: string,
292
    _command: ExprNS.Call,
293
    _context: Context,
294
  ): NumberValue {
295
    const currentTime = Date.now();
×
296
    return { type: "number", value: currentTime };
×
297
  }
298

299
  @Validate(1, 1, "is_none", true)
300
  static is_none(
18✔
301
    args: Value[],
302
    _source: string,
303
    _command: ExprNS.Call,
304
    _context: Context,
305
  ): BoolValue {
306
    const obj = args[0];
462✔
307
    return { type: "bool", value: obj.type === "none" };
462✔
308
  }
309

310
  @Validate(1, 1, "is_float", true)
311
  static is_float(
18✔
312
    args: Value[],
313
    _source: string,
314
    _command: ExprNS.Call,
315
    _context: Context,
316
  ): BoolValue {
317
    const obj = args[0];
9✔
318
    return { type: "bool", value: obj.type === "number" };
9✔
319
  }
320

321
  @Validate(1, 1, "is_string", true)
322
  static is_string(
18✔
323
    args: Value[],
324
    _source: string,
325
    _command: ExprNS.Call,
326
    _context: Context,
327
  ): BoolValue {
328
    const obj = args[0];
18✔
329
    return { type: "bool", value: obj.type === "string" };
18✔
330
  }
331

332
  @Validate(1, 1, "is_boolean", true)
333
  static is_boolean(
18✔
334
    args: Value[],
335
    _source: string,
336
    _command: ExprNS.Call,
337
    _context: Context,
338
  ): BoolValue {
339
    const obj = args[0];
10✔
340
    return { type: "bool", value: obj.type === "bool" };
10✔
341
  }
342

343
  @Validate(1, 1, "is_complex", true)
344
  static is_complex(
18✔
345
    args: Value[],
346
    _source: string,
347
    _command: ExprNS.Call,
348
    _context: Context,
349
  ): BoolValue {
350
    const obj = args[0];
×
351
    return { type: "bool", value: obj.type === "complex" };
×
352
  }
353

354
  @Validate(1, 1, "is_number", true)
355
  static is_number(
18✔
356
    args: Value[],
357
    _source: string,
358
    _command: ExprNS.Call,
359
    _context: Context,
360
  ): BoolValue {
361
    // Mirrors Scheme's `number?`: true for any number in the numeric tower
362
    // (integer, float or complex), but not for booleans.
363
    const obj = args[0];
9✔
364
    return {
9✔
365
      type: "bool",
366
      value: obj.type === "bigint" || obj.type === "number" || obj.type === "complex",
23✔
367
    };
368
  }
369

370
  @Validate(1, 1, "is_integer", true)
371
  static is_integer(
18✔
372
    args: Value[],
373
    _source: string,
374
    _command: ExprNS.Call,
375
    _context: Context,
376
  ): BoolValue {
377
    const obj = args[0];
9✔
378
    return { type: "bool", value: obj.type === "bigint" };
9✔
379
  }
380

381
  @Validate(1, 1, "is_function", true)
382
  static is_function(
18✔
383
    args: Value[],
384
    _source: string,
385
    _command: ExprNS.Call,
386
    _context: Context,
387
  ): BoolValue {
388
    const obj = args[0];
314✔
389
    return {
314✔
390
      type: "bool",
391
      value: obj.type === "function" || obj.type === "closure" || obj.type === "builtin",
719✔
392
    };
393
  }
394

395
  static async input(
396
    _args: Value[],
397
    _source: string,
398
    _command: ExprNS.Call,
399
    context: Context,
400
  ): Promise<Value> {
401
    const userInput = await receiveInput(context);
×
402
    return { type: "string", value: userInput };
×
403
  }
404

405
  static async print(
406
    args: Value[],
407
    _source: string,
408
    _command: ExprNS.Call,
409
    context: Context,
410
  ): Promise<Value> {
411
    const output = args.map(arg => toPythonString(arg)).join(" ") + "\n";
169✔
412
    await displayOutput(context, output);
154✔
413
    return { type: "none" };
154✔
414
  }
415
  static str(
416
    args: Value[],
417
    _source: string,
418
    _command: ExprNS.Call,
419
    _context: Context,
420
  ): StringValue {
421
    if (args.length === 0) {
14!
422
      return { type: "string", value: "" };
×
423
    }
424
    const obj = args[0];
14✔
425
    const result = toPythonString(obj);
14✔
426
    return { type: "string", value: result };
14✔
427
  }
428
  @Validate(1, 1, "repr", true)
429
  static repr(
18✔
430
    args: Value[],
431
    _source: string,
432
    _command: ExprNS.Call,
433
    _context: Context,
434
  ): StringValue {
435
    const obj = args[0];
15✔
436
    const result = toPythonString(obj, true);
15✔
437
    return { type: "string", value: result };
15✔
438
  }
439
}
440
for (const builtin of Object.getOwnPropertyNames(MiscBuiltins)) {
18✔
441
  if (
486✔
442
    typeof MiscBuiltins[builtin as keyof typeof MiscBuiltins] === "function" &&
918✔
443
    !builtin.startsWith("_")
444
  ) {
445
    miscBuiltins.set(builtin, {
432✔
446
      type: "builtin",
447
      func: MiscBuiltins[builtin as keyof typeof MiscBuiltins] as BuiltinValue["func"],
448
      name: builtin,
449
      minArgs: minArgMap.get(builtin) || 0,
558✔
450
    });
451
  }
452
}
453

454
export default {
18✔
455
  name: GroupName.MISC,
456
  prelude: "",
457
  builtins: miscBuiltins,
458
};
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