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

source-academy / py-slang / 24697489873

21 Apr 2026 12:27AM UTC coverage: 73.8% (+0.5%) from 73.253%
24697489873

Pull #147

github

web-flow
Merge 2b15214dd into 98f152d78
Pull Request #147: Split `stdlib.ts` into a `MATH` and `MISC` group

1562 of 2383 branches covered (65.55%)

Branch coverage included in aggregate %.

273 of 341 new or added lines in 11 files covered. (80.06%)

4559 of 5911 relevant lines covered (77.13%)

11051.06 hits per line

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

77.78
/src/stdlib/utils.ts
1
import { ExprNS } from "../ast-types";
2
import { Context } from "../engines/cse/context";
3
import { handleRuntimeError } from "../engines/cse/error";
12✔
4
import { Value } from "../engines/cse/stash";
5
import { MissingRequiredPositionalError, TooManyPositionalArgumentsError } from "../errors";
12✔
6
import { stringify } from "../utils/stringify";
12✔
7

8
export enum GroupName {
12✔
9
  MATH = "math",
12✔
10
  MISC = "misc",
12✔
11
  LINKED_LISTS = "linked-list",
12✔
12
  STREAMS = "stream",
12✔
13
  LIST = "list",
12✔
14
  PAIRMUTATORS = "pair-mutators",
12✔
15
  MCE = "mce",
12✔
16
}
17

18
/**
19
 * A Group represents a library of related built-in values (e.g., linked list library, stream library).
20
 * It consists of primitive built-in values implemented in TypeScript, as well as a prelude of non-primitive built-in values
21
 * evaluated in Python using the current evaluator
22
 */
23
export type Group = {
24
  name: GroupName;
25

26
  /**
27
   * The prelude is a string of code that defines the non-primitive built-in values in this group.
28
   * It is loaded before any user code is run, so that the built-in values are available to the user code.
29
   * The execution of functions is performed using the current evaluator, so the prelude can use other built-in values defined in other groups.
30
   */
31
  prelude: string;
32

33
  /**
34
   * The builtins are primitive built-in values implemented in TypeScript. They are to provide functionalities that are not easily implemented in the required sublanguage of Python,
35
   * such as variadic functions in Python §2 (e.g., `linked_list`)
36
   *
37
   * They are stored as a map from the name of the built-in value to its corresponding implementation.
38
   */
39
  builtins: Map<string, Value>;
40
};
41

42
export const minArgMap = new Map<string, number>();
12✔
43

44
export function Validate<T extends Value | Promise<Value>>(
12✔
45
  minArgs: number | null,
46
  maxArgs: number | null,
47
  functionName: string,
48
  strict: boolean,
49
) {
50
  return function (
903✔
51
    _target: unknown,
52
    _propertyKey: string,
53
    descriptor: TypedPropertyDescriptor<
54
      (args: Value[], source: string, command: ExprNS.Call, context: Context) => T
55
    >,
56
  ): void {
57
    const originalMethod = descriptor.value!;
903✔
58
    minArgMap.set(functionName, minArgs || 0);
903✔
59
    descriptor.value = function (
903✔
60
      args: Value[],
61
      source: string,
62
      command: ExprNS.Call,
63
      context: Context,
64
    ): T {
65
      if (minArgs !== null && args.length < minArgs) {
5,708✔
66
        handleRuntimeError(
11✔
67
          context,
68
          new MissingRequiredPositionalError(source, command, functionName, minArgs, args, strict),
69
        );
70
      }
71

72
      if (maxArgs !== null && args.length > maxArgs) {
5,697✔
73
        handleRuntimeError(
6✔
74
          context,
75
          new TooManyPositionalArgumentsError(source, command, functionName, maxArgs, args, strict),
76
        );
77
      }
78

79
      return originalMethod.call(this, args, source, command, context);
5,691✔
80
    };
81
  };
82
}
83

84
/**
85
 * Converts a number to a string that mimics Python's float formatting behavior.
86
 *
87
 * In Python, float values are printed in scientific notation when their absolute value
88
 * is ≥ 1e16 or < 1e-4. This differs from JavaScript/TypeScript's default behavior,
89
 * so we explicitly enforce these formatting thresholds.
90
 *
91
 * The logic here is based on Python's internal `format_float_short` implementation
92
 * in CPython's `pystrtod.c`:
93
 * https://github.com/python/cpython/blob/main/Python/pystrtod.c
94
 *
95
 * Special cases such as -0, Infinity, and NaN are also handled to ensure that
96
 * output matches Python’s display conventions.
97
 */
98
export function toPythonFloat(num: number): string {
12✔
99
  if (Object.is(num, -0)) {
2!
NEW
100
    return "-0.0";
×
101
  }
102
  if (num === 0) {
2!
NEW
103
    return "0.0";
×
104
  }
105

106
  if (num === Infinity) {
2!
NEW
107
    return "inf";
×
108
  }
109
  if (num === -Infinity) {
2!
NEW
110
    return "-inf";
×
111
  }
112

113
  if (Number.isNaN(num)) {
2!
NEW
114
    return "nan";
×
115
  }
116

117
  if (Math.abs(num) >= 1e16 || (num !== 0 && Math.abs(num) < 1e-4)) {
2!
NEW
118
    return num.toExponential().replace(/e([+-])(\d)$/, "e$10$2");
×
119
  }
120
  if (Number.isInteger(num)) {
2!
NEW
121
    return num.toFixed(1).toString();
×
122
  }
123
  return num.toString();
2✔
124
}
125
function escape(str: string): string {
126
  let escaped = JSON.stringify(str);
6✔
127
  if (!(str.includes("'") && !str.includes('"'))) {
6✔
128
    escaped = `'${escaped.slice(1, -1).replace(/'/g, "\\'").replace(/\\"/g, '"')}'`;
6✔
129
  }
130
  return escaped;
6✔
131
}
132
function toPythonList(obj: Value): string {
133
  return stringify(obj);
75✔
134
}
135

136
export function toPythonString(obj: Value, repr: boolean = false): string {
12✔
137
  let ret: string = "";
211✔
138
  if (obj.type == "builtin") {
211!
NEW
139
    return `<built-in function ${obj.name}>`;
×
140
  }
141
  if (obj.type === "bigint" || obj.type === "complex") {
211✔
142
    ret = obj.value.toString();
112✔
143
  } else if (obj.type === "number") {
99✔
144
    ret = toPythonFloat(obj.value);
2✔
145
  } else if (obj.type === "bool") {
97✔
146
    if (obj.value) {
2!
147
      return "True";
2✔
148
    } else {
NEW
149
      return "False";
×
150
    }
151
  } else if (obj.type === "error") {
95!
NEW
152
    return obj.message;
×
153
  } else if (obj.type === "closure") {
95✔
154
    if (obj.closure.node) {
2✔
155
      const funcName =
156
        obj.closure.node.kind === "FunctionDef" ? obj.closure.node.name.lexeme : "(anonymous)";
2!
157
      return `<function ${funcName}>`;
2✔
158
    }
159
  } else if (obj.type === "none") {
93✔
160
    ret = "None";
3✔
161
  } else if (obj.type === "string") {
90✔
162
    ret = repr ? escape(obj.value) : obj.value;
15✔
163
  } else if (obj.type === "function") {
75!
NEW
164
    const funcName = obj.name || "(anonymous)";
×
NEW
165
    ret = `<function ${funcName}>`;
×
166
  } else if (obj.type === "list") {
75!
167
    ret = toPythonList(obj);
75✔
168
  } else {
NEW
169
    ret = `<${obj.type} object>`;
×
170
  }
171
  return ret;
207✔
172
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc