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

source-academy / py-slang / 24658048223

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

push

github

web-flow
General Fixes (#145)

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

* Fix the step limit

* Refactor type narrowing

* Remove unused code

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

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

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

* Fix formatting

* Add stream test cases and fix function and variable hoisting

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

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

* Format files

1470 of 2361 branches covered (62.26%)

Branch coverage included in aggregate %.

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

8 existing lines in 4 files now uncovered.

4429 of 5889 relevant lines covered (75.21%)

7636.98 hits per line

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

91.49
/src/stdlib/parser.ts
1
import { ExprNS, FunctionParam, StmtNS } from "../ast-types";
1✔
2
import { Context } from "../engines/cse/context";
3
import { handleRuntimeError } from "../engines/cse/error";
1✔
4
import { BuiltinValue, ListValue, NoneValue, StringValue, Value } from "../engines/cse/stash";
5
import { operatorTranslator } from "../engines/cse/types";
1✔
6
import { TypeError } from "../errors/errors";
1✔
7
import { parse } from "../parser";
1✔
8
import pythonLexer from "../parser/lexer";
1✔
9
import { minArgMap, Validate } from "../stdlib";
1✔
10
import { GroupName } from "./utils";
1✔
11

12
const None: NoneValue = { type: "none" };
1✔
13

14
function pair(h: Value, t: Value): ListValue {
15
  return { type: "list", value: [h, t] };
626✔
16
}
17

18
function vector_to_linked_list(arr: Value[]): ListValue | NoneValue {
19
  let res: ListValue | NoneValue = None;
281✔
20
  for (let i = arr.length - 1; i >= 0; i--) {
281✔
21
    res = pair(arr[i], res);
626✔
22
  }
23
  return res;
281✔
24
}
25

26
function isDeclaration(node: StmtNS.Stmt | null | undefined): boolean {
27
  if (!node) return false;
12!
28
  const type = node.kind;
12✔
29
  return type === "FunctionDef" || type === "Assign" || type === "AnnAssign";
12✔
30
}
31

32
function hasDeclaration(statements: StmtNS.Stmt[]): boolean {
33
  return statements.some(isDeclaration);
12✔
34
}
35

36
function makeSequenceIfNeeded(
37
  statements: StmtNS.Stmt[] | StmtNS.Stmt | null | undefined,
38
  declaredNames: Set<string>,
39
): Value {
40
  if (!statements) return None;
94✔
41
  if (!Array.isArray(statements)) return transform(statements, declaredNames);
93!
42
  if (statements.length === 0) {
93!
43
    return None;
×
44
  }
45
  if (statements.length === 1) {
93✔
46
    return transform(statements[0], declaredNames);
86✔
47
  }
48
  return vector_to_linked_list([
7✔
49
    { type: "string", value: "sequence" },
50
    vector_to_linked_list(statements.map(stmt => transform(stmt, declaredNames))),
17✔
51
  ]);
52
}
53

54
function makeBlockIfNeeded(statements: StmtNS.Stmt[], declaredNames: Set<string>): Value {
55
  return hasDeclaration(statements)
12✔
56
    ? vector_to_linked_list([
57
        { type: "string", value: "block" },
58
        makeSequenceIfNeeded(statements, declaredNames),
59
      ])
60
    : makeSequenceIfNeeded(statements, declaredNames);
61
}
62

63
function transformParam(p: FunctionParam): Value {
64
  const nameNode = vector_to_linked_list([
8✔
65
    { type: "string", value: "name" },
66
    { type: "string", value: p.lexeme },
67
  ]);
68
  if (p.isStarred) {
8✔
69
    return vector_to_linked_list([{ type: "string", value: "rest_element" }, nameNode]);
1✔
70
  }
71
  return nameNode;
7✔
72
}
73

74
function transform(
75
  node: ExprNS.Expr | StmtNS.Stmt | null | undefined,
76
  declaredNames: Set<string>,
77
): Value {
78
  if (!node) return None;
334✔
79
  const type = node.kind;
333✔
80
  switch (type) {
333!
81
    case "FileInput":
82
      return makeSequenceIfNeeded((node as StmtNS.FileInput).statements, declaredNames);
72✔
83
    case "SimpleExpr":
84
      return transform((node as StmtNS.SimpleExpr).expression, declaredNames);
55✔
85
    case "Literal": {
86
      const lit = node as ExprNS.Literal;
17✔
87
      let val: Value;
88
      if (typeof lit.value === "number") val = { type: "number", value: lit.value };
17✔
89
      else if (typeof lit.value === "boolean") val = { type: "bool", value: lit.value };
16✔
90
      else if (typeof lit.value === "string") val = { type: "string", value: lit.value };
1!
UNCOV
91
      else val = None;
×
92
      return vector_to_linked_list([{ type: "string", value: "literal" }, val]);
17✔
93
    }
94
    case "BigIntLiteral":
95
      return vector_to_linked_list([
71✔
96
        { type: "string", value: "literal" },
97
        { type: "bigint", value: BigInt((node as ExprNS.BigIntLiteral).value) },
98
      ]);
99
    case "Complex":
100
      return vector_to_linked_list([
2✔
101
        { type: "string", value: "literal" },
102
        { type: "complex", value: (node as ExprNS.Complex).value },
103
      ]);
104
    case "None":
105
      return vector_to_linked_list([{ type: "string", value: "literal" }, None]);
1✔
106
    case "Variable":
107
      return vector_to_linked_list([
28✔
108
        { type: "string", value: "name" },
109
        { type: "string", value: (node as ExprNS.Variable).name.lexeme },
110
      ]);
111
    case "Binary":
112
      return vector_to_linked_list([
10✔
113
        { type: "string", value: "binary_operator_combination" },
114
        {
115
          type: "string",
116
          value: operatorTranslator((node as ExprNS.Binary).operator.type),
117
        },
118
        transform((node as ExprNS.Binary).left, declaredNames),
119
        transform((node as ExprNS.Binary).right, declaredNames),
120
      ]);
121
    case "BoolOp":
122
      return vector_to_linked_list([
2✔
123
        { type: "string", value: "logical_composition" },
124
        {
125
          type: "string",
126
          value: operatorTranslator((node as ExprNS.BoolOp).operator.type),
127
        },
128
        transform((node as ExprNS.BoolOp).left, declaredNames),
129
        transform((node as ExprNS.BoolOp).right, declaredNames),
130
      ]);
131
    case "Compare":
132
      return vector_to_linked_list([
7✔
133
        { type: "string", value: "comparison" },
134
        {
135
          type: "string",
136
          value: operatorTranslator((node as ExprNS.Compare).operator.type),
137
        },
138
        transform((node as ExprNS.Compare).left, declaredNames),
139
        transform((node as ExprNS.Compare).right, declaredNames),
140
      ]);
141
    case "Unary":
142
      return vector_to_linked_list([
2✔
143
        { type: "string", value: "unary_operator_combination" },
144
        {
145
          type: "string",
146
          value: operatorTranslator((node as ExprNS.Unary).operator.type),
147
        },
148
        transform((node as ExprNS.Unary).right, declaredNames),
149
      ]);
150
    case "Assign": {
151
      const assign = node as StmtNS.Assign;
8✔
152
      const isDecl =
153
        assign.target instanceof ExprNS.Variable && !declaredNames.has(assign.target.name.lexeme);
8✔
154
      if (isDecl && assign.target instanceof ExprNS.Variable) {
8✔
155
        declaredNames.add(assign.target.name.lexeme);
7✔
156
      }
157
      return vector_to_linked_list([
8✔
158
        { type: "string", value: isDecl ? "declaration" : "assignment" },
8✔
159
        transform(assign.target, declaredNames),
160
        transform(assign.value, declaredNames),
161
      ]);
162
    }
163
    case "AnnAssign":
164
      return vector_to_linked_list([
×
165
        { type: "string", value: "annotated_assignment" },
166
        transform((node as StmtNS.AnnAssign).target, declaredNames),
167
        transform((node as StmtNS.AnnAssign).ann, declaredNames),
168
        transform((node as StmtNS.AnnAssign).value, declaredNames),
169
      ]);
170
    case "Grouping":
NEW
171
      return transform((node as ExprNS.Grouping).expression, declaredNames);
×
172
    case "If":
173
      return vector_to_linked_list([
2✔
174
        { type: "string", value: "conditional_statement" },
175
        transform((node as StmtNS.If).condition, declaredNames),
176
        makeSequenceIfNeeded((node as StmtNS.If).body, declaredNames),
177
        makeSequenceIfNeeded((node as StmtNS.If).elseBlock, declaredNames),
178
      ]);
179
    case "Ternary":
180
      return vector_to_linked_list([
1✔
181
        { type: "string", value: "conditional_expression" },
182
        transform((node as ExprNS.Ternary).predicate, declaredNames),
183
        transform((node as ExprNS.Ternary).consequent, declaredNames),
184
        transform((node as ExprNS.Ternary).alternative, declaredNames),
185
      ]);
186
    case "While":
187
      return vector_to_linked_list([
4✔
188
        { type: "string", value: "while_loop" },
189
        transform((node as StmtNS.While).condition, declaredNames),
190
        makeSequenceIfNeeded((node as StmtNS.While).body, declaredNames),
191
      ]);
192
    case "For": {
193
      const forNode = node as StmtNS.For;
2✔
194
      return vector_to_linked_list([
2✔
195
        { type: "string", value: "for_loop" },
196
        vector_to_linked_list([
197
          { type: "string", value: "name" },
198
          { type: "string", value: forNode.target.lexeme },
199
        ]),
200
        transform(forNode.iter, declaredNames),
201
        makeSequenceIfNeeded(forNode.body, declaredNames),
202
      ]);
203
    }
204
    case "FunctionDef": {
205
      const fn = node as StmtNS.FunctionDef;
12✔
206
      return vector_to_linked_list([
12✔
207
        { type: "string", value: "function_declaration" },
208
        vector_to_linked_list([
209
          { type: "string", value: "name" },
210
          { type: "string", value: fn.name.lexeme },
211
        ]),
212
        vector_to_linked_list(fn.parameters.map(transformParam)),
213
        makeBlockIfNeeded(fn.body, declaredNames),
214
      ]);
215
    }
216
    case "Lambda": {
217
      const lam = node as ExprNS.Lambda;
3✔
218
      return vector_to_linked_list([
3✔
219
        { type: "string", value: "lambda_expression" },
220
        vector_to_linked_list(lam.parameters.map(transformParam)),
221
        vector_to_linked_list([
222
          { type: "string", value: "return_statement" },
223
          transform(lam.body, declaredNames),
224
        ]),
225
      ]);
226
    }
227
    case "MultiLambda": {
228
      const lam = node as ExprNS.MultiLambda;
×
229
      return vector_to_linked_list([
×
230
        { type: "string", value: "lambda_expression" },
231
        vector_to_linked_list(lam.parameters.map(transformParam)),
232
        makeBlockIfNeeded(lam.body, declaredNames),
233
      ]);
234
    }
235
    case "Return":
236
      return vector_to_linked_list([
8✔
237
        { type: "string", value: "return_statement" },
238
        transform((node as StmtNS.Return).value, declaredNames),
239
      ]);
240
    case "Call":
241
      return vector_to_linked_list([
8✔
242
        { type: "string", value: "application" },
243
        transform((node as ExprNS.Call).callee, declaredNames),
244
        vector_to_linked_list((node as ExprNS.Call).args.map(arg => transform(arg, declaredNames))),
8✔
245
      ]);
246
    case "List":
247
      return vector_to_linked_list([
4✔
248
        { type: "string", value: "array_expression" },
249
        vector_to_linked_list(
250
          (node as ExprNS.List).elements.map(el => transform(el, declaredNames)),
5✔
251
        ),
252
      ]);
253
    case "Subscript":
254
      return vector_to_linked_list([
1✔
255
        { type: "string", value: "object_access" },
256
        transform((node as ExprNS.Subscript).value, declaredNames),
257
        transform((node as ExprNS.Subscript).index, declaredNames),
258
      ]);
259
    case "Starred":
260
      return vector_to_linked_list([
1✔
261
        { type: "string", value: "starred_expression" },
262
        transform((node as ExprNS.Starred).value, declaredNames),
263
      ]);
264
    case "FromImport":
265
      return vector_to_linked_list([
2✔
266
        { type: "string", value: "import_from" },
267
        { type: "string", value: (node as StmtNS.FromImport).module.lexeme },
268
        vector_to_linked_list(
269
          (node as StmtNS.FromImport).names.map(n => ({ type: "string", value: n.name.lexeme })),
3✔
270
        ),
271
      ]);
272
    case "Global":
273
      return vector_to_linked_list([
1✔
274
        { type: "string", value: "global_statement" },
275
        { type: "string", value: (node as StmtNS.Global).name.lexeme },
276
      ]);
277
    case "NonLocal":
278
      return vector_to_linked_list([
1✔
279
        { type: "string", value: "nonlocal_statement" },
280
        { type: "string", value: (node as StmtNS.NonLocal).name.lexeme },
281
      ]);
282
    case "Assert":
283
      return vector_to_linked_list([
2✔
284
        { type: "string", value: "assert_statement" },
285
        transform((node as StmtNS.Assert).value, declaredNames),
286
      ]);
287
    case "Pass":
288
      return vector_to_linked_list([{ type: "string", value: "pass_statement" }]);
4✔
289
    case "Break":
290
      return vector_to_linked_list([{ type: "string", value: "break_statement" }]);
1✔
291
    case "Continue":
292
      return vector_to_linked_list([{ type: "string", value: "continue_statement" }]);
1✔
293
    default:
294
      throw new Error("Cannot transform unknown type: " + type);
×
295
  }
296
}
297

298
class ParserBuiltins {
299
  @Validate(1, 1, "parse", false)
300
  static parse(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
1✔
301
    if (args[0].type !== "string") {
77✔
302
      handleRuntimeError(context, new TypeError(source, command, context, args[0].type, "string"));
5✔
303
    }
304
    const x = args[0].value;
72✔
305
    const program = parse(x + "\n");
72✔
306
    const declaredNames = new Set<string>();
72✔
307
    return transform(program, declaredNames);
72✔
308
  }
309

310
  @Validate(2, 2, "apply_in_underlying_python", false)
311
  static async apply_in_underlying_python(
1✔
312
    args: Value[],
313
    source: string,
314
    command: ExprNS.Call,
315
    context: Context,
316
  ): Promise<Value> {
317
    const func = args[0];
11✔
318
    const argList = args[1];
11✔
319
    const argArray: Value[] = [];
11✔
320
    let current = argList;
11✔
321
    while (current && current.type === "list" && current.value.length === 2) {
11✔
322
      argArray.push(current.value[0]);
16✔
323
      current = current.value[1];
16✔
324
    }
325

326
    if (func.type === "builtin") {
11✔
327
      return func.func(argArray, source, command, context);
7✔
328
    }
329
    return handleRuntimeError(
4✔
330
      context,
331
      new TypeError(source, command, context, func.type, "primitive function"),
332
    );
333
  }
334

335
  @Validate(1, 1, "tokenize", false)
336
  static tokenize(args: Value[], source: string, command: ExprNS.Call, context: Context): Value {
1✔
337
    if (args[0].type !== "string") {
6✔
338
      handleRuntimeError(context, new TypeError(source, command, context, args[0].type, "string"));
3✔
339
    }
340
    const x = args[0].value;
3✔
341
    pythonLexer.reset(x);
3✔
342
    const tokens: StringValue[] = [];
3✔
343
    let tok;
344
    while ((tok = pythonLexer.next())) {
3✔
345
      tokens.push({ type: "string", value: tok.value });
7✔
346
    }
347
    return vector_to_linked_list(tokens);
3✔
348
  }
349
}
350

351
const parserBuiltins = new Map<string, BuiltinValue>();
1✔
352
for (const builtin of Object.getOwnPropertyNames(ParserBuiltins)) {
1✔
353
  const member = (ParserBuiltins as unknown as Record<string, unknown>)[builtin];
6✔
354
  if (typeof member === "function" && !builtin.startsWith("_")) {
6✔
355
    const fn = member as BuiltinValue["func"];
3✔
356
    parserBuiltins.set(builtin, {
3✔
357
      type: "builtin",
358
      func: fn,
359
      name: builtin,
360
      minArgs: minArgMap.get(builtin) || 0,
3!
361
    });
362
  }
363
}
364

365
export default {
1✔
366
  name: GroupName.MCE,
367
  prelude: "",
368
  builtins: parserBuiltins,
369
};
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