pll-python 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +114 -0
  2. package/cli.cjs +1711 -0
  3. package/package.json +36 -0
  4. package/worker.cjs +1478 -0
package/worker.cjs ADDED
@@ -0,0 +1,1478 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/desktop/pyodideWorker.ts
26
+ var import_node_worker_threads = require("node:worker_threads");
27
+
28
+ // src/common/workspaceFilePolicy.ts
29
+ var MAX_FILE_BYTES = 2 * 1024 * 1024;
30
+ var MAX_TOTAL_BYTES = 8 * 1024 * 1024;
31
+ var WRITEBACK_EXTENSIONS = /* @__PURE__ */ new Set([
32
+ ".csv",
33
+ ".txt",
34
+ ".tsv",
35
+ ".json",
36
+ ".md",
37
+ ".dat",
38
+ ".xml"
39
+ ]);
40
+ function extensionOf(name) {
41
+ const dot = name.lastIndexOf(".");
42
+ if (dot <= 0) {
43
+ return "";
44
+ }
45
+ return name.slice(dot).toLowerCase();
46
+ }
47
+ function isSafeBasename(name) {
48
+ if (!name || name === "." || name === "..") {
49
+ return false;
50
+ }
51
+ if (name.startsWith(".")) {
52
+ return false;
53
+ }
54
+ if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ function isWritebackName(name) {
60
+ return isSafeBasename(name) && WRITEBACK_EXTENSIONS.has(extensionOf(name));
61
+ }
62
+ function utf8ByteLength(text) {
63
+ return new TextEncoder().encode(text).byteLength;
64
+ }
65
+
66
+ // src/common/memfsWorkspace.ts
67
+ var PLL_WORK_DIR = "/home/pyodide/pll_workspace";
68
+ var lastMounted = /* @__PURE__ */ new Map();
69
+ function mtimeMs(stat) {
70
+ const m = stat.mtime;
71
+ if (m instanceof Date) {
72
+ return m.getTime();
73
+ }
74
+ return typeof m === "number" ? m : Number.NaN;
75
+ }
76
+ function joinCwd(FS, name) {
77
+ const cwd = FS.cwd();
78
+ return cwd.endsWith("/") ? `${cwd}${name}` : `${cwd}/${name}`;
79
+ }
80
+ function ensureWorkDir(FS) {
81
+ try {
82
+ FS.mkdir(PLL_WORK_DIR);
83
+ } catch {
84
+ }
85
+ FS.chdir(PLL_WORK_DIR);
86
+ }
87
+ function listRegularFiles(FS) {
88
+ const names = [];
89
+ for (const name of FS.readdir(FS.cwd())) {
90
+ if (name === "." || name === "..") {
91
+ continue;
92
+ }
93
+ try {
94
+ const stat = FS.stat(joinCwd(FS, name));
95
+ if (FS.isFile(stat.mode)) {
96
+ names.push(name);
97
+ }
98
+ } catch {
99
+ }
100
+ }
101
+ return names;
102
+ }
103
+ function clearWorkDirFiles(FS) {
104
+ for (const name of listRegularFiles(FS)) {
105
+ try {
106
+ FS.unlink(joinCwd(FS, name));
107
+ } catch {
108
+ }
109
+ }
110
+ }
111
+ function mountWorkspaceFiles(FS, files) {
112
+ ensureWorkDir(FS);
113
+ clearWorkDirFiles(FS);
114
+ lastMounted = /* @__PURE__ */ new Map();
115
+ for (const file of files) {
116
+ if (!isSafeBasename(file.name)) {
117
+ continue;
118
+ }
119
+ if (utf8ByteLength(file.contents) > MAX_FILE_BYTES) {
120
+ continue;
121
+ }
122
+ const path = joinCwd(FS, file.name);
123
+ FS.writeFile(path, file.contents);
124
+ try {
125
+ FS.utime(path, 0, 0);
126
+ } catch {
127
+ }
128
+ lastMounted.set(file.name, {
129
+ contents: file.contents,
130
+ mtimeMs: mtimeMs(FS.stat(path))
131
+ });
132
+ }
133
+ }
134
+ function collectChangedWorkspaceFiles(FS) {
135
+ ensureWorkDir(FS);
136
+ const out = [];
137
+ for (const name of listRegularFiles(FS)) {
138
+ if (!isWritebackName(name)) {
139
+ continue;
140
+ }
141
+ const path = joinCwd(FS, name);
142
+ let contents;
143
+ let stat;
144
+ try {
145
+ const raw = FS.readFile(path, { encoding: "utf8" });
146
+ contents = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
147
+ stat = FS.stat(path);
148
+ } catch {
149
+ continue;
150
+ }
151
+ if (utf8ByteLength(contents) > MAX_FILE_BYTES) {
152
+ continue;
153
+ }
154
+ const prev = lastMounted.get(name);
155
+ if (prev && prev.contents === contents && prev.mtimeMs === mtimeMs(stat)) {
156
+ continue;
157
+ }
158
+ out.push({ name, contents });
159
+ }
160
+ return out;
161
+ }
162
+
163
+ // src/common/pyodideBootstrap.py
164
+ var pyodideBootstrap_default = '# Python Language Levels runtime bootstrap.\n#\n# This module is loaded into Pyodide once when the runtime initializes.\n# It exposes four entry points used by the TypeScript host:\n#\n# _pll_run_file(code, filename, session_key, level="raw") -> dict\n# _pll_repl_eval(code, session_key, level="raw") -> dict\n# _pll_repl_check(source) -> dict\n# _pll_has_tests(code) -> bool\n# _pll_run_tests(code, filename, level="raw") -> dict\n# _pll_static_analyze(code, level, filename, session_key=None) -> list[dict]\n#\n# The level is the only knob: it decides both the static checks and whether\n# annotations are instrumented. `raw` is plain Python plus PLL\'s libraries.\n#\n# Each returns a JSON-friendly dict / list of dicts so the JS side can\n# consume the result via `proxy.toJs({ dict_converter: Object.fromEntries })`.\n#\n# Sessions\n# --------\n# Each Python file gets its own session, keyed by an opaque string the host\n# chooses (typically the document URI). Sessions hold their own globals\n# dict, so file A\'s `data = ...` doesn\'t leak into file B\'s REPL prompt.\n# `_pll_run_file` resets the addressed session\'s globals to the baseline\n# template before executing; `_pll_repl_eval` does NOT reset, so REPL\n# input keeps the names defined by the most recent Run File of the same\n# session.\n\nimport traceback as _tb_mod\nimport ast as _ast\nimport copy as _pll_copy\nimport codeop as _codeop\nimport contextlib\nimport json as _pll_json\nimport sys as _sys\nimport builtins as _builtins_mod\n\n# Must match PLL_WORK_DIR in memfsWorkspace.ts. Sibling files are mounted\n# here and it is cwd, so open("cars.csv") works. It must not sit first on\n# sys.path or a neighboring pandas.py wins over the real package.\n_PLL_WORK_DIR = "/home/pyodide/pll_workspace"\n\n# Vendored pure-Python wheels (typeguard + typing_extensions) that the\n# worker writes into MEMFS before calling `_pll_enable_type_checking`.\n# Must match PLL_VENDOR_DIR in pythonVendor.ts.\n_PLL_VENDOR_DIR = "/pll_vendor"\n\n# Set by `_pll_enable_type_checking`. While False, `_pll_instrument_types`\n# leaves code untouched, so a program still runs - just unchecked.\n_PLL_TYPEGUARD_READY = False\n_pll_typeguard_transformer = None\n_pll_type_check_error = None\n\n# Whether a bool is rejected where int / float is annotated. Set per run\n# from the language level; see `levelRejectsBoolAsNumber` in level.ts.\n# Python counts True as 1 and both mypy and typeguard follow it, which is a\n# hole at the teaching levels: a student who annotates `int` and passes\n# `True` has almost always made a mistake. `advanced` keeps Python\'s rule.\n_PLL_STRICT_NUMBERS = False\n\n# Whether annotations are instrumented at all. Set per run from the language\n# level; see `levelHasTypeChecking` in level.ts. False only at `#level raw`,\n# which exists so a file can opt out. There is no separate setting: the level\n# is the single input, so nothing can disagree with it.\n_PLL_TYPE_CHECK = False\n\n\ndef _pll_check_strict_int(value, origin_type, args, memo):\n """Replaces typeguard\'s `int` check; bools are not whole numbers here.\n\n The wording matches typeguard\'s own so the host-side explainer needs\n no special case: it sees the actual type (bool) and the expected one.\n """\n if isinstance(value, bool) or not isinstance(value, int):\n raise _pll_type_check_error("is not an instance of int")\n\n\ndef _pll_check_strict_float(value, origin_type, args, memo):\n """As `_pll_check_strict_int`, for the int-or-float numeric tower."""\n if isinstance(value, bool) or not isinstance(value, (int, float)):\n raise _pll_type_check_error("is neither float or int")\n\n\ndef _pll_strict_number_lookup(origin_type, args, extras):\n """typeguard checker lookup; consulted on every check, so the flag\n can be flipped per run without re-registering."""\n if not _PLL_STRICT_NUMBERS:\n return None\n if origin_type is int:\n return _pll_check_strict_int\n if origin_type is float:\n return _pll_check_strict_float\n return None\n\n\ndef _pll_apply_level(level):\n """Set the per-run strictness implied by the language level.\n\n The level is the only input. `raw` checks nothing; `advanced` checks\n annotations by Python\'s own rules; the two teaching levels additionally\n reject a bool where a number is annotated. Must agree with level.ts.\n """\n global _PLL_STRICT_NUMBERS, _PLL_TYPE_CHECK\n _PLL_TYPE_CHECK = level != "raw"\n _PLL_STRICT_NUMBERS = level in ("beginner", "intermediate")\n\n\ndef _pll_enable_type_checking():\n """Put the vendored wheels on sys.path and import typeguard.\n\n Returns True when runtime type checking is available. A failure here\n is deliberately not fatal: students\' code must still run, so the\n feature degrades to "no type checks" rather than breaking the editor.\n """\n global _PLL_TYPEGUARD_READY, _pll_typeguard_transformer, _pll_type_check_error\n if _PLL_TYPEGUARD_READY:\n return True\n try:\n for name in ("typing_extensions.whl", "typeguard.whl"):\n path = _PLL_VENDOR_DIR + "/" + name\n # Appended, not prepended: a real package in site-packages\n # (Pyodide ships typing_extensions too) should still win.\n if path not in _sys.path:\n _sys.path.append(path)\n from typeguard import TypeCheckError, TypeCheckMemo\n from typeguard._checkers import checker_lookup_functions\n from typeguard._config import CollectionCheckStrategy, global_config\n from typeguard._functions import check_variable_assignment\n from typeguard._transformer import TypeguardTransformer\n\n # `list[int]` should mean *every* item is an int. typeguard only\n # checks the first item by default, which silently accepts\n # [1, 2, "three"] - confusing when the annotation says otherwise.\n global_config.collection_check_strategy = CollectionCheckStrategy.ALL_ITEMS\n\n # Referenced by name in the AST that `_PllTopLevelAnnAssign`\n # injects, so they have to be visible in user globals.\n _pll_initial_globals["_pll_tg_memo"] = TypeCheckMemo\n _pll_initial_globals["_pll_tg_check_assign"] = check_variable_assignment\n # Consulted before typeguard\'s builtin lookup, and gated on\n # `_PLL_STRICT_NUMBERS` so it is a no-op at `advanced`.\n _pll_type_check_error = TypeCheckError\n if _pll_strict_number_lookup not in checker_lookup_functions:\n checker_lookup_functions.insert(0, _pll_strict_number_lookup)\n\n _pll_typeguard_transformer = TypeguardTransformer\n _PLL_TYPEGUARD_READY = True\n except BaseException:\n _PLL_TYPEGUARD_READY = False\n return _PLL_TYPEGUARD_READY\n\n\ndef _pll_is_vendor_frame(filename):\n return isinstance(filename, str) and filename.startswith(_PLL_VENDOR_DIR)\n\n\ndef _pll_format_exception(exc):\n """`format_exception`, minus frames inside the vendored type checker.\n\n A typeguard failure otherwise ends in several frames of typeguard\'s\n own checker, burying the student\'s line under machinery they did not\n write. When nothing is dropped this returns the stdlib formatting\n unchanged, so ordinary errors look exactly as they did before.\n """\n try:\n frames = _tb_mod.extract_tb(exc.__traceback__)\n kept = [f for f in frames if not _pll_is_vendor_frame(f.filename)]\n if len(kept) == len(frames):\n return "".join(_tb_mod.format_exception(type(exc), exc, exc.__traceback__))\n parts = ["Traceback (most recent call last):\\n"]\n parts.extend(_tb_mod.StackSummary.from_list(kept).format())\n parts.extend(_tb_mod.format_exception_only(type(exc), exc))\n return "".join(parts)\n except BaseException:\n return "".join(_tb_mod.format_exception(type(exc), exc, exc.__traceback__))\n\n\ndef _pll_protect_import_path():\n """Keep sibling .py files from shadowing installed packages.\n\n Python puts \'\' (cwd) first on sys.path. After chdir to the work dir,\n import pandas would load a mounted pandas.py. Drop the empty entry,\n append the work dir so unique sibling modules still import, and evict\n anything already loaded from that folder (a failed pandas.py import\n leaves a poisoned sys.modules entry).\n """\n while "" in _sys.path:\n _sys.path.remove("")\n while _PLL_WORK_DIR in _sys.path:\n _sys.path.remove(_PLL_WORK_DIR)\n _sys.path.append(_PLL_WORK_DIR)\n prefix = _PLL_WORK_DIR + "/"\n for name, mod in list(_sys.modules.items()):\n filename = getattr(mod, "__file__", None)\n if isinstance(filename, str) and filename.startswith(prefix):\n _sys.modules.pop(name, None)\n\n# Per-session globals dicts, keyed by session_key (e.g. document URI).\n# Created lazily; initialized from `_pll_initial_globals`.\n_pll_sessions = {}\n\n# The "template" globals used to seed each new session and to reset a\n# session at the start of every Run File. Populated by PYODIDE_INSTALL_PY\n# at the end of bootstrap so the template includes the PLL image\n# library + the auto-display helper.\n_pll_initial_globals = {"__name__": "__main__", "__builtins__": __builtins__}\n\n# Display emissions captured during the most recent `_pll_run_file` /\n# `_pll_repl_eval` call. The host drains this list after the call.\n# Each entry is a typed dict (`{"type": "stdout"|"stderr", "text": ...}`,\n# `{"type": "image", ...}`, or `{"type": "table", ...}`) so the host can\n# dispatch by kind while preserving the *exact* order in which the user\'s\n# code produced them - that way a `print()` followed by a top-level table\n# shows up as text-then-table in the interactions view, even though\n# stdout and image/table emissions take different paths inside Python.\n# Pyodide is single-threaded, so a single shared list is fine.\n_pll_displays = []\n\n# When set (by either host during a file run), this is a JS callback taking\n# one JSON string. Every display payload \u2014 each stdout/stderr write, image,\n# and table \u2014 is emitted through it as it is produced, so the interactions\n# view updates *during* the run instead of only at the end. This is what\n# lets an interactive program print a prompt before input() blocks.\n# Left as None during REPL/tests, where output is delivered in one batch.\n_pll_live_emit = None\n\n\ndef _pll_push(payload):\n """Emit a display payload live, or record it for the end of the run.\n\n Exactly one of the two: when a live hook is installed the host streams\n each payload as it happens and then discards `result["displays"]`\n (see `withLiveEmit` / the `runFile` case in workerHost.ts), so also\n accumulating them costs memory and a large FFI conversion for a list\n nobody reads. A program printing in a loop built a multi-million entry\n list that was copied and converted to JS purely to be dropped.\n """\n emit = _pll_live_emit\n if emit is None:\n _pll_displays.append(payload)\n return\n try:\n emit(_pll_json.dumps(payload))\n except Exception:\n pass\n\n\nclass _PllStream:\n """Drop-in replacement for `sys.stdout` / `sys.stderr` during a run.\n\n Each `write` pushes a typed entry onto `_pll_displays` so text\n output interleaves with image/table emissions. We also keep the\n aggregate string so `result["stdout"]` / `result["stderr"]` can\n still be inspected by smoke tests and any caller that just wants\n "what did the program print?".\n """\n\n __slots__ = ("_kind", "_chunks")\n\n def __init__(self, kind):\n self._kind = kind # "stdout" or "stderr"\n self._chunks = []\n\n def write(self, s):\n if not isinstance(s, str):\n s = str(s)\n if s:\n _pll_push({"type": self._kind, "text": s})\n self._chunks.append(s)\n return len(s)\n\n def writelines(self, lines):\n for line in lines:\n self.write(line)\n\n def flush(self):\n pass\n\n def isatty(self):\n return False\n\n def getvalue(self):\n return "".join(self._chunks)\n\n\ndef _pll_extract_display(value):\n """Try every known display protocol on `value`.\n\n Returns a typed payload (`{"type": "image"|"table", ...}`) or None if\n `value` doesn\'t know how to display itself.\n """\n if hasattr(value, "_pll_table_data"):\n try:\n data = value._pll_table_data()\n except Exception:\n return None\n if isinstance(data, dict):\n data = dict(data)\n data["type"] = "table"\n return data\n if hasattr(value, "_pll_image_data"):\n try:\n data = value._pll_image_data()\n except Exception:\n return None\n # _pll_image_data historically uses {"type": "svg", ...}\n # internally; promote to the unified outer type.\n if isinstance(data, dict):\n payload = dict(data)\n payload["type"] = "image"\n payload["format"] = data.get("type", "svg")\n return payload\n return None\n\n\ndef _pll_show_top_level(value):\n """Emit a value produced by a top-level expression statement.\n\n Mirrors the behavior of Python\'s interactive shell: `None` is suppressed,\n PLL images and tables are captured for the host to render, anything\n else is printed via `repr` so bare expressions like `1 + 2` still display.\n """\n if value is None:\n return\n # A reactor pushed its own card when it was started; printing its repr\n # as well would be noise. Duck-typed like the display protocols, so the\n # bootstrap still knows nothing about reactorLib.\n if getattr(value, "_pll_already_displayed", False):\n return\n payload = _pll_extract_display(value)\n if payload is not None:\n _pll_push(payload)\n return\n print(repr(value))\n\n\n# Seed the template with the auto-display helper. The image library names\n# get added later by PYODIDE_INSTALL_PY.\n_pll_initial_globals["_pll_show_top_level"] = _pll_show_top_level\n\n\ndef _pll_get_session(session_key):\n """Get-or-create the globals dict for `session_key`.\n\n Newly-created sessions start as a copy of `_pll_initial_globals`\n (so all baseline names like the image primitives are present).\n """\n g = _pll_sessions.get(session_key)\n if g is None:\n g = dict(_pll_initial_globals)\n _pll_sessions[session_key] = g\n return g\n\n\ndef _pll_reset_session(session_key):\n """Reset the globals for `session_key` to the baseline template.\n\n Mutates the existing dict in place (`clear` + `update`) so any cached\n reference to it (e.g. from `_pll_show_top_level`\'s closure or from\n Pyodide\'s `globals.get(...)`) remains valid.\n """\n g = _pll_get_session(session_key)\n g.clear()\n g.update(_pll_initial_globals)\n return g\n\n\nclass _PllTopLevelAnnAssign(_ast.NodeTransformer):\n """Type-check annotated assignments outside functions.\n\n typeguard instruments function arguments, return values, and\n annotated assignments *inside* functions, but leaves\n `total: int = "oops"` at module level unchecked. Students write those,\n so we wrap the value in typeguard\'s own check to get the same wording.\n\n Function and class bodies are skipped: typeguard already covers\n functions, and a bare `name: str` in a class body is a field\n declaration (dataclasses, etc.) rather than an assignment.\n """\n\n def visit_FunctionDef(self, node):\n return node\n\n visit_AsyncFunctionDef = visit_FunctionDef\n\n def visit_ClassDef(self, node):\n return node\n\n def visit_AnnAssign(self, node):\n if node.value is None or not isinstance(node.target, _ast.Name):\n return node\n node.value = self._checked(node.target.id, node.value, node.annotation)\n return node\n\n def _checked(self, name, value, annotation):\n """`value` -> `_pll_tg_check_assign(value, [(name, ann)], memo)`."""\n memo = _ast.Call(\n func=_ast.Name(id="_pll_tg_memo", ctx=_ast.Load()),\n args=[\n _ast.Call(func=_ast.Name(id="globals", ctx=_ast.Load()), args=[], keywords=[]),\n _ast.Call(func=_ast.Name(id="locals", ctx=_ast.Load()), args=[], keywords=[]),\n ],\n keywords=[],\n )\n target = _ast.Tuple(\n elts=[_ast.Constant(value=name), _pll_copy.deepcopy(annotation)],\n ctx=_ast.Load(),\n )\n call = _ast.Call(\n func=_ast.Name(id="_pll_tg_check_assign", ctx=_ast.Load()),\n args=[value, _ast.List(elts=[target], ctx=_ast.Load()), memo],\n keywords=[],\n )\n _ast.copy_location(call, value)\n _ast.fix_missing_locations(call)\n return call\n\n\ndef _pll_parse_and_instrument(code, filename):\n """Parse `code`, adding runtime type checks when they are available.\n\n Whether to check at all comes from `_PLL_TYPE_CHECK`, which\n `_pll_apply_level` sets from the level before this is called.\n\n Instrumentation is attempted on a second parse and validated by\n compiling it, so anything typeguard cannot handle falls back to the\n plain tree rather than failing the run.\n """\n tree = _ast.parse(code, filename=filename, mode="exec")\n if not _PLL_TYPE_CHECK or not _PLL_TYPEGUARD_READY:\n return tree\n try:\n instrumented = _ast.parse(code, filename=filename, mode="exec")\n _pll_typeguard_transformer().visit(instrumented)\n _PllTopLevelAnnAssign().visit(instrumented)\n _ast.fix_missing_locations(instrumented)\n compile(instrumented, filename, "exec")\n return instrumented\n except BaseException:\n return tree\n\n\nclass _PllTopLevelExprWrapper(_ast.NodeTransformer):\n """Wrap module-level expression statements so they auto-display.\n\n Skips the conventional module docstring (a string literal as the first\n statement) and bare `None` / `...` constants which are usually noise.\n """\n\n def visit_Module(self, node):\n new_body = []\n for i, stmt in enumerate(node.body):\n if isinstance(stmt, _ast.Expr) and not _pll_should_skip_expr(stmt, i):\n call = _ast.Call(\n func=_ast.Name(id="_pll_show_top_level", ctx=_ast.Load()),\n args=[stmt.value],\n keywords=[],\n )\n wrapped = _ast.Expr(value=call)\n _ast.copy_location(wrapped, stmt)\n _ast.fix_missing_locations(wrapped)\n new_body.append(wrapped)\n else:\n new_body.append(stmt)\n node.body = new_body\n return node\n\n\ndef _pll_should_skip_expr(stmt, index):\n value = stmt.value\n if isinstance(value, _ast.Constant):\n if index == 0 and isinstance(value.value, str):\n return True # module docstring\n if value.value is None or value.value is Ellipsis:\n return True\n return False\n\n\n# -----------------------------------------------------------------------------\n# REPL syntax check (codeop.compile_command in \'single\' mode)\n# -----------------------------------------------------------------------------\n\ndef _pll_repl_check(source):\n """Return whether \'source\' is a complete REPL input.\n\n Mirrors what CPython\'s interactive shell does: uses codeop.compile_command\n in \'single\' mode, which returns None for incomplete input (e.g. open\n parens, unfinished block) and raises SyntaxError for actually-broken code.\n """\n try:\n result = _codeop.compile_command(source, "<repl>", "single")\n except (SyntaxError, OverflowError, ValueError) as e:\n return {\n "status": "invalid",\n "error_type": type(e).__name__,\n "message": str(e),\n "lineno": getattr(e, "lineno", None) or 0,\n "offset": (e.offset - 1) if isinstance(e, SyntaxError) and e.offset else 0,\n }\n if result is None:\n return {"status": "incomplete"}\n return {"status": "complete"}\n\n\n# -----------------------------------------------------------------------------\n# Run a whole file (with output capture and traceback extraction)\n# -----------------------------------------------------------------------------\n\ndef _pll_extract_loc(tb_str, fallback_filename):\n line_no = None\n col = None\n for raw in reversed(tb_str.splitlines()):\n line = raw.strip()\n if line.startswith(\'File "\') and ", line " in line:\n try:\n rest = line.split(", line ", 1)[1]\n num_part = rest.split(",", 1)[0].strip()\n line_no = int(num_part)\n except Exception:\n line_no = None\n break\n if line_no is None and fallback_filename:\n for raw in tb_str.splitlines():\n if fallback_filename in raw and ", line " in raw:\n try:\n rest = raw.split(", line ", 1)[1]\n num_part = rest.split(",", 1)[0].strip()\n line_no = int(num_part)\n except Exception:\n pass\n break\n return line_no, col\n\n\ndef _pll_run_file(code, filename, session_key, level="raw"):\n stdout = _PllStream("stdout")\n stderr = _PllStream("stderr")\n result = {\n "ok": False,\n "stdout": "",\n "stderr": "",\n "result_repr": None,\n "error_type": None,\n "error_message": None,\n "traceback": None,\n "line_number": None,\n "column": None,\n "displays": [],\n }\n # Each Run File starts with a clean slate for this session: discard any\n # names defined by a previous Run File of the same session or by REPL\n # exploration since then.\n _pll_apply_level(level)\n _pll_protect_import_path()\n user_globals = _pll_reset_session(session_key)\n _pll_displays.clear()\n try:\n tree = _pll_parse_and_instrument(code, filename)\n _PllTopLevelExprWrapper().visit(tree)\n _ast.fix_missing_locations(tree)\n compiled = compile(tree, filename, "exec")\n except SyntaxError as e:\n tb_text = "".join(_tb_mod.format_exception(type(e), e, e.__traceback__))\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["traceback"] = tb_text\n result["line_number"] = e.lineno\n result["column"] = (e.offset - 1) if e.offset else None\n result["stdout"] = stdout.getvalue()\n result["stderr"] = stderr.getvalue()\n result["displays"] = list(_pll_displays)\n return result\n\n try:\n with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):\n exec(compiled, user_globals)\n result["ok"] = True\n except SystemExit:\n result["ok"] = True\n except BaseException as e:\n formatted = _pll_format_exception(e)\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["traceback"] = formatted\n line_no, col = _pll_extract_loc(formatted, filename)\n result["line_number"] = line_no\n result["column"] = col\n finally:\n result["stdout"] = stdout.getvalue()\n result["stderr"] = stderr.getvalue()\n result["displays"] = list(_pll_displays)\n return result\n\n\n# -----------------------------------------------------------------------------\n# Tests (pytest, same-file test_* / Test* collection)\n# -----------------------------------------------------------------------------\n\ndef _pll_has_tests(code):\n """Return True if `code` looks like it contains pytest tests.\n\n Mirrors pytest\'s default collection: module-level `test_*` functions\n and `test_*` methods on `Test*` classes. Used so we can skip loading\n pytest for files that have no tests.\n """\n try:\n tree = _ast.parse(code)\n except (SyntaxError, ValueError):\n return False\n for node in tree.body:\n if isinstance(node, (_ast.FunctionDef, _ast.AsyncFunctionDef)):\n if node.name.startswith("test_"):\n return True\n elif isinstance(node, _ast.ClassDef) and node.name.startswith("Test"):\n for item in node.body:\n if isinstance(item, (_ast.FunctionDef, _ast.AsyncFunctionDef)):\n if item.name.startswith("test_"):\n return True\n return False\n\n\n_PLL_TRACE_SKIP = (\n _PLL_VENDOR_DIR,\n "_pytest",\n "pluggy",\n "site-packages",\n "/lib/python",\n "get_terminal_writer",\n "pyio.py",\n "pathlib",\n)\n\n\ndef _pll_is_internal_frame(line):\n return any(tok in line for tok in _PLL_TRACE_SKIP)\n\n\ndef _pll_fix_ast_ranges(tree):\n """Make rewritten asserts compile on Python 3.12+.\n\n `rewrite_asserts` can leave `end_lineno` < `lineno` on injected nodes.\n CPython then raises `ValueError: AST node line range (...) is not valid`.\n """\n for child in _ast.walk(tree):\n lineno = getattr(child, "lineno", None)\n end_lineno = getattr(child, "end_lineno", None)\n if lineno is not None and end_lineno is not None and end_lineno < lineno:\n child.end_lineno = lineno\n col = getattr(child, "col_offset", None)\n end_col = getattr(child, "end_col_offset", None)\n if (\n col is not None\n and end_col is not None\n and lineno is not None\n and end_lineno is not None\n and lineno == end_lineno\n and end_col < col\n ):\n child.end_col_offset = col\n\n\ndef _pll_test_locations(tree):\n """Map collected test names to 1-based source lines."""\n locs = {}\n for node in tree.body:\n if isinstance(node, (_ast.FunctionDef, _ast.AsyncFunctionDef)):\n if node.name.startswith("test_"):\n locs[node.name] = node.lineno\n elif isinstance(node, _ast.ClassDef) and node.name.startswith("Test"):\n for item in node.body:\n if isinstance(item, (_ast.FunctionDef, _ast.AsyncFunctionDef)):\n if item.name.startswith("test_"):\n locs[node.name + "::" + item.name] = item.lineno\n return locs\n\n\ndef _pll_friendly_assert_message(exc, tb_text):\n """Short explanation of a failed assert, with pytest internals stripped."""\n msg = str(exc).strip()\n lines = []\n for raw in msg.split("\\n"):\n stripped = raw.strip()\n if not stripped:\n continue\n if _pll_is_internal_frame(stripped):\n continue\n if stripped.startswith("+"):\n stripped = stripped.lstrip("+ ").strip()\n lines.append(stripped)\n if lines:\n return "\\n".join(lines)\n for raw in reversed((tb_text or "").splitlines()):\n stripped = raw.strip()\n if stripped.startswith("assert "):\n return stripped\n return "This test failed."\n\n\ndef _pll_is_async(fn):\n try:\n import inspect\n return inspect.iscoroutinefunction(fn)\n except Exception:\n return False\n\n\ndef _pll_call_test(fn):\n """Run one test function. Returns (outcome, message, stdout)."""\n import io\n\n buf = io.StringIO()\n try:\n with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):\n if _pll_is_async(fn):\n return (\n "error",\n "async tests are not supported.",\n buf.getvalue().strip() or None,\n )\n fn()\n return ("passed", None, buf.getvalue().strip() or None)\n except AssertionError as e:\n tb_text = "".join(_tb_mod.format_exception(type(e), e, e.__traceback__))\n return (\n "failed",\n _pll_friendly_assert_message(e, tb_text),\n buf.getvalue().strip() or None,\n )\n except BaseException as e:\n name = type(e).__name__\n if name == "Skipped":\n return ("skipped", str(e).strip() or None, buf.getvalue().strip() or None)\n return (\n "error",\n name + ": " + (str(e) or "this test raised an exception."),\n buf.getvalue().strip() or None,\n )\n\n\ndef _pll_iter_tests(ns):\n """Yield (name, callable) for pytest-style tests in namespace `ns`."""\n items = list(ns.items())\n for name, obj in items:\n if name.startswith("test_") and callable(obj) and not isinstance(obj, type):\n yield name, obj\n for name, obj in items:\n if not (isinstance(obj, type) and name.startswith("Test")):\n continue\n # pytest skips classes with a custom constructor.\n try:\n obj()\n except TypeError:\n continue\n for meth_name in dir(obj):\n if not meth_name.startswith("test_"):\n continue\n attr = getattr(obj, meth_name, None)\n if not callable(attr):\n continue\n\n def _bound(cls=obj, method=meth_name):\n return getattr(cls(), method)()\n\n yield name + "::" + meth_name, _bound\n\n\ndef _pll_run_tests(code, filename, level="raw"):\n """Run same-file tests (`test_*` / `Test*`) in an isolated namespace.\n\n Uses pytest only to rewrite assertions so failures show `assert 4 == 5`\n instead of an empty AssertionError. Does **not** call `pytest.main()`,\n which is not safe to invoke repeatedly in one Pyodide interpreter.\n """\n display_name = filename.rsplit("/", 1)[-1].rsplit("\\\\", 1)[-1] or "user_script.py"\n stdout = _PllStream("stdout")\n stderr = _PllStream("stderr")\n result = {\n "ok": False,\n "internal_error": False,\n "passed": 0,\n "failed": 0,\n "skipped": 0,\n "errors": 0,\n "tests": [],\n "stdout": "",\n "stderr": "",\n "error_type": None,\n "error_message": None,\n "traceback": None,\n "line_number": None,\n "column": None,\n "displays": [],\n }\n _pll_displays.clear()\n _pll_apply_level(level)\n _pll_protect_import_path()\n\n try:\n tree = _pll_parse_and_instrument(code, display_name)\n except SyntaxError as e:\n result["internal_error"] = True\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["line_number"] = e.lineno\n result["column"] = (e.offset - 1) if e.offset else None\n return result\n\n locs = _pll_test_locations(tree)\n try:\n from _pytest.assertion.rewrite import rewrite_asserts\n rewrite_asserts(tree, code.encode("utf-8"), module_path=display_name)\n # Do not call ast.fix_missing_locations here: it copies parent\n # positions onto pytest\'s injected nodes and yields ranges that\n # Python 3.12+ rejects (`end_lineno` < `lineno`).\n _pll_fix_ast_ranges(tree)\n compiled = compile(tree, display_name, "exec")\n except Exception:\n # Assert rewriting failed; keep the type instrumentation.\n compiled = compile(tree, display_name, "exec")\n\n ns = dict(_pll_initial_globals)\n ns["__name__"] = "__pll_test__"\n ns["__file__"] = display_name\n\n try:\n with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):\n exec(compiled, ns)\n except BaseException as e:\n formatted = _pll_format_exception(e)\n result["internal_error"] = True\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["traceback"] = formatted\n line_no, col = _pll_extract_loc(formatted, display_name)\n result["line_number"] = line_no\n result["column"] = col\n result["stdout"] = stdout.getvalue()\n result["stderr"] = stderr.getvalue()\n result["displays"] = list(_pll_displays)\n return result\n\n rows = []\n passed = failed = errors = skipped = 0\n for name, fn in _pll_iter_tests(ns):\n outcome, message, cap = _pll_call_test(fn)\n if outcome == "passed":\n passed += 1\n elif outcome == "failed":\n failed += 1\n elif outcome == "skipped":\n skipped += 1\n else:\n errors += 1\n rows.append({\n "name": name,\n "outcome": outcome,\n "line_number": locs.get(name),\n "message": message,\n "stdout": cap,\n })\n\n result["passed"] = passed\n result["failed"] = failed\n result["skipped"] = skipped\n result["errors"] = errors\n result["tests"] = rows\n result["ok"] = failed == 0 and errors == 0\n result["stdout"] = stdout.getvalue()\n result["stderr"] = stderr.getvalue()\n result["displays"] = list(_pll_displays)\n return result\n\n\n# -----------------------------------------------------------------------------\n# REPL-style eval (statements + last-expression value)\n# -----------------------------------------------------------------------------\n\ndef _pll_repl_eval(code, session_key, level="raw"):\n stdout = _PllStream("stdout")\n stderr = _PllStream("stderr")\n result = {\n "ok": False,\n "stdout": "",\n "stderr": "",\n "result_repr": None,\n "error_type": None,\n "error_message": None,\n "traceback": None,\n "line_number": None,\n "column": None,\n "displays": [],\n }\n _pll_displays.clear()\n _pll_apply_level(level)\n _pll_protect_import_path()\n user_globals = _pll_get_session(session_key)\n filename = "<repl>"\n try:\n tree = _pll_parse_and_instrument(code, filename)\n except SyntaxError as e:\n formatted = _pll_format_exception(e)\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["traceback"] = formatted\n result["line_number"] = e.lineno\n result["column"] = (e.offset - 1) if e.offset else None\n return result\n\n last_expr = None\n if tree.body and isinstance(tree.body[-1], _ast.Expr):\n last_expr = tree.body[-1]\n tree.body = tree.body[:-1]\n\n try:\n with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):\n if tree.body:\n compiled_stmts = compile(tree, filename, "exec")\n exec(compiled_stmts, user_globals)\n if last_expr is not None:\n expr_module = _ast.Expression(body=last_expr.value)\n compiled_expr = compile(expr_module, filename, "eval")\n value = eval(compiled_expr, user_globals)\n if value is not None:\n payload = _pll_extract_display(value)\n if payload is not None:\n _pll_push(payload)\n else:\n result["result_repr"] = repr(value)\n result["ok"] = True\n except SystemExit:\n result["ok"] = True\n except BaseException as e:\n formatted = _pll_format_exception(e)\n result["error_type"] = type(e).__name__\n result["error_message"] = str(e)\n result["traceback"] = formatted\n line_no, col = _pll_extract_loc(formatted, filename)\n result["line_number"] = line_no\n result["column"] = col\n finally:\n result["stdout"] = stdout.getvalue()\n result["stderr"] = stderr.getvalue()\n result["displays"] = list(_pll_displays)\n return result\n\n\n# =============================================================================\n# Static analysis (beginner / intermediate level checks)\n# =============================================================================\n#\n# Per-level rules:\n#\n# beginner:\n# 1. Shadowing: a binding whose name appears in any enclosing\n# scope or is the name of a Python built-in.\n# 2. Reassignment: a name bound more than once within the *same*\n# scope. Suppressed for names already flagged as\n# shadowing in that scope (fix the shadow first).\n# 3. Disallowed kw: `global` and `nonlocal` statements.\n#\n# intermediate:\n# 1. Shadowing: same as beginner.\n# 2. Reassignment: only flagged at module scope. Function/lambda/\n# class/comprehension scopes are allowed to rebind,\n# which is what enables for-loop accumulator\n# patterns (e.g. `total = 0; for x in xs: total += x`\n# inside `def`).\n# 3. Disallowed kw: `global` and `nonlocal` statements.\n#\n# advanced:\n# No checks. Full Python.\n#\n# A "scope" is one of: module, function (incl. async), lambda, class,\n# comprehension/generator. We model these explicitly because Python 3\n# comprehensions have their own scope.\n\n\n_PLL_SCOPE_FUNC = (_ast.FunctionDef, _ast.AsyncFunctionDef)\n_PLL_SCOPE_COMP = (_ast.ListComp, _ast.SetComp, _ast.DictComp, _ast.GeneratorExp)\n\n# Attributes `dir()` reports for the `builtins` *module object* itself. They\n# are not built-in functions, so reporting them as shadowed would be a false\n# positive with misleading wording ("`__name__` is the name of a Python\n# built-in"). Everything else `dir()` returns is a real builtin and is worth\n# flagging, including the dunder ones like `__import__`.\n_PLL_BUILTINS_MODULE_META = frozenset(\n ("__doc__", "__loader__", "__name__", "__package__", "__spec__")\n)\n\n\nclass _PllScope:\n __slots__ = ("node", "kind", "parent", "bindings")\n\n def __init__(self, node, kind, parent):\n self.node = node\n self.kind = kind # "module" | "function" | "lambda" | "class" | "comprehension"\n self.parent = parent # _PllScope | None\n self.bindings = {} # name -> [(lineno, col, kind), ...]\n\n\ndef _pll_arg_names(args):\n """All argument names (with positions) on an ast.arguments node."""\n out = []\n posonly = list(getattr(args, "posonlyargs", []) or [])\n for a in posonly + list(args.args) + list(args.kwonlyargs):\n out.append((a.arg, a.lineno, a.col_offset))\n if args.vararg:\n out.append((args.vararg.arg, args.vararg.lineno, args.vararg.col_offset))\n if args.kwarg:\n out.append((args.kwarg.arg, args.kwarg.lineno, args.kwarg.col_offset))\n return out\n\n\nclass _PllScopeBuilder:\n """Walk a module AST and build a list of scopes with their bindings.\n\n A binding is collected in the *enclosing* scope of the syntactic node\n (e.g. a `def f` adds `f` to the surrounding scope and creates a new\n function scope for its body). Comprehensions, lambdas, and classes\n each open their own scope.\n """\n\n def __init__(self):\n self.scopes = []\n\n def build(self, tree):\n module = _PllScope(tree, "module", None)\n self.scopes.append(module)\n for stmt in tree.body:\n self._walk(stmt, module)\n return module\n\n def _add(self, scope, name, lineno, col, kind):\n scope.bindings.setdefault(name, []).append((lineno, col, kind))\n\n def _add_target(self, target, scope, kind):\n if isinstance(target, _ast.Name):\n self._add(scope, target.id, target.lineno, target.col_offset, kind)\n elif isinstance(target, (_ast.Tuple, _ast.List)):\n for elt in target.elts:\n self._add_target(elt, scope, kind)\n elif isinstance(target, _ast.Starred):\n self._add_target(target.value, scope, kind)\n else:\n # Subscript/Attribute targets are not name bindings.\n self._walk(target, scope)\n\n def _walk(self, node, scope):\n # --- Binding-creating statements ---------------------------------\n if isinstance(node, _ast.Assign):\n for t in node.targets:\n self._add_target(t, scope, "assign")\n self._walk(node.value, scope)\n return\n if isinstance(node, _ast.AnnAssign):\n if isinstance(node.target, _ast.Name):\n self._add(scope, node.target.id, node.target.lineno, node.target.col_offset, "annassign")\n else:\n self._walk(node.target, scope)\n if node.annotation is not None:\n self._walk(node.annotation, scope)\n if node.value is not None:\n self._walk(node.value, scope)\n return\n if isinstance(node, _ast.AugAssign):\n if isinstance(node.target, _ast.Name):\n self._add(scope, node.target.id, node.target.lineno, node.target.col_offset, "augassign")\n else:\n self._walk(node.target, scope)\n self._walk(node.value, scope)\n return\n if isinstance(node, (_ast.For, _ast.AsyncFor)):\n self._add_target(node.target, scope, "for")\n self._walk(node.iter, scope)\n for s in node.body:\n self._walk(s, scope)\n for s in node.orelse:\n self._walk(s, scope)\n return\n if isinstance(node, (_ast.With, _ast.AsyncWith)):\n for item in node.items:\n self._walk(item.context_expr, scope)\n if item.optional_vars is not None:\n self._add_target(item.optional_vars, scope, "with")\n for s in node.body:\n self._walk(s, scope)\n return\n if isinstance(node, _ast.Import):\n for alias in node.names:\n name = alias.asname or alias.name.split(".", 1)[0]\n self._add(scope, name, node.lineno, node.col_offset, "import")\n return\n if isinstance(node, _ast.ImportFrom):\n for alias in node.names:\n name = alias.asname or alias.name\n self._add(scope, name, node.lineno, node.col_offset, "importfrom")\n return\n if isinstance(node, _ast.NamedExpr):\n # Walrus :=\n if isinstance(node.target, _ast.Name):\n self._add(scope, node.target.id, node.target.lineno, node.target.col_offset, "walrus")\n self._walk(node.value, scope)\n return\n if isinstance(node, (_ast.Global, _ast.Nonlocal)):\n # These don\'t create bindings.\n return\n\n # --- Scope-introducing nodes -------------------------------------\n if isinstance(node, _PLL_SCOPE_FUNC):\n # Function name binds in the OUTER scope.\n self._add(scope, node.name, node.lineno, node.col_offset, "functiondef")\n for d in node.decorator_list:\n self._walk(d, scope)\n for d in node.args.defaults:\n self._walk(d, scope)\n for d in (node.args.kw_defaults or []):\n if d is not None:\n self._walk(d, scope)\n inner = _PllScope(node, "function", scope)\n self.scopes.append(inner)\n for name, lineno, col in _pll_arg_names(node.args):\n self._add(inner, name, lineno, col, "argument")\n for s in node.body:\n self._walk(s, inner)\n return\n if isinstance(node, _ast.Lambda):\n for d in node.args.defaults:\n self._walk(d, scope)\n for d in (node.args.kw_defaults or []):\n if d is not None:\n self._walk(d, scope)\n inner = _PllScope(node, "lambda", scope)\n self.scopes.append(inner)\n for name, lineno, col in _pll_arg_names(node.args):\n self._add(inner, name, lineno, col, "argument")\n self._walk(node.body, inner)\n return\n if isinstance(node, _ast.ClassDef):\n self._add(scope, node.name, node.lineno, node.col_offset, "classdef")\n for d in node.decorator_list:\n self._walk(d, scope)\n for b in node.bases:\n self._walk(b, scope)\n for kw in node.keywords:\n self._walk(kw.value, scope)\n inner = _PllScope(node, "class", scope)\n self.scopes.append(inner)\n for s in node.body:\n self._walk(s, inner)\n return\n if isinstance(node, _PLL_SCOPE_COMP):\n inner = _PllScope(node, "comprehension", scope)\n self.scopes.append(inner)\n # Per Python semantics: outermost iter is evaluated in the OUTER\n # scope, everything else inside the comp scope.\n for i, gen in enumerate(node.generators):\n if i == 0:\n self._walk(gen.iter, scope)\n else:\n self._walk(gen.iter, inner)\n self._add_target(gen.target, inner, "comprehension")\n for cond in gen.ifs:\n self._walk(cond, inner)\n if isinstance(node, _ast.DictComp):\n self._walk(node.key, inner)\n self._walk(node.value, inner)\n else:\n self._walk(node.elt, inner)\n return\n\n # --- Default: descend in the same scope --------------------------\n for child in _ast.iter_child_nodes(node):\n self._walk(child, scope)\n\n\nclass _PllKeywordVisitor(_ast.NodeVisitor):\n """Collect every `global` / `nonlocal` statement in a tree.\n\n Each entry is `(keyword, lineno, col_offset, names)` where `keyword`\n is the literal string "global" or "nonlocal" and `names` is the list\n of identifiers the statement applies to.\n """\n\n def __init__(self):\n self.found = []\n\n def visit_Global(self, node):\n self.found.append(("global", node.lineno, node.col_offset, list(node.names)))\n self.generic_visit(node)\n\n def visit_Nonlocal(self, node):\n self.found.append(("nonlocal", node.lineno, node.col_offset, list(node.names)))\n self.generic_visit(node)\n\n\ndef _pll_session_bound_names(session_key):\n """User-defined names already bound in a session (for REPL checks).\n\n Baseline names from `_pll_initial_globals` (image primitives, etc.) are\n skipped unless the user rebound them, so prompt analysis matches file\n analysis for the same snippet.\n """\n g = _pll_sessions.get(session_key)\n if not g:\n return []\n names = []\n for n in g:\n if n.startswith("_") or n in ("__builtins__", "__name__", "__doc__"):\n continue\n if n in _pll_initial_globals and g[n] is _pll_initial_globals[n]:\n continue\n names.append(n)\n return names\n\n\ndef _pll_static_analyze(code, level, filename, session_key=None):\n """Run static checks for `level` over `code` and return findings.\n\n Returns a list of dicts. Each dict has at minimum:\n id, error_type, message, line_number, column, name_token, scope_kind.\n\n If `session_key` is set, names already bound in that session are treated\n as existing module-level bindings. That way a `#level beginner` prompt cannot\n reassign a name the file (or an earlier prompt line) already defined.\n """\n if level not in ("beginner", "intermediate"):\n return []\n try:\n tree = _ast.parse(code, filename=filename)\n except SyntaxError:\n # Let the runtime path surface SyntaxErrors with their normal flow.\n return []\n\n findings = []\n builder = _PllScopeBuilder()\n builder.build(tree)\n if session_key:\n module = builder.scopes[0]\n for name in _pll_session_bound_names(session_key):\n locs = module.bindings.setdefault(name, [])\n locs.insert(0, (0, 0, "preexisting"))\n builtins_set = set(dir(_builtins_mod)) - _PLL_BUILTINS_MODULE_META\n\n # Whether we flag reassignment in `scope` at this level. At beginner,\n # we flag everywhere; at intermediate, only at module scope so that\n # function-local accumulator patterns (`total = 0; for x in xs:\n # total += x`) work.\n def reassignment_active(scope_kind):\n if level == "beginner":\n return True\n if level == "intermediate":\n return scope_kind == "module"\n return False\n\n for scope in builder.scopes:\n # Names visible from any enclosing scope, paired with the *nearest*\n # outer binding\'s location and scope kind. We walk parents inner-to-\n # outer and refuse to overwrite, so the closest enclosing binding wins\n # (which is the one Python\'s lookup rules would resolve to).\n enclosing = {} # name -> (lineno, col, scope_kind)\n cur = scope.parent\n while cur is not None:\n for outer_name, outer_locs in cur.bindings.items():\n if outer_name in enclosing:\n continue\n outer_first = outer_locs[0]\n enclosing[outer_name] = (outer_first[0], outer_first[1], cur.kind)\n cur = cur.parent\n\n shadowed_in_scope = set()\n\n # ---- Shadowing first ----\n for name, locs in scope.bindings.items():\n first_loc = locs[0]\n if name in enclosing:\n shadowed_in_scope.add(name)\n outer = enclosing[name]\n findings.append({\n "id": "shadowing",\n "error_type": "Shadowing",\n "message": "`%s` is already defined in an outer scope" % name,\n "line_number": first_loc[0],\n "column": first_loc[1],\n "name_token": name,\n "scope_kind": scope.kind,\n "outer_line_number": outer[0],\n "outer_column": outer[1],\n "outer_scope_kind": outer[2],\n })\n elif name in builtins_set:\n shadowed_in_scope.add(name)\n findings.append({\n "id": "shadowing-builtin",\n "error_type": "Shadowing",\n "message": "`%s` is the name of a Python built-in" % name,\n "line_number": first_loc[0],\n "column": first_loc[1],\n "name_token": name,\n "scope_kind": scope.kind,\n })\n\n # ---- Then reassignment (skip names already shadow-flagged) ----\n if reassignment_active(scope.kind):\n for name, locs in scope.bindings.items():\n if name in shadowed_in_scope:\n continue\n if len(locs) > 1:\n second_loc = locs[1]\n first_loc = locs[0]\n findings.append({\n "id": "reassignment",\n "error_type": "Reassignment",\n "message": "`%s` is assigned more than once in this scope" % name,\n "line_number": second_loc[0],\n "column": second_loc[1],\n "name_token": name,\n "scope_kind": scope.kind,\n "first_line_number": first_loc[0],\n "first_column": first_loc[1],\n })\n\n # ---- `global` / `nonlocal` keyword check ----\n # Both are disallowed at beginner and intermediate. We emit one finding\n # per declaration (not per name) so a `global x, y` produces a single\n # diagnostic on that line.\n kw_visitor = _PllKeywordVisitor()\n kw_visitor.visit(tree)\n for keyword, lineno, col, names in kw_visitor.found:\n primary = names[0] if names else ""\n findings.append({\n "id": "disallowed-keyword",\n "error_type": "DisallowedKeyword",\n "message": "`%s` is not allowed at the %s level" % (keyword, level),\n "line_number": lineno,\n "column": col,\n "name_token": primary,\n "scope_kind": "function",\n "keyword": keyword,\n "names": list(names),\n })\n\n findings.sort(key=lambda f: (f["line_number"] or 0, f["column"] or 0))\n return findings\n';
165
+
166
+ // src/common/imageLib.py
167
+ var imageLib_default = '# PLL image library.\n#\n# A small, immutable, HtDP-style image library inspired by Racket\'s\n# `2htdp/image` and Pyret\'s `image-lib`. Every primitive and combinator\n# returns a new `Image`; nothing mutates. Each `Image` knows how to render\n# itself as an SVG fragment positioned in a parent box.\n#\n# This module is loaded into Pyodide alongside `pyodideBootstrap.py`. The\n# bootstrap then injects the public names directly into the user\'s module\n# globals so a beginner can write `circle(50, "solid", "red")` with no\n# import boilerplate (matching the HtDP/Pyret experience).\n\nimport math as _math\n\n\n# -----------------------------------------------------------------------------\n# Color handling\n# -----------------------------------------------------------------------------\n\ndef _pll_color_to_css(color):\n """Convert a PLL color value to an SVG/CSS color string.\n\n Accepts:\n - a string CSS name or hex ("red", "#ff0000")\n - an (r, g, b) tuple/list of integers 0..255\n - an (r, g, b, a) tuple/list, a in 0..1 or 0..255\n """\n if isinstance(color, str):\n return color\n if isinstance(color, (tuple, list)):\n if len(color) == 3:\n r, g, b = color\n return "rgb(%d, %d, %d)" % (int(r), int(g), int(b))\n if len(color) == 4:\n r, g, b, a = color\n if a > 1:\n a = a / 255.0\n return "rgba(%d, %d, %d, %g)" % (int(r), int(g), int(b), a)\n return str(color)\n\n\ndef _pll_xml_escape(text):\n return (\n text.replace("&", "&amp;")\n .replace("<", "&lt;")\n .replace(">", "&gt;")\n .replace(\'"\', "&quot;")\n .replace("\'", "&apos;")\n )\n\n\n# -----------------------------------------------------------------------------\n# Image base class\n# -----------------------------------------------------------------------------\n#\n# Each Image has:\n# width / height -> bounding box in pixels\n# _render_body(x, y) -> SVG fragment positioned at (x, y) in\n# its parent\'s coordinate system\n# to_svg() -> standalone <svg> document\n# _pll_image_data() -> dict the host uses to display the image\n#\n\nclass Image:\n """Base class for all PLL images. Don\'t instantiate directly."""\n\n @property\n def width(self):\n raise NotImplementedError\n\n @property\n def height(self):\n raise NotImplementedError\n\n def _render_body(self, x, y):\n raise NotImplementedError\n\n def to_svg(self):\n body = self._render_body(0, 0)\n # Use ceil to give a tiny bit of room so antialiased edges aren\'t clipped.\n w = max(1, int(_math.ceil(self.width)))\n h = max(1, int(_math.ceil(self.height)))\n return (\n \'<svg xmlns="http://www.w3.org/2000/svg" \'\n \'width="%d" height="%d" \'\n \'viewBox="0 0 %d %d" \'\n \'shape-rendering="geometricPrecision">%s</svg>\'\n ) % (w, h, w, h, body)\n\n def _pll_image_data(self):\n return {\n "type": "svg",\n "width": int(_math.ceil(self.width)),\n "height": int(_math.ceil(self.height)),\n "data": self.to_svg(),\n }\n\n def __repr__(self):\n return "<Image %dx%d>" % (\n int(_math.ceil(self.width)),\n int(_math.ceil(self.height)),\n )\n\n\n# -----------------------------------------------------------------------------\n# Primitive shapes\n# -----------------------------------------------------------------------------\n\ndef _pll_paint_attrs(mode, color):\n """Return SVG paint attributes for a `mode` ("solid" / "outline") shape."""\n css = _pll_color_to_css(color)\n if mode == "outline":\n return \'fill="none" stroke="%s" stroke-width="2"\' % css\n # default: solid\n return \'fill="%s"\' % css\n\n\nclass _Circle(Image):\n def __init__(self, radius, mode, color):\n self._radius = float(radius)\n self._mode = mode\n self._color = color\n\n @property\n def width(self):\n return self._radius * 2\n\n @property\n def height(self):\n return self._radius * 2\n\n def _render_body(self, x, y):\n cx = x + self._radius\n cy = y + self._radius\n return \'<circle cx="%g" cy="%g" r="%g" %s />\' % (\n cx, cy, self._radius, _pll_paint_attrs(self._mode, self._color),\n )\n\n\nclass _Rectangle(Image):\n def __init__(self, width, height, mode, color):\n self._w = float(width)\n self._h = float(height)\n self._mode = mode\n self._color = color\n\n @property\n def width(self):\n return self._w\n\n @property\n def height(self):\n return self._h\n\n def _render_body(self, x, y):\n return \'<rect x="%g" y="%g" width="%g" height="%g" %s />\' % (\n x, y, self._w, self._h,\n _pll_paint_attrs(self._mode, self._color),\n )\n\n\nclass _Ellipse(Image):\n def __init__(self, width, height, mode, color):\n self._w = float(width)\n self._h = float(height)\n self._mode = mode\n self._color = color\n\n @property\n def width(self):\n return self._w\n\n @property\n def height(self):\n return self._h\n\n def _render_body(self, x, y):\n rx = self._w / 2.0\n ry = self._h / 2.0\n return \'<ellipse cx="%g" cy="%g" rx="%g" ry="%g" %s />\' % (\n x + rx, y + ry, rx, ry,\n _pll_paint_attrs(self._mode, self._color),\n )\n\n\nclass _Polygon(Image):\n """A polygon defined by points relative to its top-left bounding box."""\n\n def __init__(self, points, mode, color):\n # points: list of (px, py) within the local 0..w / 0..h box\n if not points:\n raise ValueError("polygon needs at least one point")\n xs = [p[0] for p in points]\n ys = [p[1] for p in points]\n self._w = max(xs) - min(xs)\n self._h = max(ys) - min(ys)\n self._origin_x = min(xs)\n self._origin_y = min(ys)\n self._points = points\n self._mode = mode\n self._color = color\n\n @property\n def width(self):\n return self._w\n\n @property\n def height(self):\n return self._h\n\n def _render_body(self, x, y):\n coords = " ".join(\n "%g,%g" % (x + (px - self._origin_x), y + (py - self._origin_y))\n for px, py in self._points\n )\n return \'<polygon points="%s" %s />\' % (\n coords, _pll_paint_attrs(self._mode, self._color),\n )\n\n\nclass _Line(Image):\n def __init__(self, dx, dy, color):\n self._dx = float(dx)\n self._dy = float(dy)\n self._color = color\n\n @property\n def width(self):\n return abs(self._dx) if abs(self._dx) > 1 else 1\n\n @property\n def height(self):\n return abs(self._dy) if abs(self._dy) > 1 else 1\n\n def _render_body(self, x, y):\n x1 = x + (0 if self._dx >= 0 else -self._dx)\n y1 = y + (0 if self._dy >= 0 else -self._dy)\n x2 = x1 + self._dx\n y2 = y1 + self._dy\n return \'<line x1="%g" y1="%g" x2="%g" y2="%g" stroke="%s" stroke-width="2" stroke-linecap="round" />\' % (\n x1, y1, x2, y2, _pll_color_to_css(self._color),\n )\n\n\nclass _Text(Image):\n """SVG text. Width is estimated; SVG handles actual glyph layout."""\n\n def __init__(self, text, size, color):\n self._text = str(text)\n self._size = float(size)\n self._color = color\n\n @property\n def width(self):\n # crude estimate, ~0.6em per char on average\n return max(1, len(self._text)) * self._size * 0.6\n\n @property\n def height(self):\n return self._size * 1.2\n\n def _render_body(self, x, y):\n # Position baseline so glyphs sit inside the bounding box.\n baseline = y + self._size\n return (\n \'<text x="%g" y="%g" font-family="sans-serif" font-size="%g" fill="%s" \'\n \'text-rendering="optimizeLegibility">%s</text>\'\n ) % (\n x, baseline, self._size,\n _pll_color_to_css(self._color),\n _pll_xml_escape(self._text),\n )\n\n\n# -----------------------------------------------------------------------------\n# Combinators\n# -----------------------------------------------------------------------------\n\n# -----------------------------------------------------------------------------\n# Alignment (HtDP\'s x-place / y-place)\n# -----------------------------------------------------------------------------\n\n_PLL_X_PLACES = ("left", "center", "middle", "right")\n_PLL_Y_PLACES = ("top", "center", "middle", "bottom")\n\n\ndef _pll_check_place(place, allowed, who):\n if place not in allowed:\n raise ValueError(\n "%s: expected one of %s, got %r"\n % (who, ", ".join(repr(p) for p in allowed), place)\n )\n return place\n\n\ndef _pll_offset(place, outer, inner):\n """Where a child of size `inner` sits inside a box of size `outer`."""\n if place in ("left", "top"):\n return 0.0\n if place in ("right", "bottom"):\n return outer - inner\n return (outer - inner) / 2.0\n\n\n_pll_clip_seq = 0\n\n\ndef _pll_next_clip_id():\n """Unique per element: two crops in one picture must not share an id."""\n global _pll_clip_seq\n _pll_clip_seq += 1\n return "pllclip%d" % _pll_clip_seq\n\n\ndef _pll_clipped(body, x, y, width, height):\n """`body`, with everything outside the given rectangle clipped away."""\n cid = _pll_next_clip_id()\n return (\n \'<defs><clipPath id="%s">\'\n \'<rect x="%g" y="%g" width="%g" height="%g"/>\'\n "</clipPath></defs>"\n \'<g clip-path="url(#%s)">%s</g>\'\n ) % (cid, x, y, width, height, cid, body)\n\n\nclass _Beside(Image):\n """Children left to right, aligned vertically by `y_place`."""\n\n def __init__(self, children, y_place="center"):\n self._children = list(children)\n self._y_place = y_place\n\n @property\n def width(self):\n return sum(c.width for c in self._children)\n\n @property\n def height(self):\n return max((c.height for c in self._children), default=0)\n\n def _render_body(self, x, y):\n out = []\n cx = x\n h = self.height\n for child in self._children:\n cy = y + _pll_offset(self._y_place, h, child.height)\n out.append(child._render_body(cx, cy))\n cx += child.width\n return "".join(out)\n\n\nclass _Above(Image):\n """Children top to bottom, aligned horizontally by `x_place`."""\n\n def __init__(self, children, x_place="center"):\n self._children = list(children)\n self._x_place = x_place\n\n @property\n def width(self):\n return max((c.width for c in self._children), default=0)\n\n @property\n def height(self):\n return sum(c.height for c in self._children)\n\n def _render_body(self, x, y):\n out = []\n cy = y\n w = self.width\n for child in self._children:\n cx = x + _pll_offset(self._x_place, w, child.width)\n out.append(child._render_body(cx, cy))\n cy += child.height\n return "".join(out)\n\n\nclass _Overlay(Image):\n """First arg is on top. Children aligned by (`x_place`, `y_place`)."""\n\n def __init__(self, children, x_place="center", y_place="center"):\n if not children:\n raise ValueError("overlay needs at least one image")\n self._children = list(children)\n self._x_place = x_place\n self._y_place = y_place\n\n @property\n def width(self):\n return max(c.width for c in self._children)\n\n @property\n def height(self):\n return max(c.height for c in self._children)\n\n def _render_body(self, x, y):\n # SVG draws in document order, so render bottom-up: last arg first.\n out = []\n w = self.width\n h = self.height\n for child in reversed(self._children):\n cx = x + _pll_offset(self._x_place, w, child.width)\n cy = y + _pll_offset(self._y_place, h, child.height)\n out.append(child._render_body(cx, cy))\n return "".join(out)\n\n\nclass _LayeredXY(Image):\n """`first` at the origin, `second` offset by (dx, dy).\n\n Negative offsets move `second` left / up, which grows the bounding box\n in that direction - so the composite\'s own origin shifts. The class\n absorbs that itself: it reports the union size and moves *both* children\n right / down by however far the box grew, so the protocol\'s "draw with\n your top-left at (x, y)" contract still holds for whoever renders it.\n Nothing else in the library needs to know about negative coordinates.\n """\n\n def __init__(self, first, dx, dy, second, first_on_top):\n self._first = first\n self._second = second\n self._dx = float(dx)\n self._dy = float(dy)\n self._first_on_top = first_on_top\n\n @property\n def width(self):\n left = min(0.0, self._dx)\n right = max(self._first.width, self._dx + self._second.width)\n return right - left\n\n @property\n def height(self):\n top = min(0.0, self._dy)\n bottom = max(self._first.height, self._dy + self._second.height)\n return bottom - top\n\n def _render_body(self, x, y):\n shift_x = -min(0.0, self._dx)\n shift_y = -min(0.0, self._dy)\n first = self._first._render_body(x + shift_x, y + shift_y)\n second = self._second._render_body(\n x + shift_x + self._dx, y + shift_y + self._dy\n )\n # Document order is painting order, so whatever is on top goes last.\n return (second + first) if self._first_on_top else (first + second)\n\n\nclass _Crop(Image):\n """The `width` x `height` region of `image` starting at (`x`, `y`)."""\n\n def __init__(self, x, y, width, height, image):\n if width < 0 or height < 0:\n raise ValueError("crop needs a width and height of at least 0")\n self._x = float(x)\n self._y = float(y)\n self._w = float(width)\n self._h = float(height)\n self._image = image\n\n @property\n def width(self):\n return self._w\n\n @property\n def height(self):\n return self._h\n\n def _render_body(self, x, y):\n # Shift the source so the requested region lands at (x, y), then\n # clip away everything outside it.\n body = self._image._render_body(x - self._x, y - self._y)\n return _pll_clipped(body, x, y, self._w, self._h)\n\n\nclass _PlaceImage(Image):\n """`image` centered at (cx, cy) on `scene`, cropped to the scene."""\n\n def __init__(self, image, cx, cy, scene):\n self._image = image\n self._cx = float(cx)\n self._cy = float(cy)\n self._scene = scene\n\n @property\n def width(self):\n return self._scene.width\n\n @property\n def height(self):\n return self._scene.height\n\n def _render_body(self, x, y):\n ix = x + self._cx - self._image.width / 2.0\n iy = y + self._cy - self._image.height / 2.0\n body = self._scene._render_body(x, y) + self._image._render_body(ix, iy)\n return _pll_clipped(body, x, y, self.width, self.height)\n\n\nclass _Frame(Image):\n """`image` with a thin outline around its bounding box."""\n\n def __init__(self, image):\n self._image = image\n\n @property\n def width(self):\n return self._image.width\n\n @property\n def height(self):\n return self._image.height\n\n def _render_body(self, x, y):\n # Half-pixel inset so a 1-unit stroke sits inside the bounding box\n # instead of straddling its edge.\n box = (\n \'<rect x="%g" y="%g" width="%g" height="%g" \'\n \'fill="none" stroke="black" stroke-width="1"/>\'\n ) % (\n x + 0.5,\n y + 0.5,\n max(0.0, self.width - 1.0),\n max(0.0, self.height - 1.0),\n )\n return self._image._render_body(x, y) + box\n\n\nclass _Rotate(Image):\n """Rotate `image` counter-clockwise by `angle` degrees (HtDP convention)."""\n\n def __init__(self, angle, image):\n self._angle = float(angle) % 360.0\n self._image = image\n\n @property\n def width(self):\n rad = _math.radians(self._angle)\n w = self._image.width\n h = self._image.height\n return abs(w * _math.cos(rad)) + abs(h * _math.sin(rad))\n\n @property\n def height(self):\n rad = _math.radians(self._angle)\n w = self._image.width\n h = self._image.height\n return abs(w * _math.sin(rad)) + abs(h * _math.cos(rad))\n\n def _render_body(self, x, y):\n # Place the unrotated child centered inside the rotated bounding box,\n # then rotate around its center. SVG\'s `rotate` is clockwise positive,\n # we want counter-clockwise positive, so we negate.\n cw = self._image.width\n ch = self._image.height\n bw = self.width\n bh = self.height\n ox = x + (bw - cw) / 2.0\n oy = y + (bh - ch) / 2.0\n body = self._image._render_body(0, 0)\n return (\n \'<g transform="translate(%g,%g) rotate(%g,%g,%g)">%s</g>\'\n ) % (ox, oy, -self._angle, cw / 2.0, ch / 2.0, body)\n\n\nclass _Scale(Image):\n def __init__(self, factor, image):\n self._fx = float(factor)\n self._fy = float(factor)\n self._image = image\n\n @property\n def width(self):\n return self._image.width * self._fx\n\n @property\n def height(self):\n return self._image.height * self._fy\n\n def _render_body(self, x, y):\n body = self._image._render_body(0, 0)\n return \'<g transform="translate(%g,%g) scale(%g,%g)">%s</g>\' % (\n x, y, self._fx, self._fy, body,\n )\n\n\nclass _Flip(Image):\n def __init__(self, image, horizontal):\n self._image = image\n self._horizontal = horizontal\n\n @property\n def width(self):\n return self._image.width\n\n @property\n def height(self):\n return self._image.height\n\n def _render_body(self, x, y):\n body = self._image._render_body(0, 0)\n if self._horizontal:\n tx = x + self.width\n return \'<g transform="translate(%g,%g) scale(-1,1)">%s</g>\' % (tx, y, body)\n ty = y + self.height\n return \'<g transform="translate(%g,%g) scale(1,-1)">%s</g>\' % (x, ty, body)\n\n\n# -----------------------------------------------------------------------------\n# Public API (exported into user globals by the bootstrap)\n# -----------------------------------------------------------------------------\n\ndef circle(radius, mode, color):\n """Solid or outline circle."""\n return _Circle(radius, mode, color)\n\n\ndef square(side, mode, color):\n """Square with the given side length."""\n return _Rectangle(side, side, mode, color)\n\n\ndef rectangle(width, height, mode, color):\n return _Rectangle(width, height, mode, color)\n\n\ndef ellipse(width, height, mode, color):\n return _Ellipse(width, height, mode, color)\n\n\ndef triangle(side, mode, color):\n """Equilateral triangle pointing up."""\n h = side * _math.sqrt(3) / 2.0\n points = [(side / 2.0, 0.0), (side, h), (0.0, h)]\n return _Polygon(points, mode, color)\n\n\ndef right_triangle(width, height, mode, color):\n """Right triangle with legs `width` (bottom) and `height` (right)."""\n points = [(0.0, height), (width, height), (width, 0.0)]\n return _Polygon(points, mode, color)\n\n\ndef regular_polygon(side, sides, mode, color):\n """Regular polygon with `sides` sides each `side` units long."""\n if sides < 3:\n raise ValueError("regular_polygon needs at least 3 sides")\n radius = side / (2 * _math.sin(_math.pi / sides))\n points = []\n for i in range(sides):\n # start at the top and go clockwise\n angle = -_math.pi / 2.0 + i * 2 * _math.pi / sides\n px = radius + radius * _math.cos(angle)\n py = radius + radius * _math.sin(angle)\n points.append((px, py))\n return _Polygon(points, mode, color)\n\n\ndef star(side, mode, color):\n """5-point star with the given outer "side" length."""\n return star_polygon(side, 5, 2, mode, color)\n\n\ndef star_polygon(side, points_count, step, mode, color):\n """An n-pointed star with the given inner step (e.g. 5/2 -> classic star)."""\n if points_count < 3 or step < 1:\n raise ValueError("invalid star_polygon arguments")\n radius = side / (2 * _math.sin(_math.pi / points_count))\n pts = []\n n = points_count * 2\n inner_radius = radius * _math.cos(step * _math.pi / points_count)\n for i in range(n):\n r = radius if i % 2 == 0 else inner_radius\n angle = -_math.pi / 2.0 + i * _math.pi / points_count\n px = radius + r * _math.cos(angle)\n py = radius + r * _math.sin(angle)\n pts.append((px, py))\n return _Polygon(pts, mode, color)\n\n\ndef line(dx, dy, color):\n """Line going `(dx, dy)` from its top-left anchor."""\n return _Line(dx, dy, color)\n\n\ndef text(value, size, color):\n return _Text(value, size, color)\n\n\ndef beside(*images):\n return _Beside(images)\n\n\ndef above(*images):\n return _Above(images)\n\n\ndef overlay(*images):\n return _Overlay(images)\n\n\ndef underlay(*images):\n """Like overlay, but first arg is on the bottom."""\n return _Overlay(list(reversed(images)))\n\n\ndef beside_align(y_place, *images):\n """Like `beside`, aligned by "top" / "center" / "bottom"."""\n return _Beside(images, _pll_check_place(y_place, _PLL_Y_PLACES, "beside_align"))\n\n\ndef above_align(x_place, *images):\n """Like `above`, aligned by "left" / "center" / "right"."""\n return _Above(images, _pll_check_place(x_place, _PLL_X_PLACES, "above_align"))\n\n\ndef overlay_align(x_place, y_place, *images):\n """Like `overlay`, with both axes aligned explicitly."""\n return _Overlay(\n images,\n _pll_check_place(x_place, _PLL_X_PLACES, "overlay_align"),\n _pll_check_place(y_place, _PLL_Y_PLACES, "overlay_align"),\n )\n\n\ndef underlay_align(x_place, y_place, *images):\n """Like `overlay_align`, but the first image is on the bottom."""\n return _Overlay(\n list(reversed(images)),\n _pll_check_place(x_place, _PLL_X_PLACES, "underlay_align"),\n _pll_check_place(y_place, _PLL_Y_PLACES, "underlay_align"),\n )\n\n\ndef overlay_xy(image1, dx, dy, image2):\n """`image1` on top; `image2` moved `dx` right and `dy` down from it.\n\n Negative offsets move `image2` left / up and the picture grows that way,\n so nothing is ever cut off.\n """\n return _LayeredXY(image1, dx, dy, image2, first_on_top=True)\n\n\ndef underlay_xy(image1, dx, dy, image2):\n """`image1` underneath; `image2` moved `dx` right and `dy` down."""\n return _LayeredXY(image1, dx, dy, image2, first_on_top=False)\n\n\ndef place_image(image, x, y, scene):\n """Put `image`\'s *center* at (x, y) on `scene`, cropped to the scene."""\n return _PlaceImage(image, x, y, scene)\n\n\ndef crop(x, y, width, height, image):\n """The `width` x `height` piece of `image` starting at (x, y)."""\n return _Crop(x, y, width, height, image)\n\n\ndef frame(image):\n """`image` with a thin outline around it, to show its bounding box."""\n return _Frame(image)\n\n\ndef empty_scene(width, height):\n """A blank white scene with an outline, to use with `place_image`."""\n return _Frame(_Rectangle(width, height, "solid", "white"))\n\n\ndef rotate(angle, image):\n return _Rotate(angle, image)\n\n\ndef scale(factor, image):\n return _Scale(factor, image)\n\n\ndef flip_horizontal(image):\n return _Flip(image, horizontal=True)\n\n\ndef flip_vertical(image):\n return _Flip(image, horizontal=False)\n\n\ndef image_width(image):\n return int(_math.ceil(image.width))\n\n\ndef image_height(image):\n return int(_math.ceil(image.height))\n\n\nempty_image = _Rectangle(0, 0, "solid", (0, 0, 0, 0))\n\n\n# Names exported into user globals by the bootstrap. Keep this list explicit\n# so we don\'t accidentally leak helpers (anything starting with `_` would\n# already be filtered, but being explicit avoids drift).\nPLL_IMAGE_EXPORTS = [\n "Image",\n "circle",\n "square",\n "rectangle",\n "ellipse",\n "triangle",\n "right_triangle",\n "regular_polygon",\n "star",\n "star_polygon",\n "line",\n "text",\n "beside",\n "above",\n "overlay",\n "underlay",\n "beside_align",\n "above_align",\n "overlay_align",\n "underlay_align",\n "overlay_xy",\n "underlay_xy",\n "place_image",\n "crop",\n "frame",\n "empty_scene",\n "rotate",\n "scale",\n "flip_horizontal",\n "flip_vertical",\n "image_width",\n "image_height",\n "empty_image",\n]\n';
168
+
169
+ // src/common/tableLib.py
170
+ var tableLib_default = `# PLL table library.
171
+ #
172
+ # A small, immutable, Pyret-style table abstraction. Internally a \`Table\`
173
+ # stores its data column-by-column (a dict of name -> list-of-values) so
174
+ # column transforms and aggregations are O(rows) and don't allocate row
175
+ # dicts unless asked. Every operation returns a new \`Table\`; nothing
176
+ # mutates.
177
+ #
178
+ # Public surface (all method-based, so beginners never collide with
179
+ # Python built-ins like \`filter\`, \`sum\`, \`min\`, \`max\`):
180
+ #
181
+ # Construction:
182
+ # table(columns, rows) list-of-rows constructor
183
+ # table_from_columns(d) dict-of-columns constructor
184
+ #
185
+ # Inspection:
186
+ # t.columns() -> list[str]
187
+ # t.length() / len(t) -> int
188
+ # t.column(name) -> list of values
189
+ # t.row(index) -> dict
190
+ # t.rows() -> list[dict]
191
+ #
192
+ # Functional ops (return Table):
193
+ # t.filter(predicate) keep rows where predicate(row) is truthy
194
+ # t.transform_column(name, fn) fn(value) -> new value
195
+ # t.add_column(name, vs_or_fn) vs_or_fn: list of values OR fn(row)
196
+ # t.select_columns(names)
197
+ # t.order_by(name, ascending=True)
198
+ # t.head(n=10)
199
+ # t.tail(n=10)
200
+ #
201
+ # Aggregations (return number):
202
+ # t.sum(name)
203
+ # t.mean(name)
204
+ # t.min(name)
205
+ # t.max(name)
206
+ # t.count() row count (alias for length)
207
+ #
208
+ # Charts (return Image-compatible objects that auto-display):
209
+ # t.bar_chart(x_name, y_name, title=None)
210
+ # t.scatter_chart(x_name, y_name, title=None)
211
+ # t.line_chart(x_name, y_name, title=None)
212
+ # t.histogram(name, bins=10, title=None)
213
+ #
214
+ # Escape hatch:
215
+ # t.to_pandas() -> pandas.DataFrame (lazy import)
216
+
217
+ import math as _math
218
+
219
+
220
+ # -----------------------------------------------------------------------------
221
+ # Cell formatting (for HTML display)
222
+ # -----------------------------------------------------------------------------
223
+
224
+ def _format_cell(value):
225
+ """Render a cell value to a short display string."""
226
+ if value is None:
227
+ return ""
228
+ if isinstance(value, bool):
229
+ return "True" if value else "False"
230
+ if isinstance(value, float):
231
+ if _math.isnan(value):
232
+ return "NaN"
233
+ if _math.isinf(value):
234
+ return "+inf" if value > 0 else "-inf"
235
+ if value == int(value) and abs(value) < 1e16:
236
+ return "%d" % int(value)
237
+ return "%g" % value
238
+ if isinstance(value, int):
239
+ return "%d" % value
240
+ if isinstance(value, str):
241
+ return value
242
+ return repr(value)
243
+
244
+
245
+ # -----------------------------------------------------------------------------
246
+ # The Table class
247
+ # -----------------------------------------------------------------------------
248
+
249
+ class Table:
250
+ __slots__ = ("_columns", "_data", "_length")
251
+
252
+ def __init__(self, columns, rows):
253
+ """Build a table from a list of column names and a list of rows.
254
+
255
+ Each row is a list/tuple aligned with \`columns\`. Rows may also be
256
+ dicts; missing keys default to None.
257
+ """
258
+ cols = list(columns)
259
+ if len(cols) != len(set(cols)):
260
+ raise ValueError("Duplicate column names: %r" % (cols,))
261
+
262
+ data = {c: [] for c in cols}
263
+ n = 0
264
+ for row in rows:
265
+ n += 1
266
+ if isinstance(row, dict):
267
+ for c in cols:
268
+ data[c].append(row.get(c))
269
+ else:
270
+ row_seq = list(row)
271
+ if len(row_seq) != len(cols):
272
+ raise ValueError(
273
+ "Row %d has %d values but the table has %d columns"
274
+ % (n - 1, len(row_seq), len(cols))
275
+ )
276
+ for c, v in zip(cols, row_seq):
277
+ data[c].append(v)
278
+
279
+ self._columns = cols
280
+ self._data = data
281
+ self._length = n
282
+
283
+ # ---- Internal constructor that skips validation ----
284
+
285
+ @classmethod
286
+ def _from_columns(cls, columns, data, length):
287
+ t = cls.__new__(cls)
288
+ t._columns = list(columns)
289
+ t._data = {c: list(data[c]) for c in t._columns}
290
+ t._length = length
291
+ return t
292
+
293
+ # ---- Inspection ----
294
+
295
+ def columns(self):
296
+ """Names of the table's columns."""
297
+ return list(self._columns)
298
+
299
+ def length(self):
300
+ """Number of rows in the table."""
301
+ return self._length
302
+
303
+ def __len__(self):
304
+ return self._length
305
+
306
+ def column(self, name):
307
+ """All values in \`name\` as a list."""
308
+ self._require_column(name)
309
+ return list(self._data[name])
310
+
311
+ def row(self, index):
312
+ """Row at \`index\` as a {column: value} dict."""
313
+ if index < 0 or index >= self._length:
314
+ raise IndexError(
315
+ "row index %d out of range (table has %d rows)"
316
+ % (index, self._length)
317
+ )
318
+ return {c: self._data[c][index] for c in self._columns}
319
+
320
+ def rows(self):
321
+ """All rows as a list of {column: value} dicts."""
322
+ return [self.row(i) for i in range(self._length)]
323
+
324
+ # ---- Functional ops ----
325
+
326
+ def filter(self, predicate):
327
+ """Keep rows where \`predicate(row_dict)\` is truthy."""
328
+ if not callable(predicate):
329
+ raise TypeError("filter expects a function, got %r" % (predicate,))
330
+ keep = []
331
+ for i in range(self._length):
332
+ row = {c: self._data[c][i] for c in self._columns}
333
+ if predicate(row):
334
+ keep.append(i)
335
+ new_data = {c: [self._data[c][i] for i in keep] for c in self._columns}
336
+ return Table._from_columns(self._columns, new_data, len(keep))
337
+
338
+ def transform_column(self, name, fn):
339
+ """Replace \`name\` with the result of \`fn(value)\` applied to each value."""
340
+ self._require_column(name)
341
+ if not callable(fn):
342
+ raise TypeError("transform_column expects a function, got %r" % (fn,))
343
+ new_data = {c: list(self._data[c]) for c in self._columns}
344
+ new_data[name] = [fn(v) for v in self._data[name]]
345
+ return Table._from_columns(self._columns, new_data, self._length)
346
+
347
+ def add_column(self, name, values_or_fn):
348
+ """Add a new column.
349
+
350
+ Pass a list of values aligned with the existing rows, or a function
351
+ that takes a row dict and returns the value for that row.
352
+ """
353
+ if name in self._data:
354
+ raise ValueError("Column %r already exists" % name)
355
+ if callable(values_or_fn):
356
+ new_values = []
357
+ for i in range(self._length):
358
+ row = {c: self._data[c][i] for c in self._columns}
359
+ new_values.append(values_or_fn(row))
360
+ else:
361
+ new_values = list(values_or_fn)
362
+ if len(new_values) != self._length:
363
+ raise ValueError(
364
+ "Column %r has %d values, but the table has %d rows"
365
+ % (name, len(new_values), self._length)
366
+ )
367
+ new_columns = self._columns + [name]
368
+ new_data = {c: list(self._data[c]) for c in self._columns}
369
+ new_data[name] = new_values
370
+ return Table._from_columns(new_columns, new_data, self._length)
371
+
372
+ def select_columns(self, names):
373
+ """Keep only the columns in \`names\`, in that order."""
374
+ names = list(names)
375
+ for n in names:
376
+ self._require_column(n)
377
+ new_data = {n: list(self._data[n]) for n in names}
378
+ return Table._from_columns(names, new_data, self._length)
379
+
380
+ def order_by(self, name, ascending=True):
381
+ """Sort rows by \`name\` (ascending by default)."""
382
+ self._require_column(name)
383
+ # Sort indices to keep all columns in lockstep.
384
+ order = sorted(
385
+ range(self._length),
386
+ key=lambda i: _sort_key(self._data[name][i]),
387
+ reverse=not ascending,
388
+ )
389
+ new_data = {c: [self._data[c][i] for i in order] for c in self._columns}
390
+ return Table._from_columns(self._columns, new_data, self._length)
391
+
392
+ def head(self, n=10):
393
+ """First \`n\` rows as a new table."""
394
+ return self._slice(0, min(n, self._length))
395
+
396
+ def tail(self, n=10):
397
+ """Last \`n\` rows as a new table."""
398
+ start = max(0, self._length - n)
399
+ return self._slice(start, self._length)
400
+
401
+ def _slice(self, start, end):
402
+ new_data = {c: self._data[c][start:end] for c in self._columns}
403
+ return Table._from_columns(self._columns, new_data, end - start)
404
+
405
+ # ---- Aggregations ----
406
+
407
+ def sum(self, name):
408
+ """Sum of all values in \`name\` (numeric column)."""
409
+ return _sum_numeric(self._numeric_column(name, "sum"))
410
+
411
+ def mean(self, name):
412
+ """Mean (average) of \`name\`."""
413
+ values = self._numeric_column(name, "mean")
414
+ if len(values) == 0:
415
+ raise ValueError("mean of empty column %r" % name)
416
+ return _sum_numeric(values) / len(values)
417
+
418
+ def min(self, name):
419
+ """Minimum value in \`name\`."""
420
+ self._require_column(name)
421
+ values = self._data[name]
422
+ if len(values) == 0:
423
+ raise ValueError("min of empty column %r" % name)
424
+ return min(values, key=_sort_key)
425
+
426
+ def max(self, name):
427
+ """Maximum value in \`name\`."""
428
+ self._require_column(name)
429
+ values = self._data[name]
430
+ if len(values) == 0:
431
+ raise ValueError("max of empty column %r" % name)
432
+ return max(values, key=_sort_key)
433
+
434
+ def count(self):
435
+ """Number of rows (alias for length)."""
436
+ return self._length
437
+
438
+ # ---- Charts ----
439
+
440
+ def bar_chart(self, x, y, title=None):
441
+ """Bar chart: one bar per row, x as label, y as height."""
442
+ self._require_column(x)
443
+ self._require_column(y)
444
+ labels = [_format_cell(v) for v in self._data[x]]
445
+ values = self._numeric_column(y, "bar_chart")
446
+ return _PllChart(_render_bar_chart(labels, values, x, y, title))
447
+
448
+ def scatter_chart(self, x, y, title=None):
449
+ """Scatter plot of x vs y (both numeric)."""
450
+ xs = self._numeric_column(x, "scatter_chart")
451
+ ys = self._numeric_column(y, "scatter_chart")
452
+ return _PllChart(_render_xy_chart(xs, ys, x, y, title, mode="scatter"))
453
+
454
+ def line_chart(self, x, y, title=None):
455
+ """Line chart of x vs y (both numeric, sorted by x)."""
456
+ xs = self._numeric_column(x, "line_chart")
457
+ ys = self._numeric_column(y, "line_chart")
458
+ # Sort by x so the line draws monotonically.
459
+ pairs = sorted(zip(xs, ys), key=lambda p: p[0])
460
+ sx = [p[0] for p in pairs]
461
+ sy = [p[1] for p in pairs]
462
+ return _PllChart(_render_xy_chart(sx, sy, x, y, title, mode="line"))
463
+
464
+ def histogram(self, name, bins=10, title=None):
465
+ """Histogram of \`name\` (numeric)."""
466
+ if bins < 1:
467
+ raise ValueError("bins must be >= 1")
468
+ values = self._numeric_column(name, "histogram")
469
+ return _PllChart(_render_histogram(values, bins, name, title))
470
+
471
+ # ---- Display protocol ----
472
+
473
+ def _pll_table_data(self, max_rows=200):
474
+ """Return the JSON-friendly payload the host renders."""
475
+ n = self._length
476
+ shown = min(n, max_rows)
477
+ rows = []
478
+ for i in range(shown):
479
+ rows.append([_format_cell(self._data[c][i]) for c in self._columns])
480
+ return {
481
+ "type": "table",
482
+ "columns": list(self._columns),
483
+ "rows": rows,
484
+ "row_count": n,
485
+ "shown_count": shown,
486
+ "truncated": n > shown,
487
+ }
488
+
489
+ # ---- Escape hatch ----
490
+
491
+ def to_pandas(self):
492
+ """Convert to a pandas DataFrame.
493
+
494
+ Lazy import: pandas is only loaded if you actually call this.
495
+ Inside Pyodide, this triggers a micropip install of pandas the
496
+ first time you use it; subsequent calls reuse the loaded module.
497
+ """
498
+ import pandas as _pd # noqa: F401 (raises ImportError if unavailable)
499
+ return _pd.DataFrame({c: list(self._data[c]) for c in self._columns})
500
+
501
+ # ---- Repr ----
502
+
503
+ def __repr__(self):
504
+ return "<Table %d rows x %d columns: %s>" % (
505
+ self._length,
506
+ len(self._columns),
507
+ ", ".join(self._columns),
508
+ )
509
+
510
+ # ---- Helpers ----
511
+
512
+ def _require_column(self, name):
513
+ if name not in self._data:
514
+ raise KeyError(
515
+ "No column named %r (have: %s)" % (name, ", ".join(self._columns))
516
+ )
517
+
518
+ def _numeric_column(self, name, op):
519
+ self._require_column(name)
520
+ out = []
521
+ for v in self._data[name]:
522
+ f = _to_number(v)
523
+ if f is None:
524
+ raise TypeError(
525
+ "%s needs a numeric column; column %r contains %r"
526
+ % (op, name, v)
527
+ )
528
+ out.append(f)
529
+ return out
530
+
531
+
532
+ # -----------------------------------------------------------------------------
533
+ # Constructors (module-level)
534
+ # -----------------------------------------------------------------------------
535
+
536
+ def table(columns, rows):
537
+ """Build a table from a list of column names and a list of rows."""
538
+ return Table(columns, rows)
539
+
540
+
541
+ def table_from_columns(data):
542
+ """Build a table from a \`{name: list-of-values}\` dict.
543
+
544
+ Iteration order of the dict determines column order (Python 3.7+
545
+ preserves insertion order).
546
+ """
547
+ cols = list(data.keys())
548
+ n = None
549
+ for c in cols:
550
+ ln = len(data[c])
551
+ if n is None:
552
+ n = ln
553
+ elif ln != n:
554
+ raise ValueError(
555
+ "Columns have differing lengths: %r"
556
+ % {c: len(data[c]) for c in cols}
557
+ )
558
+ if n is None:
559
+ n = 0
560
+ return Table._from_columns(cols, data, n)
561
+
562
+
563
+ # -----------------------------------------------------------------------------
564
+ # Numeric helpers
565
+ # -----------------------------------------------------------------------------
566
+
567
+ def _to_number(value):
568
+ if isinstance(value, bool):
569
+ return float(value)
570
+ if isinstance(value, (int, float)):
571
+ if isinstance(value, float) and (_math.isnan(value) or _math.isinf(value)):
572
+ return None
573
+ return float(value)
574
+ return None
575
+
576
+
577
+ def _sum_numeric(values):
578
+ s = 0.0
579
+ for v in values:
580
+ s += v
581
+ return s
582
+
583
+
584
+ def _sort_key(value):
585
+ """Sort key tolerant of mixed None/str/numeric columns."""
586
+ if value is None:
587
+ return (0, 0)
588
+ if isinstance(value, bool):
589
+ return (1, int(value))
590
+ if isinstance(value, (int, float)):
591
+ return (1, float(value))
592
+ if isinstance(value, str):
593
+ return (2, value)
594
+ return (3, repr(value))
595
+
596
+
597
+ # -----------------------------------------------------------------------------
598
+ # Chart rendering
599
+ # -----------------------------------------------------------------------------
600
+ #
601
+ # Charts are pure SVG, produced as a \`_PllChart\` object that exposes the
602
+ # image-display protocol so the host's existing image-card pipeline can
603
+ # render them. We don't subclass \`Image\` from imageLib.py so the table
604
+ # library doesn't need to import it; we duck-type via \`_pll_image_data\`.
605
+
606
+ _CHART_W = 480
607
+ _CHART_H = 320
608
+ _CHART_MARGIN_L = 56
609
+ _CHART_MARGIN_R = 16
610
+ _CHART_MARGIN_T = 28 # space for title
611
+ _CHART_MARGIN_B = 44
612
+
613
+
614
+ def _xml_escape(s):
615
+ return (
616
+ str(s)
617
+ .replace("&", "&amp;")
618
+ .replace("<", "&lt;")
619
+ .replace(">", "&gt;")
620
+ .replace('"', "&quot;")
621
+ )
622
+
623
+
624
+ class _PllChart:
625
+ """An immutable, displayable SVG chart. Duck-types as a PLL image."""
626
+
627
+ def __init__(self, svg_payload):
628
+ # svg_payload: {"width", "height", "data"}
629
+ self._payload = svg_payload
630
+
631
+ def _pll_image_data(self):
632
+ return {
633
+ "type": "svg",
634
+ "width": int(self._payload["width"]),
635
+ "height": int(self._payload["height"]),
636
+ "data": self._payload["data"],
637
+ }
638
+
639
+ def __repr__(self):
640
+ return "<Chart %dx%d>" % (self._payload["width"], self._payload["height"])
641
+
642
+
643
+ def _plot_box():
644
+ """Inner plotting rectangle: (x, y, w, h)."""
645
+ x = _CHART_MARGIN_L
646
+ y = _CHART_MARGIN_T
647
+ w = _CHART_W - _CHART_MARGIN_L - _CHART_MARGIN_R
648
+ h = _CHART_H - _CHART_MARGIN_T - _CHART_MARGIN_B
649
+ return x, y, w, h
650
+
651
+
652
+ def _nice_step(span, target_ticks):
653
+ """Pick a 1/2/5*10^k step that gives roughly \`target_ticks\` ticks."""
654
+ if span <= 0:
655
+ return 1.0
656
+ raw = span / max(1, target_ticks)
657
+ mag = 10.0 ** _math.floor(_math.log10(raw))
658
+ norm = raw / mag
659
+ if norm < 1.5:
660
+ nice = 1.0
661
+ elif norm < 3.5:
662
+ nice = 2.0
663
+ elif norm < 7.5:
664
+ nice = 5.0
665
+ else:
666
+ nice = 10.0
667
+ return nice * mag
668
+
669
+
670
+ def _nice_ticks(lo, hi, target=5):
671
+ if lo == hi:
672
+ # Pad a degenerate range so we still draw something sensible.
673
+ if lo == 0:
674
+ lo, hi = -1.0, 1.0
675
+ else:
676
+ lo, hi = lo - abs(lo) * 0.5, hi + abs(hi) * 0.5
677
+ step = _nice_step(hi - lo, target)
678
+ start = _math.floor(lo / step) * step
679
+ end = _math.ceil(hi / step) * step
680
+ ticks = []
681
+ v = start
682
+ # Guard against floating-point drift on the upper bound.
683
+ while v <= end + step * 0.5:
684
+ ticks.append(v)
685
+ v += step
686
+ return ticks, start, end
687
+
688
+
689
+ def _format_tick(v):
690
+ if v == int(v) and abs(v) < 1e16:
691
+ return "%d" % int(v)
692
+ return "%g" % v
693
+
694
+
695
+ def _chart_frame(title, x_label, y_label, body_svg):
696
+ """Wrap chart body SVG with title + axis labels + outer <svg>."""
697
+ px, py, pw, ph = _plot_box()
698
+ title_svg = ""
699
+ if title:
700
+ title_svg = (
701
+ '<text x="%d" y="%d" text-anchor="middle" '
702
+ 'font-size="13" font-weight="600">%s</text>'
703
+ ) % (_CHART_W / 2, _CHART_MARGIN_T - 12, _xml_escape(title))
704
+
705
+ x_label_svg = (
706
+ '<text x="%d" y="%d" text-anchor="middle" '
707
+ 'font-size="11" font-style="italic">%s</text>'
708
+ ) % (px + pw / 2, _CHART_H - 8, _xml_escape(x_label))
709
+
710
+ # y-label rotated 90deg, anchored on the left margin
711
+ y_label_svg = (
712
+ '<text x="%d" y="%d" text-anchor="middle" font-size="11" '
713
+ 'font-style="italic" transform="rotate(-90 %d %d)">%s</text>'
714
+ ) % (16, py + ph / 2, 16, py + ph / 2, _xml_escape(y_label))
715
+
716
+ return (
717
+ '<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
718
+ 'viewBox="0 0 %d %d" font-family="sans-serif" '
719
+ 'shape-rendering="geometricPrecision">'
720
+ '<rect x="0" y="0" width="%d" height="%d" fill="white"/>'
721
+ "%s%s%s%s</svg>"
722
+ ) % (
723
+ _CHART_W,
724
+ _CHART_H,
725
+ _CHART_W,
726
+ _CHART_H,
727
+ _CHART_W,
728
+ _CHART_H,
729
+ title_svg,
730
+ x_label_svg,
731
+ y_label_svg,
732
+ body_svg,
733
+ )
734
+
735
+
736
+ def _draw_y_axis(lo, hi, ticks):
737
+ px, py, pw, ph = _plot_box()
738
+ parts = ['<g stroke="#bbb" stroke-width="1" fill="none">']
739
+ text_parts = []
740
+ for t in ticks:
741
+ if t < lo - 1e-9 or t > hi + 1e-9:
742
+ continue
743
+ y = py + ph - (t - lo) / (hi - lo) * ph if hi > lo else py + ph / 2
744
+ parts.append(
745
+ '<line x1="%g" y1="%g" x2="%g" y2="%g" stroke="#eee"/>'
746
+ % (px, y, px + pw, y)
747
+ )
748
+ text_parts.append(
749
+ '<text x="%g" y="%g" font-size="10" text-anchor="end" '
750
+ 'fill="#444" dominant-baseline="middle">%s</text>'
751
+ % (px - 6, y, _xml_escape(_format_tick(t)))
752
+ )
753
+ # Axis line.
754
+ parts.append('<line x1="%g" y1="%g" x2="%g" y2="%g" stroke="#888"/>' % (px, py, px, py + ph))
755
+ parts.append("</g>")
756
+ return "".join(parts) + "".join(text_parts)
757
+
758
+
759
+ def _draw_x_axis_numeric(lo, hi, ticks):
760
+ px, py, pw, ph = _plot_box()
761
+ parts = ['<g stroke="#bbb" stroke-width="1" fill="none">']
762
+ text_parts = []
763
+ for t in ticks:
764
+ if t < lo - 1e-9 or t > hi + 1e-9:
765
+ continue
766
+ x = px + (t - lo) / (hi - lo) * pw if hi > lo else px + pw / 2
767
+ parts.append(
768
+ '<line x1="%g" y1="%g" x2="%g" y2="%g" stroke="#eee"/>'
769
+ % (x, py, x, py + ph)
770
+ )
771
+ text_parts.append(
772
+ '<text x="%g" y="%g" font-size="10" text-anchor="middle" fill="#444">%s</text>'
773
+ % (x, py + ph + 14, _xml_escape(_format_tick(t)))
774
+ )
775
+ parts.append(
776
+ '<line x1="%g" y1="%g" x2="%g" y2="%g" stroke="#888"/>'
777
+ % (px, py + ph, px + pw, py + ph)
778
+ )
779
+ parts.append("</g>")
780
+ return "".join(parts) + "".join(text_parts)
781
+
782
+
783
+ def _draw_x_axis_categorical(labels):
784
+ px, py, pw, ph = _plot_box()
785
+ parts = ['<g stroke="#888" fill="none">']
786
+ text_parts = []
787
+ n = len(labels)
788
+ if n > 0:
789
+ slot = pw / n
790
+ for i, label in enumerate(labels):
791
+ cx = px + slot * (i + 0.5)
792
+ text_parts.append(
793
+ '<text x="%g" y="%g" font-size="10" text-anchor="middle" '
794
+ 'fill="#444">%s</text>' % (cx, py + ph + 14, _xml_escape(label))
795
+ )
796
+ parts.append(
797
+ '<line x1="%g" y1="%g" x2="%g" y2="%g"/>'
798
+ % (px, py + ph, px + pw, py + ph)
799
+ )
800
+ parts.append("</g>")
801
+ return "".join(parts) + "".join(text_parts)
802
+
803
+
804
+ def _render_bar_chart(labels, values, x_label, y_label, title):
805
+ px, py, pw, ph = _plot_box()
806
+ n = len(values)
807
+ lo = min(0.0, min(values) if values else 0.0)
808
+ hi = max(values) if values else 1.0
809
+ if hi == lo:
810
+ hi = lo + 1.0
811
+ ticks, lo_n, hi_n = _nice_ticks(lo, hi, target=5)
812
+
813
+ body = []
814
+ body.append(_draw_y_axis(lo_n, hi_n, ticks))
815
+ body.append(_draw_x_axis_categorical(labels))
816
+
817
+ if n > 0:
818
+ slot = pw / n
819
+ bar_w = slot * 0.7
820
+ for i, v in enumerate(values):
821
+ cx = px + slot * (i + 0.5)
822
+ bx = cx - bar_w / 2
823
+ y0 = py + ph - (0 - lo_n) / (hi_n - lo_n) * ph
824
+ yv = py + ph - (v - lo_n) / (hi_n - lo_n) * ph
825
+ top = min(y0, yv)
826
+ h = abs(y0 - yv)
827
+ body.append(
828
+ '<rect x="%g" y="%g" width="%g" height="%g" fill="#4f8cff" />'
829
+ % (bx, top, bar_w, h)
830
+ )
831
+ return {
832
+ "width": _CHART_W,
833
+ "height": _CHART_H,
834
+ "data": _chart_frame(title, x_label, y_label, "".join(body)),
835
+ }
836
+
837
+
838
+ def _render_xy_chart(xs, ys, x_label, y_label, title, mode):
839
+ px, py, pw, ph = _plot_box()
840
+ if not xs:
841
+ body = ""
842
+ return {
843
+ "width": _CHART_W,
844
+ "height": _CHART_H,
845
+ "data": _chart_frame(title, x_label, y_label, body),
846
+ }
847
+
848
+ x_lo, x_hi = min(xs), max(xs)
849
+ y_lo, y_hi = min(ys), max(ys)
850
+ x_ticks, x_lo_n, x_hi_n = _nice_ticks(x_lo, x_hi, target=6)
851
+ y_ticks, y_lo_n, y_hi_n = _nice_ticks(y_lo, y_hi, target=5)
852
+
853
+ def to_px(x, y):
854
+ sx = px + (x - x_lo_n) / (x_hi_n - x_lo_n) * pw if x_hi_n > x_lo_n else px + pw / 2
855
+ sy = py + ph - (y - y_lo_n) / (y_hi_n - y_lo_n) * ph if y_hi_n > y_lo_n else py + ph / 2
856
+ return sx, sy
857
+
858
+ body = []
859
+ body.append(_draw_y_axis(y_lo_n, y_hi_n, y_ticks))
860
+ body.append(_draw_x_axis_numeric(x_lo_n, x_hi_n, x_ticks))
861
+
862
+ if mode == "line":
863
+ coords = [to_px(x, y) for x, y in zip(xs, ys)]
864
+ path = "M " + " L ".join("%g %g" % (sx, sy) for sx, sy in coords)
865
+ body.append(
866
+ '<path d="%s" fill="none" stroke="#4f8cff" stroke-width="2" '
867
+ 'stroke-linejoin="round" stroke-linecap="round"/>' % path
868
+ )
869
+ for sx, sy in coords:
870
+ body.append(
871
+ '<circle cx="%g" cy="%g" r="2.5" fill="#4f8cff"/>' % (sx, sy)
872
+ )
873
+ else:
874
+ for x, y in zip(xs, ys):
875
+ sx, sy = to_px(x, y)
876
+ body.append(
877
+ '<circle cx="%g" cy="%g" r="3" fill="#4f8cff" opacity="0.7"/>'
878
+ % (sx, sy)
879
+ )
880
+ return {
881
+ "width": _CHART_W,
882
+ "height": _CHART_H,
883
+ "data": _chart_frame(title, x_label, y_label, "".join(body)),
884
+ }
885
+
886
+
887
+ def _render_histogram(values, bins, name, title):
888
+ px, py, pw, ph = _plot_box()
889
+ if not values:
890
+ body = ""
891
+ return {
892
+ "width": _CHART_W,
893
+ "height": _CHART_H,
894
+ "data": _chart_frame(title, name, "count", body),
895
+ }
896
+
897
+ lo, hi = min(values), max(values)
898
+ if hi == lo:
899
+ hi = lo + 1.0 # avoid zero-width bins for constant data
900
+ width = (hi - lo) / bins
901
+ counts = [0] * bins
902
+ for v in values:
903
+ idx = int((v - lo) / width)
904
+ if idx == bins:
905
+ idx = bins - 1
906
+ counts[idx] += 1
907
+ max_count = max(counts) if counts else 1
908
+
909
+ y_ticks, y_lo_n, y_hi_n = _nice_ticks(0, max_count, target=5)
910
+ x_ticks, x_lo_n, x_hi_n = _nice_ticks(lo, hi, target=6)
911
+
912
+ body = []
913
+ body.append(_draw_y_axis(y_lo_n, y_hi_n, y_ticks))
914
+ body.append(_draw_x_axis_numeric(x_lo_n, x_hi_n, x_ticks))
915
+
916
+ for i, c in enumerate(counts):
917
+ bin_lo = lo + i * width
918
+ bin_hi = bin_lo + width
919
+ sx_lo = px + (bin_lo - x_lo_n) / (x_hi_n - x_lo_n) * pw
920
+ sx_hi = px + (bin_hi - x_lo_n) / (x_hi_n - x_lo_n) * pw
921
+ # Inset slightly so bars don't touch.
922
+ bar_w = max(1.0, sx_hi - sx_lo - 1)
923
+ y_top = py + ph - (c - y_lo_n) / (y_hi_n - y_lo_n) * ph if y_hi_n > y_lo_n else py + ph
924
+ h = py + ph - y_top
925
+ body.append(
926
+ '<rect x="%g" y="%g" width="%g" height="%g" '
927
+ 'fill="#4f8cff" opacity="0.85"/>' % (sx_lo + 0.5, y_top, bar_w, h)
928
+ )
929
+ return {
930
+ "width": _CHART_W,
931
+ "height": _CHART_H,
932
+ "data": _chart_frame(title, name, "count", "".join(body)),
933
+ }
934
+
935
+
936
+ # -----------------------------------------------------------------------------
937
+ # Names exported into user globals by the bootstrap.
938
+ # -----------------------------------------------------------------------------
939
+
940
+ PLL_TABLE_EXPORTS = [
941
+ "Table",
942
+ "table",
943
+ "table_from_columns",
944
+ ]
945
+ `;
946
+
947
+ // src/common/reactorLib.py
948
+ var reactorLib_default = '# Python Language Levels: interactive reactors (big-bang / animate).\n#\n# Loaded into Pyodide after imageLib, so `to_draw` handlers can use the\n# image primitives without importing anything.\n#\n# A reactor is a *value*: it holds the handlers plus the current state, and\n# `react` returns a new reactor rather than mutating one. That is what makes\n# stepping backwards possible - the trace is simply the states we have seen,\n# and rewinding is re-drawing one of them.\n#\n# Nothing in here runs an event loop. The extension host owns the clock (and\n# the socket, for the universe client) and calls `_pll_reactor_step` once per\n# event. A loop in here would hold the worker and the whole exec chain for as\n# long as the animation ran, which is the failure mode `Stop` exists for.\n\nimport json as _rx_json\n\n_PLL_DEFAULT_TICK_RATE = 1.0 / 28.0\n\n# Longest trace we keep. A reactor left running for an hour at 28 ticks a\n# second would otherwise hold 100k states alive just so it could rewind.\n_PLL_MAX_TRACE = 10000\n\n_PLL_HANDLER_NAMES = (\n "init",\n "to_draw",\n "on_tick",\n "tick_rate",\n "stop_when",\n "on_key",\n "on_mouse",\n "on_receive",\n "register",\n "title",\n)\n\n\nclass _PllPackage:\n """A new state, plus a message to send to the server."""\n\n __slots__ = ("state", "message")\n\n def __init__(self, state, message):\n self.state = state\n self.message = message\n\n def __repr__(self):\n return "package(%r, %r)" % (self.state, self.message)\n\n\ndef package(state, message):\n """Return `state` to the reactor and send `message` to the server."""\n return _PllPackage(state, message)\n\n\ndef _pll_split_package(result):\n """`(state, messages)` for whatever a handler returned."""\n if isinstance(result, _PllPackage):\n return result.state, (result.message,)\n return result, ()\n\n\nclass Reactor:\n """An interactive program: handlers plus the state they act on.\n\n Build one with `reactor(...)`. Every method that advances it returns a\n *new* Reactor, so a reactor you are holding never changes underneath\n you - `r.react(...)` twice from the same `r` gives the same answer.\n """\n\n __slots__ = ("_h", "_state", "_trace", "_tracing", "_outbox", "_shown")\n\n def __init__(self, handlers, state, trace=None, tracing=False, outbox=()):\n self._h = handlers\n self._state = state\n self._trace = list(trace) if trace else []\n self._tracing = tracing\n self._outbox = tuple(outbox)\n # Set by `interact()` on the reactor it hands back, so a top-level\n # `big_bang(...)` shows its card and not also its repr.\n self._shown = False\n\n @property\n def _pll_already_displayed(self):\n return self._shown\n\n def _pll_image_data(self):\n """Display protocol: a reactor shows the picture for its state.\n\n So `r` at the prompt draws the current frame, the same way a bare\n image or table displays itself.\n """\n return self.draw()._pll_image_data()\n\n # -- plumbing -----------------------------------------------------\n\n def _next(self, state, messages=()):\n trace = self._trace\n if self._tracing and len(trace) < _PLL_MAX_TRACE:\n trace = trace + [state]\n return Reactor(self._h, state, trace, self._tracing, messages)\n\n def _handler(self, name):\n return self._h.get(name)\n\n # -- inspection ---------------------------------------------------\n\n def get_value(self):\n """The current state."""\n return self._state\n\n def draw(self):\n """The image for the current state."""\n return self._h["to_draw"](self._state)\n\n def is_stopped(self):\n """True when `stop_when` says this state is the last one."""\n stop = self._handler("stop_when")\n return bool(stop(self._state)) if stop else False\n\n @property\n def title(self):\n return self._h.get("title") or "reactor"\n\n @property\n def tick_rate(self):\n return self._h.get("tick_rate", _PLL_DEFAULT_TICK_RATE)\n\n @property\n def register(self):\n return self._h.get("register")\n\n def handles(self, name):\n """Whether this reactor has the named handler."""\n return self._handler(name) is not None\n\n def outgoing(self):\n """Messages the last `react` asked to send to the server."""\n return list(self._outbox)\n\n # -- advancing ----------------------------------------------------\n\n def react(self, event):\n """A new reactor with one event applied.\n\n `event` is a dict: `{"kind": "tick"}`, `{"kind": "key", "key": "left"}`,\n `{"kind": "mouse", "x": 1, "y": 2, "event": "button-down"}`, or\n `{"kind": "receive", "message": ...}`. An event this reactor has no\n handler for leaves the state alone.\n """\n kind = event.get("kind") if isinstance(event, dict) else None\n if kind == "tick":\n handler = self._handler("on_tick")\n args = (self._state,)\n elif kind == "key":\n handler = self._handler("on_key")\n args = (self._state, event.get("key", ""))\n elif kind == "mouse":\n handler = self._handler("on_mouse")\n args = (\n self._state,\n event.get("x", 0),\n event.get("y", 0),\n event.get("event", "move"),\n )\n elif kind == "receive":\n handler = self._handler("on_receive")\n args = (self._state, event.get("message"))\n else:\n raise ValueError("react: unknown event kind %r" % (kind,))\n if handler is None:\n return self\n state, messages = _pll_split_package(handler(*args))\n return self._next(state, messages)\n\n def tick(self):\n """Shorthand for `react({"kind": "tick"})`."""\n return self.react({"kind": "tick"})\n\n # -- tracing ------------------------------------------------------\n\n def start_trace(self):\n """A new reactor that records every state it passes through."""\n return Reactor(self._h, self._state, [self._state], True, self._outbox)\n\n def stop_trace(self):\n """A new reactor that stops recording, keeping what it has."""\n return Reactor(self._h, self._state, self._trace, False, self._outbox)\n\n def get_trace(self):\n """The states recorded so far, oldest first."""\n return list(self._trace)\n\n def simulate_trace(self, limit):\n """Run up to `limit` ticks with tracing on, stopping at `stop_when`.\n\n No drawing and no clock: this is the way to test a reactor\'s logic\n at the prompt, or in a test, without watching it.\n """\n if limit < 0:\n raise ValueError("simulate_trace needs a limit of at least 0")\n current = self.start_trace()\n for _ in range(limit):\n if current.is_stopped():\n break\n nxt = current.tick()\n if nxt is current:\n break # no on_tick handler; nothing would ever change\n current = nxt\n return current\n\n def interact(self):\n """Show this reactor in the interactions panel and start it."""\n return _pll_reactor_interact(self)\n\n def __repr__(self):\n return "<reactor %s state=%r>" % (self.title, self._state)\n\n\ndef reactor(**handlers):\n """Build a reactor. `init` and `to_draw` are required.\n\n Handlers: `on_tick(state)`, `on_key(state, key)`,\n `on_mouse(state, x, y, event)`, `on_receive(state, message)`,\n `stop_when(state)`, plus `tick_rate` (seconds), `title`, and `register`\n (a `ws://` URL for the universe client).\n """\n unknown = [k for k in handlers if k not in _PLL_HANDLER_NAMES]\n if unknown:\n raise ValueError(\n "reactor: unknown handler%s %s. Known: %s"\n % (\n "" if len(unknown) == 1 else "s",\n ", ".join(repr(u) for u in sorted(unknown)),\n ", ".join(_PLL_HANDLER_NAMES),\n )\n )\n if "init" not in handlers:\n raise ValueError("reactor: needs an `init` state")\n if "to_draw" not in handlers:\n raise ValueError("reactor: needs a `to_draw` handler")\n for name in ("to_draw", "on_tick", "stop_when", "on_key", "on_mouse", "on_receive"):\n fn = handlers.get(name)\n if fn is not None and not callable(fn):\n raise ValueError("reactor: `%s` must be a function" % name)\n rate = handlers.get("tick_rate", _PLL_DEFAULT_TICK_RATE)\n if not isinstance(rate, (int, float)) or isinstance(rate, bool) or rate <= 0:\n raise ValueError("reactor: `tick_rate` must be a number of seconds above 0")\n state = handlers.pop("init")\n handlers["tick_rate"] = float(rate)\n return Reactor(handlers, state)\n\n\ndef big_bang(init, **handlers):\n """Build a reactor and start it. Racket\'s `big-bang`."""\n handlers["init"] = init\n return reactor(**handlers).interact()\n\n\ndef animate(to_draw, **handlers):\n """Animate `to_draw(n)`, where `n` counts ticks from 0. Racket\'s `animate`."""\n handlers.setdefault("init", 0)\n handlers.setdefault("on_tick", lambda n: n + 1)\n handlers["to_draw"] = to_draw\n return reactor(**handlers).interact()\n\n\nPLL_REACTOR_EXPORTS = [\n "Reactor",\n "reactor",\n "package",\n "big_bang",\n "animate",\n]\n\n\n# -----------------------------------------------------------------------------\n# Running reactors (driven by the extension host, one event per call)\n# -----------------------------------------------------------------------------\n\n_pll_reactors = {}\n_pll_reactor_seq = 0\n\n\nclass _PllRunning:\n """A reactor being interacted with, plus the frames it has been through.\n\n The history is a list of `(reactor, event)` pairs with a cursor, rather\n than a list of states, so rewinding and then playing forward again is\n *replay* - the same reactor values, not a recomputation that could drift\n if a handler is not deterministic. A new event at a rewound cursor\n discards the frames after it, exactly like an editor\'s undo history.\n """\n\n __slots__ = ("frames", "cursor", "dropped")\n\n def __init__(self, reactor_value):\n self.frames = [(reactor_value, None)]\n self.cursor = 0\n # Frames aged out of the front, so the card can still number them.\n self.dropped = 0\n\n @property\n def current(self):\n return self.frames[self.cursor][0]\n\n def seek(self, index):\n self.cursor = max(0, min(index, len(self.frames) - 1))\n return self.current\n\n def step(self, event):\n """Advance by one event. True if the frame was newly computed.\n\n The return value matters for the universe client: a *replayed* frame\n still carries the messages its handler produced the first time, and\n re-sending them because the student dragged the slider back and\n played forward would be wrong.\n """\n ahead = self.cursor + 1\n # Exact redo: the frame we already have came from this same event.\n if ahead < len(self.frames) and self.frames[ahead][1] == event:\n self.cursor = ahead\n return False\n del self.frames[ahead:]\n nxt = self.current.react(event)\n self.frames.append((nxt, event))\n self.cursor = len(self.frames) - 1\n if len(self.frames) > _PLL_MAX_TRACE:\n drop = len(self.frames) - _PLL_MAX_TRACE\n del self.frames[:drop]\n self.dropped += drop\n self.cursor -= drop\n return True\n\n\ndef _pll_reactor_frame(image):\n return {\n "data": image.to_svg(),\n "width": int(image.width) + (1 if image.width % 1 else 0),\n "height": int(image.height) + (1 if image.height % 1 else 0),\n }\n\n\ndef _pll_reactor_view(rid, running):\n """What the host needs to render the current frame."""\n current = running.current\n return {\n "ok": True,\n "id": rid,\n "frame": _pll_reactor_frame(current.draw()),\n "index": running.dropped + running.cursor,\n "length": running.dropped + len(running.frames),\n "at_end": running.cursor == len(running.frames) - 1,\n "stopped": current.is_stopped(),\n "value_repr": repr(current.get_value()),\n }\n\n\ndef _pll_reactor_failure(exc):\n return {\n "ok": False,\n "error_type": type(exc).__name__,\n "error_message": str(exc),\n "traceback": _pll_format_exception(exc),\n }\n\n\ndef _pll_reactor_interact(reactor_value):\n """Register a reactor and emit the card that will drive it."""\n global _pll_reactor_seq\n _pll_reactor_seq += 1\n rid = "r%d" % _pll_reactor_seq\n running = _PllRunning(reactor_value)\n _pll_reactors[rid] = running\n payload = {\n "type": "reactor",\n "id": rid,\n "title": reactor_value.title,\n "tick_rate": reactor_value.tick_rate,\n "ticking": reactor_value.handles("on_tick"),\n "wants_keys": reactor_value.handles("on_key"),\n "wants_mouse": reactor_value.handles("on_mouse"),\n "register": reactor_value.register,\n }\n payload.update(_pll_reactor_view(rid, running))\n payload.pop("ok", None)\n _pll_push(payload)\n reactor_value._shown = True\n return reactor_value\n\n\ndef _pll_reactor_step(rid, event_json):\n """Apply one event to a running reactor and return the new frame."""\n running = _pll_reactors.get(rid)\n if running is None:\n return {"ok": False, "gone": True}\n try:\n event = _rx_json.loads(event_json)\n computed = running.step(event)\n view = _pll_reactor_view(rid, running)\n outgoing = []\n for message in running.current.outgoing() if computed else ():\n try:\n outgoing.append(_rx_json.dumps(message))\n except (TypeError, ValueError):\n return {\n "ok": False,\n "error_type": "TypeError",\n "error_message": (\n "package(...) can only send values the server can read: "\n "numbers, strings, True/False, None, lists and dicts of "\n "those. Got %r." % (message,)\n ),\n "traceback": "",\n }\n view["messages"] = outgoing\n return view\n except BaseException as e:\n return _pll_reactor_failure(e)\n\n\ndef _pll_reactor_seek(rid, index):\n """Show an earlier (or later) frame without applying an event."""\n running = _pll_reactors.get(rid)\n if running is None:\n return {"ok": False, "gone": True}\n try:\n running.seek(int(index) - running.dropped)\n return _pll_reactor_view(rid, running)\n except BaseException as e:\n return _pll_reactor_failure(e)\n\n\ndef _pll_reactor_dispose(rid):\n """Forget a reactor, so its states and trace can be collected."""\n _pll_reactors.pop(rid, None)\n return True\n\n';
949
+
950
+ // src/common/pyodideRunner.ts
951
+ var PYODIDE_BOOTSTRAP_PY = pyodideBootstrap_default;
952
+ var PLL_IMAGE_LIB_PY = imageLib_default;
953
+ var PLL_TABLE_LIB_PY = tableLib_default;
954
+ var PLL_REACTOR_LIB_PY = reactorLib_default;
955
+ var NETWORK_IMPORT_RE = /(^|\n)[ \t]*(?:import|from)[ \t]+(?:pandas|requests|urllib|urllib3|httpx|aiohttp|http)\b/;
956
+ var PYODIDE_HTTP_PATCH_PY = "import pyodide_http as _pll_ph; _pll_ph.patch_all()";
957
+ var PYODIDE_INSTALL_PY = `
958
+ import sys as _sys, types as _types
959
+
960
+ _pll_module = _types.ModuleType("pll")
961
+ _pll_image_module = _types.ModuleType("pll.image")
962
+ _pll_table_module = _types.ModuleType("pll.table")
963
+ _pll_reactor_module = _types.ModuleType("pll.reactor")
964
+ for _name in PLL_IMAGE_EXPORTS:
965
+ setattr(_pll_image_module, _name, globals()[_name])
966
+ for _name in PLL_TABLE_EXPORTS:
967
+ setattr(_pll_table_module, _name, globals()[_name])
968
+ for _name in PLL_REACTOR_EXPORTS:
969
+ setattr(_pll_reactor_module, _name, globals()[_name])
970
+ _pll_module.image = _pll_image_module
971
+ _pll_module.table = _pll_table_module
972
+ _pll_module.reactor = _pll_reactor_module
973
+ _sys.modules["pll"] = _pll_module
974
+ _sys.modules["pll.image"] = _pll_image_module
975
+ _sys.modules["pll.table"] = _pll_table_module
976
+ _sys.modules["pll.reactor"] = _pll_reactor_module
977
+
978
+ # Add image + table library names to the per-session globals template.
979
+ # Each new session is initialized as a copy of this template, so every
980
+ # file's Run File / REPL prompt sees these names without explicit imports.
981
+ for _name in PLL_IMAGE_EXPORTS:
982
+ _pll_initial_globals[_name] = globals()[_name]
983
+ for _name in PLL_TABLE_EXPORTS:
984
+ _pll_initial_globals[_name] = globals()[_name]
985
+ for _name in PLL_REACTOR_EXPORTS:
986
+ _pll_initial_globals[_name] = globals()[_name]
987
+ del _name
988
+ `;
989
+
990
+ // src/common/interruptBuffer.ts
991
+ function clearInterrupt(sab) {
992
+ Atomics.store(new Uint8Array(sab), 0, 0);
993
+ }
994
+
995
+ // vendor/python/typeguard-4.6.0-py3-none-any.whl
996
+ var typeguard_4_6_0_py3_none_any_default = "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";
997
+
998
+ // vendor/python/typing_extensions-4.15.0-py3-none-any.whl
999
+ var typing_extensions_4_15_0_py3_none_any_default = "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";
1000
+
1001
+ // src/common/pythonVendor.ts
1002
+ var PLL_VENDOR_DIR = "/pll_vendor";
1003
+ var VENDORED_WHEELS = [
1004
+ { name: "typing_extensions.whl", base64: typing_extensions_4_15_0_py3_none_any_default },
1005
+ { name: "typeguard.whl", base64: typeguard_4_6_0_py3_none_any_default }
1006
+ ];
1007
+
1008
+ // src/common/stdinBuffer.ts
1009
+ var STDIN_SAB_BYTES = 64 * 1024;
1010
+ var STDIN_STATE_INDEX = 0;
1011
+ var STDIN_LENGTH_INDEX = 1;
1012
+ var STDIN_PAYLOAD_OFFSET = 8;
1013
+ var STDIN_STATE_WAITING = 0;
1014
+ var STDIN_STATE_EOF = 2;
1015
+ function decodeSharedBytes(sab, offset, length) {
1016
+ const copy = new Uint8Array(length);
1017
+ if (length > 0) {
1018
+ copy.set(new Uint8Array(sab, offset, length));
1019
+ }
1020
+ return new TextDecoder().decode(copy);
1021
+ }
1022
+ function waitForStdinLine(sab, requestInput) {
1023
+ const state = new Int32Array(sab);
1024
+ Atomics.store(state, STDIN_STATE_INDEX, STDIN_STATE_WAITING);
1025
+ requestInput();
1026
+ Atomics.wait(state, STDIN_STATE_INDEX, STDIN_STATE_WAITING);
1027
+ const next = Atomics.load(state, STDIN_STATE_INDEX);
1028
+ if (next === STDIN_STATE_EOF) {
1029
+ return null;
1030
+ }
1031
+ const n = Atomics.load(state, STDIN_LENGTH_INDEX);
1032
+ const max = sab.byteLength - STDIN_PAYLOAD_OFFSET;
1033
+ const len = n > 0 && n <= max ? n : 0;
1034
+ return decodeSharedBytes(sab, STDIN_PAYLOAD_OFFSET, len);
1035
+ }
1036
+
1037
+ // src/common/workerHost.ts
1038
+ var LIVE_FLUSH_MS = 50;
1039
+ function decodeBase64(base64) {
1040
+ const binary = atob(base64);
1041
+ const bytes = new Uint8Array(binary.length);
1042
+ for (let i = 0; i < binary.length; i++) {
1043
+ bytes[i] = binary.charCodeAt(i);
1044
+ }
1045
+ return bytes;
1046
+ }
1047
+ function createWorkerHost(adapter) {
1048
+ let pyodide = null;
1049
+ let initPromise = null;
1050
+ let pytestPromise = null;
1051
+ let httpPatchPromise = null;
1052
+ let stdinBuffer = null;
1053
+ let interruptBuffer = null;
1054
+ let livePending = null;
1055
+ let liveLastPost = 0;
1056
+ function flushLive() {
1057
+ if (!livePending) {
1058
+ return;
1059
+ }
1060
+ const payload = livePending;
1061
+ livePending = null;
1062
+ liveLastPost = Date.now();
1063
+ adapter.post({ type: "display", payload });
1064
+ }
1065
+ function emitDisplay(json) {
1066
+ let payload;
1067
+ try {
1068
+ payload = JSON.parse(String(json));
1069
+ } catch {
1070
+ return;
1071
+ }
1072
+ if (payload.type === "stdout" || payload.type === "stderr") {
1073
+ if (livePending && livePending.type === payload.type) {
1074
+ livePending.text += payload.text;
1075
+ } else {
1076
+ flushLive();
1077
+ livePending = { type: payload.type, text: payload.text };
1078
+ }
1079
+ if (Date.now() - liveLastPost >= LIVE_FLUSH_MS) {
1080
+ flushLive();
1081
+ }
1082
+ return;
1083
+ }
1084
+ flushLive();
1085
+ adapter.post({ type: "display", payload });
1086
+ }
1087
+ function enableTypeChecking(instance) {
1088
+ try {
1089
+ try {
1090
+ instance.FS.mkdir(PLL_VENDOR_DIR);
1091
+ } catch {
1092
+ }
1093
+ for (const wheel of VENDORED_WHEELS) {
1094
+ instance.FS.writeFile(`${PLL_VENDOR_DIR}/${wheel.name}`, decodeBase64(wheel.base64));
1095
+ }
1096
+ const enable = instance.globals.get("_pll_enable_type_checking");
1097
+ try {
1098
+ enable();
1099
+ } finally {
1100
+ enable.destroy?.();
1101
+ }
1102
+ } catch {
1103
+ }
1104
+ }
1105
+ function readStdin() {
1106
+ if (!stdinBuffer) {
1107
+ throw new Error(adapter.stdinUnavailableMessage);
1108
+ }
1109
+ flushLive();
1110
+ return waitForStdinLine(stdinBuffer, () => adapter.post({ type: "stdinRequest" }));
1111
+ }
1112
+ async function ensurePyodide(indexUrl) {
1113
+ if (!initPromise) {
1114
+ initPromise = (async () => {
1115
+ const instance = await adapter.loadPyodide(indexUrl);
1116
+ instance.setStdin({ stdin: readStdin, autoEOF: true });
1117
+ if (interruptBuffer) {
1118
+ instance.setInterruptBuffer(new Uint8Array(interruptBuffer));
1119
+ }
1120
+ instance.runPython(PYODIDE_BOOTSTRAP_PY);
1121
+ instance.runPython(PLL_IMAGE_LIB_PY);
1122
+ instance.runPython(PLL_TABLE_LIB_PY);
1123
+ instance.runPython(PLL_REACTOR_LIB_PY);
1124
+ instance.runPython(PYODIDE_INSTALL_PY);
1125
+ enableTypeChecking(instance);
1126
+ ensureWorkDir(instance.FS);
1127
+ pyodide = instance;
1128
+ return instance;
1129
+ })();
1130
+ }
1131
+ return initPromise;
1132
+ }
1133
+ function ready() {
1134
+ if (!pyodide) {
1135
+ throw new Error("Pyodide not initialized");
1136
+ }
1137
+ return pyodide;
1138
+ }
1139
+ function ensureHttpShim() {
1140
+ if (!httpPatchPromise) {
1141
+ const instance = ready();
1142
+ httpPatchPromise = instance.loadPackage("pyodide-http").then(() => void instance.runPython(PYODIDE_HTTP_PATCH_PY));
1143
+ }
1144
+ return httpPatchPromise;
1145
+ }
1146
+ function ensurePytest() {
1147
+ if (!pytestPromise) {
1148
+ pytestPromise = ready().loadPackage("pytest");
1149
+ }
1150
+ return pytestPromise;
1151
+ }
1152
+ function callPython(name, args) {
1153
+ const fn = ready().globals.get(name);
1154
+ try {
1155
+ const proxy = fn(...args);
1156
+ try {
1157
+ return proxy.toJs({ dict_converter: Object.fromEntries });
1158
+ } finally {
1159
+ proxy.destroy?.();
1160
+ }
1161
+ } finally {
1162
+ fn.destroy?.();
1163
+ }
1164
+ }
1165
+ function dropPendingInterrupt() {
1166
+ if (interruptBuffer) {
1167
+ clearInterrupt(interruptBuffer);
1168
+ }
1169
+ }
1170
+ function withLiveEmit(run) {
1171
+ ready().globals.set("_pll_live_emit", emitDisplay);
1172
+ livePending = null;
1173
+ liveLastPost = 0;
1174
+ try {
1175
+ return run();
1176
+ } finally {
1177
+ flushLive();
1178
+ ready().runPython("_pll_live_emit = None");
1179
+ }
1180
+ }
1181
+ return async function handle2(data) {
1182
+ try {
1183
+ switch (data.type) {
1184
+ case "init": {
1185
+ stdinBuffer = data.stdinBuffer ?? null;
1186
+ interruptBuffer = data.interruptBuffer ?? null;
1187
+ await ensurePyodide(data.indexUrl);
1188
+ adapter.post({ id: data.id, type: "ready" });
1189
+ break;
1190
+ }
1191
+ case "runFile": {
1192
+ dropPendingInterrupt();
1193
+ const result = withLiveEmit(
1194
+ () => callPython("_pll_run_file", [
1195
+ data.code,
1196
+ data.fileName,
1197
+ data.sessionKey,
1198
+ data.level ?? "raw"
1199
+ ])
1200
+ );
1201
+ result.displays = [];
1202
+ adapter.post({ id: data.id, type: "result", result });
1203
+ break;
1204
+ }
1205
+ case "replEval": {
1206
+ dropPendingInterrupt();
1207
+ const result = callPython("_pll_repl_eval", [
1208
+ data.code,
1209
+ data.sessionKey,
1210
+ data.level ?? "raw"
1211
+ ]);
1212
+ adapter.post({ id: data.id, type: "result", result });
1213
+ break;
1214
+ }
1215
+ case "checkSyntax": {
1216
+ const result = callPython("_pll_repl_check", [data.code]);
1217
+ adapter.post({ id: data.id, type: "syntax", result });
1218
+ break;
1219
+ }
1220
+ case "hasTests": {
1221
+ const fn = ready().globals.get("_pll_has_tests");
1222
+ try {
1223
+ adapter.post({ id: data.id, type: "hasTests", result: Boolean(fn(data.code)) });
1224
+ } finally {
1225
+ fn.destroy?.();
1226
+ }
1227
+ break;
1228
+ }
1229
+ case "loadPackages": {
1230
+ await ready().loadPackagesFromImports(data.code);
1231
+ if (NETWORK_IMPORT_RE.test(data.code)) {
1232
+ await ensureHttpShim();
1233
+ }
1234
+ adapter.post({ id: data.id, type: "packagesReady" });
1235
+ break;
1236
+ }
1237
+ case "loadPytest": {
1238
+ await ensurePytest();
1239
+ adapter.post({ id: data.id, type: "pytestReady" });
1240
+ break;
1241
+ }
1242
+ case "runTests": {
1243
+ dropPendingInterrupt();
1244
+ await ensurePytest();
1245
+ const result = callPython("_pll_run_tests", [
1246
+ data.code,
1247
+ data.fileName,
1248
+ data.level ?? "raw"
1249
+ ]);
1250
+ adapter.post({ id: data.id, type: "testResult", result });
1251
+ break;
1252
+ }
1253
+ case "staticAnalyze": {
1254
+ const result = callPython("_pll_static_analyze", [
1255
+ data.code,
1256
+ data.level,
1257
+ data.fileName,
1258
+ data.sessionKey ?? null
1259
+ ]) ?? [];
1260
+ adapter.post({ id: data.id, type: "static", result });
1261
+ break;
1262
+ }
1263
+ case "reactorStep": {
1264
+ const result = withLiveEmit(
1265
+ () => callPython("_pll_reactor_step", [
1266
+ data.reactorId,
1267
+ data.event
1268
+ ])
1269
+ );
1270
+ adapter.post({ id: data.id, type: "reactorFrame", result });
1271
+ break;
1272
+ }
1273
+ case "reactorSeek": {
1274
+ const result = callPython("_pll_reactor_seek", [
1275
+ data.reactorId,
1276
+ data.index
1277
+ ]);
1278
+ adapter.post({ id: data.id, type: "reactorFrame", result });
1279
+ break;
1280
+ }
1281
+ case "reactorDispose": {
1282
+ const fn = ready().globals.get("_pll_reactor_dispose");
1283
+ try {
1284
+ fn(data.reactorId);
1285
+ } finally {
1286
+ fn.destroy?.();
1287
+ }
1288
+ adapter.post({ id: data.id, type: "reactorDisposed" });
1289
+ break;
1290
+ }
1291
+ case "mountWorkspace": {
1292
+ mountWorkspaceFiles(ready().FS, data.files);
1293
+ adapter.post({ id: data.id, type: "workspaceReady" });
1294
+ break;
1295
+ }
1296
+ case "collectWorkspace": {
1297
+ const files = collectChangedWorkspaceFiles(ready().FS);
1298
+ adapter.post({ id: data.id, type: "workspaceFiles", files });
1299
+ break;
1300
+ }
1301
+ }
1302
+ } catch (err) {
1303
+ adapter.post({
1304
+ id: data.id,
1305
+ type: "error",
1306
+ message: err instanceof Error ? err.message : String(err)
1307
+ });
1308
+ }
1309
+ };
1310
+ }
1311
+
1312
+ // src/desktop/syncHttp.ts
1313
+ var import_node_child_process = require("node:child_process");
1314
+ function syncHttpRequest(opts) {
1315
+ const payload = JSON.stringify({
1316
+ method: opts.method || "GET",
1317
+ url: opts.url,
1318
+ headers: opts.headers ?? {},
1319
+ body: opts.body == null || opts.body === "" ? null : Buffer.from(
1320
+ typeof opts.body === "string" ? opts.body : Buffer.from(opts.body)
1321
+ ).toString("base64")
1322
+ });
1323
+ const script = `
1324
+ const fs = require("node:fs");
1325
+ const raw = fs.readFileSync(0, "utf8");
1326
+ const req = JSON.parse(raw);
1327
+ const init = { method: req.method, headers: req.headers };
1328
+ if (req.body) init.body = Buffer.from(req.body, "base64");
1329
+ fetch(req.url, init).then(async (r) => {
1330
+ const hop = new Set(["transfer-encoding", "content-encoding", "connection", "keep-alive"]);
1331
+ const headers = {};
1332
+ r.headers.forEach((v, k) => {
1333
+ if (!hop.has(k.toLowerCase())) headers[k] = v;
1334
+ });
1335
+ const raw = Buffer.from(await r.arrayBuffer());
1336
+ headers["content-length"] = String(raw.length);
1337
+ const body = raw.toString("base64");
1338
+ process.stdout.write(JSON.stringify({ status: r.status, headers, body }));
1339
+ }).catch((e) => {
1340
+ process.stderr.write(String(e && e.stack ? e.stack : e));
1341
+ process.exit(1);
1342
+ });
1343
+ `;
1344
+ let out;
1345
+ try {
1346
+ out = (0, import_node_child_process.execFileSync)(process.execPath, ["-e", script], {
1347
+ input: payload,
1348
+ maxBuffer: 64 * 1024 * 1024,
1349
+ windowsHide: true
1350
+ });
1351
+ } catch (err) {
1352
+ const msg = err instanceof Error ? err.message : String(err);
1353
+ throw new Error(`HTTP request failed: ${msg}`);
1354
+ }
1355
+ const parsed = JSON.parse(out.toString("utf8"));
1356
+ return {
1357
+ status: parsed.status,
1358
+ headers: parsed.headers ?? {},
1359
+ body: new Uint8Array(Buffer.from(parsed.body ?? "", "base64"))
1360
+ };
1361
+ }
1362
+
1363
+ // src/desktop/xhrPolyfill.ts
1364
+ function installNodeXHR() {
1365
+ const g = globalThis;
1366
+ if (typeof g.crossOriginIsolated === "undefined") {
1367
+ g.crossOriginIsolated = false;
1368
+ }
1369
+ if (typeof g.XMLHttpRequest === "function") {
1370
+ return;
1371
+ }
1372
+ class NodeXMLHttpRequest {
1373
+ static UNSENT = 0;
1374
+ static OPENED = 1;
1375
+ static HEADERS_RECEIVED = 2;
1376
+ static LOADING = 3;
1377
+ static DONE = 4;
1378
+ method = "GET";
1379
+ url = "";
1380
+ async = true;
1381
+ timeout = 0;
1382
+ responseType = "";
1383
+ withCredentials = false;
1384
+ status = 0;
1385
+ statusText = "";
1386
+ response = null;
1387
+ responseText = "";
1388
+ readyState = 0;
1389
+ reqHeaders = {};
1390
+ resHeaders = {};
1391
+ open(method, url, async = true) {
1392
+ this.method = method;
1393
+ this.url = url;
1394
+ this.async = async;
1395
+ this.readyState = 1;
1396
+ }
1397
+ setRequestHeader(name, value) {
1398
+ this.reqHeaders[name] = value;
1399
+ }
1400
+ overrideMimeType(_mime) {
1401
+ }
1402
+ send(body) {
1403
+ let payload = null;
1404
+ if (body != null && body !== "") {
1405
+ if (typeof body === "string") {
1406
+ payload = body;
1407
+ } else if (body instanceof Uint8Array) {
1408
+ payload = body;
1409
+ } else if (body instanceof ArrayBuffer) {
1410
+ payload = new Uint8Array(body);
1411
+ }
1412
+ }
1413
+ const result = syncHttpRequest({
1414
+ method: this.method,
1415
+ url: this.url,
1416
+ headers: this.reqHeaders,
1417
+ body: payload
1418
+ });
1419
+ this.status = result.status;
1420
+ this.statusText = result.status >= 200 && result.status < 300 ? "OK" : "";
1421
+ this.resHeaders = result.headers;
1422
+ this.readyState = 4;
1423
+ if (this.responseType === "arraybuffer") {
1424
+ const copy = new Uint8Array(result.body.byteLength);
1425
+ copy.set(result.body);
1426
+ this.response = copy.buffer;
1427
+ this.responseText = "";
1428
+ } else {
1429
+ const text = new TextDecoder("latin1").decode(result.body);
1430
+ this.response = text;
1431
+ this.responseText = text;
1432
+ }
1433
+ }
1434
+ getAllResponseHeaders() {
1435
+ return Object.entries(this.resHeaders).map(([k, v]) => `${k}: ${v}`).join("\r\n");
1436
+ }
1437
+ getResponseHeader(name) {
1438
+ const lower = name.toLowerCase();
1439
+ for (const [k, v] of Object.entries(this.resHeaders)) {
1440
+ if (k.toLowerCase() === lower) {
1441
+ return v;
1442
+ }
1443
+ }
1444
+ return null;
1445
+ }
1446
+ }
1447
+ g.XMLHttpRequest = NodeXMLHttpRequest;
1448
+ }
1449
+
1450
+ // src/desktop/pyodideWorker.ts
1451
+ if (!import_node_worker_threads.parentPort) {
1452
+ throw new Error("desktop pyodide worker must be started as a worker_thread");
1453
+ }
1454
+ var port = import_node_worker_threads.parentPort;
1455
+ function ensureStdioFds() {
1456
+ const streams = [
1457
+ [process.stdin, 0],
1458
+ [process.stdout, 1],
1459
+ [process.stderr, 2]
1460
+ ];
1461
+ for (const [stream, fd] of streams) {
1462
+ if (stream && stream.fd == null) {
1463
+ Object.defineProperty(stream, "fd", { value: fd });
1464
+ }
1465
+ }
1466
+ }
1467
+ var handle = createWorkerHost({
1468
+ post: (msg) => port.postMessage(msg),
1469
+ stdinUnavailableMessage: "input() is unavailable (SharedArrayBuffer was not provided).",
1470
+ async loadPyodide(indexUrl) {
1471
+ ensureStdioFds();
1472
+ installNodeXHR();
1473
+ const { loadPyodide } = await import("pyodide");
1474
+ return await loadPyodide({ indexURL: indexUrl });
1475
+ }
1476
+ });
1477
+ port.on("message", (data) => void handle(data));
1478
+ //# sourceMappingURL=worker.cjs.map