sagadeck 1.2.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.
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/SKILL.md +148 -0
- package/bin/sagadeck.js +511 -0
- package/docs/ANDAMENTO.md +1255 -0
- package/docs/EXPERIENCIAS.md +39 -0
- package/docs/PLANO-EXPERIENCIAS.md +39 -0
- package/docs/PLANO-TESTE-AULAS-TECNICAS.md +44 -0
- package/docs/PLANO-TESTE-DESINFORMACAO.md +38 -0
- package/docs/REFERENCIA.md +1131 -0
- package/package.json +59 -0
- package/src/adaptive-layouts.js +18 -0
- package/src/ai/art-direction.js +8 -0
- package/src/ai/commands.js +130 -0
- package/src/ai/context.js +245 -0
- package/src/ai/deck-ai.js +1135 -0
- package/src/ai/directions.js +74 -0
- package/src/ai/document-materials.js +49 -0
- package/src/ai/document-visuals.js +215 -0
- package/src/ai/evaluation.js +31 -0
- package/src/ai/ground.js +55 -0
- package/src/ai/llm.js +292 -0
- package/src/ai/quality.js +51 -0
- package/src/ai/relay.js +45 -0
- package/src/ai/staged-generation.js +24 -0
- package/src/ai/transform.js +858 -0
- package/src/ai/usage.js +17 -0
- package/src/ai/variety.js +80 -0
- package/src/ai/video-frames.js +24 -0
- package/src/ai/video-generation.js +126 -0
- package/src/ai/web-operation.js +31 -0
- package/src/algo-catalog.js +153 -0
- package/src/algo-trace.js +139 -0
- package/src/api-client.js +514 -0
- package/src/api-collections.js +14 -0
- package/src/api-demo.js +222 -0
- package/src/aspect.js +40 -0
- package/src/build.js +323 -0
- package/src/carousel.js +56 -0
- package/src/chrome.js +65 -0
- package/src/code-highlight.js +68 -0
- package/src/code-lab-browser.js +22 -0
- package/src/code-lab.js +3 -0
- package/src/code-language.js +52 -0
- package/src/csv.js +74 -0
- package/src/decision-lab.js +20 -0
- package/src/deck-file.js +119 -0
- package/src/diagram/napkin-examples.js +13 -0
- package/src/diagram/napkin.js +539 -0
- package/src/docx.js +128 -0
- package/src/dynamics/git-sim.js +200 -0
- package/src/dynamics/layouts.js +112 -0
- package/src/elements.js +554 -0
- package/src/experiences.js +124 -0
- package/src/exploration.js +31 -0
- package/src/export/browser.js +53 -0
- package/src/export/estudo.js +67 -0
- package/src/export/pptx.js +475 -0
- package/src/export/roteiro.js +40 -0
- package/src/export/shots.js +182 -0
- package/src/figures/charts.js +383 -0
- package/src/figures/diagrams.js +117 -0
- package/src/figures/icons.js +166 -0
- package/src/figures/material-pictos.js +20 -0
- package/src/figures/pictos.js +255 -0
- package/src/figures/points.js +106 -0
- package/src/figures/qr.js +29 -0
- package/src/figures/ufmap.js +48 -0
- package/src/fiscal/autofix.js +377 -0
- package/src/fiscal/normalize.js +115 -0
- package/src/graph-lab.js +24 -0
- package/src/hologram.js +30 -0
- package/src/identity.js +86 -0
- package/src/import/crop.js +59 -0
- package/src/import/index.js +132 -0
- package/src/import/legacy-word.js +42 -0
- package/src/import/merge.js +45 -0
- package/src/import/office.js +123 -0
- package/src/import/omml.js +69 -0
- package/src/import/pdf-render.js +56 -0
- package/src/import/pptx.js +701 -0
- package/src/import/xml.js +42 -0
- package/src/infographic.js +401 -0
- package/src/layouts.js +723 -0
- package/src/lessons.js +171 -0
- package/src/library.js +399 -0
- package/src/markup.js +116 -0
- package/src/master.js +214 -0
- package/src/mcp/server.js +371 -0
- package/src/meeting.js +42 -0
- package/src/motion.js +56 -0
- package/src/package.js +116 -0
- package/src/playground.js +19 -0
- package/src/portal-scene.js +20 -0
- package/src/preferences.js +61 -0
- package/src/protect.js +51 -0
- package/src/purpose.js +24 -0
- package/src/pytrace.js +1092 -0
- package/src/research/duckduckgo.js +73 -0
- package/src/research/research.js +202 -0
- package/src/research/svg-image.js +17 -0
- package/src/research/visuals.js +52 -0
- package/src/runtime/api-collection-ui.js +5 -0
- package/src/runtime/api-collections.js +28 -0
- package/src/runtime/api-core.js +530 -0
- package/src/runtime/api-ui.js +966 -0
- package/src/runtime/base.css +1774 -0
- package/src/runtime/calc.js +139 -0
- package/src/runtime/code-lab.js +371 -0
- package/src/runtime/decision-lab.js +49 -0
- package/src/runtime/diagram.js +341 -0
- package/src/runtime/fit.js +268 -0
- package/src/runtime/fonts/caveat.css +2 -0
- package/src/runtime/fonts/comic-neue.css +3 -0
- package/src/runtime/fonts/fredoka.css +2 -0
- package/src/runtime/fonts/index.json +10 -0
- package/src/runtime/fonts/inter.css +2 -0
- package/src/runtime/fonts/patrick-hand.css +2 -0
- package/src/runtime/fonts/plus-jakarta-sans.css +2 -0
- package/src/runtime/fonts/press-start-2p.css +2 -0
- package/src/runtime/fonts/vt323.css +2 -0
- package/src/runtime/formula.js +148 -0
- package/src/runtime/graph-lab.js +72 -0
- package/src/runtime/meeting.css +7 -0
- package/src/runtime/meeting.js +10 -0
- package/src/runtime/motion.js +15 -0
- package/src/runtime/playground.js +11 -0
- package/src/runtime/points.js +19 -0
- package/src/runtime/portal-scene.css +6 -0
- package/src/runtime/portal-scene.js +22 -0
- package/src/runtime/runtime.js +827 -0
- package/src/runtime/science.js +89 -0
- package/src/runtime/skins/aluminio.css +31 -0
- package/src/runtime/skins/arcade.css +25 -0
- package/src/runtime/skins/aurora.css +17 -0
- package/src/runtime/skins/bauhaus.css +19 -0
- package/src/runtime/skins/editorial.css +19 -0
- package/src/runtime/skins/jornal.css +17 -0
- package/src/runtime/skins/manual-noite.css +14 -0
- package/src/runtime/skins/manual.css +14 -0
- package/src/runtime/skins/noite.css +21 -0
- package/src/runtime/skins/oceano.css +17 -0
- package/src/runtime/skins/pop.css +23 -0
- package/src/runtime/skins/prata.css +18 -0
- package/src/runtime/skins/prisma.css +28 -0
- package/src/runtime/skins/rabisco.css +18 -0
- package/src/runtime/skins/relevo.css +33 -0
- package/src/runtime/skins/sinal.css +16 -0
- package/src/runtime/skins/terminal.css +23 -0
- package/src/runtime/vendor/KATEX-LICENSE.txt +21 -0
- package/src/runtime/vendor/MERMAID-LICENSE.txt +21 -0
- package/src/runtime/vendor/PLOTLY-LICENSE.txt +21 -0
- package/src/runtime/vendor/katex.css +1 -0
- package/src/runtime/vendor/mermaid.min.js +3636 -0
- package/src/runtime/vendor/plotly.min.js +3620 -0
- package/src/runtime/video-player.js +35 -0
- package/src/science.js +162 -0
- package/src/studio/ai-response.js +36 -0
- package/src/studio/api-routes.js +152 -0
- package/src/studio/assets/contexto/leia-me.md +12 -0
- package/src/studio/assets/contexto/vendas.csv +7 -0
- package/src/studio/assets/imagens/colecao-arquitetura.png +0 -0
- package/src/studio/assets/imagens/colecao-cor.png +0 -0
- package/src/studio/assets/imagens/colecao-equipe.png +0 -0
- package/src/studio/assets/imagens/lavanda-cover.jpg +0 -0
- package/src/studio/code-routes.js +32 -0
- package/src/studio/command-approval.js +48 -0
- package/src/studio/demo-decks.js +98 -0
- package/src/studio/direction-routes.js +29 -0
- package/src/studio/errors.js +65 -0
- package/src/studio/exploration-demo.js +17 -0
- package/src/studio/exporting.js +105 -0
- package/src/studio/generation-report.js +10 -0
- package/src/studio/instance.js +48 -0
- package/src/studio/layout-samples.js +152 -0
- package/src/studio/project.js +371 -0
- package/src/studio/public/api-collections.js +28 -0
- package/src/studio/public/app.js +5265 -0
- package/src/studio/public/art-preview.js +10 -0
- package/src/studio/public/calc-fields.js +11 -0
- package/src/studio/public/chat-progress.js +47 -0
- package/src/studio/public/developer-fields.js +22 -0
- package/src/studio/public/explorer.js +443 -0
- package/src/studio/public/history.js +58 -0
- package/src/studio/public/index.html +867 -0
- package/src/studio/public/inspector.js +270 -0
- package/src/studio/public/lab-fields.js +5 -0
- package/src/studio/public/library.css +275 -0
- package/src/studio/public/library.html +55 -0
- package/src/studio/public/library.js +471 -0
- package/src/studio/public/merge-decks.js +155 -0
- package/src/studio/public/review-ui.js +100 -0
- package/src/studio/public/screenshot-editor.js +140 -0
- package/src/studio/public/share-ui.js +72 -0
- package/src/studio/public/slide-clipboard.js +23 -0
- package/src/studio/public/slide-form.js +1263 -0
- package/src/studio/public/studio-awareness.js +154 -0
- package/src/studio/public/studio-next.css +303 -0
- package/src/studio/public/style.css +1262 -0
- package/src/studio/public/ui-icons.js +2 -0
- package/src/studio/public/viewers.js +372 -0
- package/src/studio/public/visual-editor.js +326 -0
- package/src/studio/public/workspace.js +18 -0
- package/src/studio/public-files.js +1 -0
- package/src/studio/request-helpers.js +41 -0
- package/src/studio/server.js +1583 -0
- package/src/studio/share-routes.js +146 -0
- package/src/studio/slide-copy-routes.js +26 -0
- package/src/studio/slide-copy.js +30 -0
- package/src/studio/snapshot.js +160 -0
- package/src/studio/style-routes.js +103 -0
- package/src/studio/template-collections.js +97 -0
- package/src/studio/versions.js +95 -0
- package/src/studio/video-download.js +29 -0
- package/src/studio/visual-keys.js +52 -0
- package/src/studio/workspaces.js +39 -0
- package/src/table.js +93 -0
- package/src/templates/scaffold.js +372 -0
- package/src/themes.js +604 -0
- package/src/trace-view.js +450 -0
- package/src/uid.js +19 -0
- package/src/video-player.js +40 -0
- package/src/visual-edits.js +99 -0
- package/src/ws.js +134 -0
- package/templates/cenario/Texto no cen/303/241rio.yaml" +171 -0
- package/templates/cenario/imagens/cidade.svg +30 -0
- package/templates/cenario/imagens/pessoa.svg +15 -0
- package/templates/cenario-e-ciencia.yaml +47 -0
- package/templates/ensaio-api.yaml +160 -0
- package/templates/exemplo-alegre.yaml +75 -0
- package/templates/exemplo-keynote.yaml +90 -0
- package/templates/exemplo-napkin.yaml +139 -0
- package/templates/exemplo.yaml +228 -0
package/src/pytrace.js
ADDED
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
// Execução rastreada (layout algo com program:): um Python simples interpretado aqui mesmo, sem eval e sem acesso a
|
|
2
|
+
// nada fora do programa. Roda o código do professor (o algoritmo que ele inventou, uma árvore, um grafo…) e grava cada
|
|
3
|
+
// passo como num depurador: a linha, as variáveis de cada chamada, o que foi lido e escrito e o que saiu no print.
|
|
4
|
+
//
|
|
5
|
+
// Cobre o que aparece em aula: def (recursão, padrão de parâmetro), class com __init__ e métodos, if/elif/else,
|
|
6
|
+
// while, for … in, break/continue, return, listas, tuplas, dicionários, conjuntos, fatiamento, compreensões,
|
|
7
|
+
// lambda, f-strings, from collections import deque, import math/heapq e as funções de sempre (len, range, min…).
|
|
8
|
+
// Fora disso o erro diz o que não é suportado e em que linha.
|
|
9
|
+
//
|
|
10
|
+
// Comentário no fim da linha vira a legenda do passo, com {expressão} trocada pelo valor: `j += 1 # agora j = {j}`.
|
|
11
|
+
|
|
12
|
+
export class PyError extends Error {
|
|
13
|
+
constructor(msg, line) { super(line ? `linha ${line}: ${msg}` : msg); this.pyLine = line; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------------------------------- tokens
|
|
17
|
+
const OPS = ["**=", "//=", ">>=", "<<=", "**", "//", "==", "!=", "<=", ">=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "->", "<<", ">>",
|
|
18
|
+
"+", "-", "*", "/", "%", "<", ">", "=", "(", ")", "[", "]", "{", "}", ",", ":", ".", ";", "&", "|", "^", "~"];
|
|
19
|
+
const KEYWORDS = new Set(["def", "class", "if", "elif", "else", "while", "for", "in", "not", "and", "or", "is", "return", "break", "continue",
|
|
20
|
+
"pass", "global", "nonlocal", "import", "from", "as", "lambda", "True", "False", "None", "del", "assert"]);
|
|
21
|
+
|
|
22
|
+
function tokenize(src) {
|
|
23
|
+
const lines = String(src).replace(/\r\n?/g, "\n").replace(/\t/g, " ").split("\n");
|
|
24
|
+
const toks = [], comments = {}, indents = [0];
|
|
25
|
+
let depth = 0, cont = false;
|
|
26
|
+
const opened = [];
|
|
27
|
+
const push = (t, v, line, col, end) => toks.push({ t, v, line, col, end });
|
|
28
|
+
for (let li = 0; li < lines.length; li++) {
|
|
29
|
+
const text = lines[li], line = li + 1;
|
|
30
|
+
let p = 0;
|
|
31
|
+
if (depth === 0 && !cont) {
|
|
32
|
+
const ind = text.match(/^ */)[0].length;
|
|
33
|
+
const rest = text.slice(ind);
|
|
34
|
+
if (!rest.trim() || rest.trimStart().startsWith("#")) continue;
|
|
35
|
+
if (ind > indents.at(-1)) { indents.push(ind); push("INDENT", null, line, 0, 0); }
|
|
36
|
+
while (ind < indents.at(-1)) { indents.pop(); push("DEDENT", null, line, 0, 0); }
|
|
37
|
+
if (ind !== indents.at(-1)) throw new PyError("indentação não bate com as linhas de cima", line);
|
|
38
|
+
p = ind;
|
|
39
|
+
}
|
|
40
|
+
let had = depth > 0 || cont;
|
|
41
|
+
while (p < text.length) {
|
|
42
|
+
const c = text[p];
|
|
43
|
+
if (c === " ") { p++; continue; }
|
|
44
|
+
if (c === "#") { if (had) comments[line] = text.slice(p + 1).trim(); break; }
|
|
45
|
+
if (c === "\\" && p === text.length - 1) break;
|
|
46
|
+
const num = /^(?:\d[\d_]*\.?[\d_]*(?:[eE][+-]?\d+)?|\.\d+(?:[eE][+-]?\d+)?)/.exec(text.slice(p));
|
|
47
|
+
if (num && /\d/.test(num[0][0] === "." ? num[0][1] : num[0][0])) { push("NUM", Number(num[0].replace(/_/g, "")), line, p, p + num[0].length); p += num[0].length; had = true; continue; }
|
|
48
|
+
const str = /^([fFrR]{0,2})("""|'''|"|')/.exec(text.slice(p));
|
|
49
|
+
if (str) {
|
|
50
|
+
const pre = str[1].toLowerCase(), q = str[2];
|
|
51
|
+
if (q.length === 3) throw new PyError("texto com três aspas não é suportado; use uma linha só", line);
|
|
52
|
+
let i = p + str[0].length, out = "";
|
|
53
|
+
while (i < text.length && text[i] !== q) {
|
|
54
|
+
if (text[i] === "\\" && !pre.includes("r")) { const n = text[++i]; out += n === "n" ? "\n" : n === "t" ? "\t" : n === "0" ? "\0" : n; i++; continue; }
|
|
55
|
+
out += text[i++];
|
|
56
|
+
}
|
|
57
|
+
if (text[i] !== q) throw new PyError("texto sem aspas de fechamento", line);
|
|
58
|
+
push(pre.includes("f") ? "FSTR" : "STR", out, line, p, i + 1); p = i + 1; had = true; continue;
|
|
59
|
+
}
|
|
60
|
+
const name = /^[A-Za-z_À-ɏ][\wÀ-ɏ]*/.exec(text.slice(p));
|
|
61
|
+
if (name) { push(KEYWORDS.has(name[0]) ? "KW" : "NAME", name[0], line, p, p + name[0].length); p += name[0].length; had = true; continue; }
|
|
62
|
+
const op = OPS.find((o) => text.startsWith(o, p));
|
|
63
|
+
if (!op) throw new PyError(`caractere inesperado "${c}"`, line);
|
|
64
|
+
if ("([{".includes(op)) { depth++; opened.push([op, line]); }
|
|
65
|
+
if (")]}".includes(op)) { depth = Math.max(0, depth - 1); opened.pop(); }
|
|
66
|
+
push("OP", op, line, p, p + op.length); p += op.length; had = true;
|
|
67
|
+
}
|
|
68
|
+
cont = text.trimEnd().endsWith("\\");
|
|
69
|
+
if (depth === 0 && had && !cont) push("NEWLINE", null, line, text.length, text.length);
|
|
70
|
+
}
|
|
71
|
+
if (opened.length) { const [op, l] = opened.at(-1); throw new PyError(`"${op}" aberto e não fechado`, l); }
|
|
72
|
+
while (indents.length > 1) { indents.pop(); push("DEDENT", null, lines.length, 0, 0); }
|
|
73
|
+
push("EOF", null, lines.length + 1, 0, 0);
|
|
74
|
+
return { toks, comments, lines };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------------------------------- parser
|
|
78
|
+
function parse(src) {
|
|
79
|
+
const { toks, comments, lines } = tokenize(src);
|
|
80
|
+
let i = 0;
|
|
81
|
+
const peek = (o = 0) => toks[i + o];
|
|
82
|
+
const is = (t, v, o = 0) => { const k = toks[i + o]; return k.t === t && (v === undefined || k.v === v); };
|
|
83
|
+
const isOp = (v) => is("OP", v), isKw = (v) => is("KW", v);
|
|
84
|
+
const next = () => toks[i++];
|
|
85
|
+
const fail = (msg, k = peek()) => { throw new PyError(msg, k.line); };
|
|
86
|
+
const expect = (t, v) => { if (!is(t, v)) fail(v ? `esperava "${v}"` : `esperava ${t === "NAME" ? "um nome" : t === "NEWLINE" ? "fim da linha" : t}`); return next(); };
|
|
87
|
+
const srcOf = (a, b) => (a.line === b.line ? lines[a.line - 1].slice(a.col, b.end) : lines[a.line - 1].slice(a.col).trim());
|
|
88
|
+
|
|
89
|
+
function program() {
|
|
90
|
+
const body = [];
|
|
91
|
+
while (!is("EOF")) { if (is("NEWLINE")) { next(); continue; } body.push(...statement()); }
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
function block() {
|
|
95
|
+
expect("OP", ":");
|
|
96
|
+
if (is("NEWLINE")) {
|
|
97
|
+
next();
|
|
98
|
+
if (!is("INDENT")) fail("esperava um bloco indentado");
|
|
99
|
+
next();
|
|
100
|
+
const body = [];
|
|
101
|
+
while (!is("DEDENT") && !is("EOF")) body.push(...statement());
|
|
102
|
+
if (is("DEDENT")) next();
|
|
103
|
+
return body;
|
|
104
|
+
}
|
|
105
|
+
return simpleLine();
|
|
106
|
+
}
|
|
107
|
+
function statement() {
|
|
108
|
+
const k = peek();
|
|
109
|
+
if (k.t === "KW") {
|
|
110
|
+
if (k.v === "def") return [funcDef()];
|
|
111
|
+
if (k.v === "class") return [classDef()];
|
|
112
|
+
if (k.v === "if") return [ifStmt()];
|
|
113
|
+
if (k.v === "while") { next(); const test = expr(), end = toks[i - 1]; return [{ k: "While", line: k.line, test, src: srcOf(toks[toks.indexOf(k) + 1], end), body: block() }]; }
|
|
114
|
+
if (k.v === "for") {
|
|
115
|
+
next();
|
|
116
|
+
const target = targetList();
|
|
117
|
+
expect("KW", "in");
|
|
118
|
+
const start = peek(); const iter = testList(); const end = toks[i - 1];
|
|
119
|
+
return [{ k: "For", line: k.line, target, iter, src: srcOf(start, end), body: block() }];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return simpleLine();
|
|
123
|
+
}
|
|
124
|
+
function simpleLine() {
|
|
125
|
+
const out = [small()];
|
|
126
|
+
while (isOp(";")) { next(); if (is("NEWLINE")) break; out.push(small()); }
|
|
127
|
+
expect("NEWLINE");
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function ifStmt() {
|
|
131
|
+
const k = next(); const start = peek(); const test = expr(); const src = srcOf(start, toks[i - 1]);
|
|
132
|
+
const body = block();
|
|
133
|
+
let orelse = [];
|
|
134
|
+
if (isKw("elif")) orelse = [ifStmt()];
|
|
135
|
+
else if (isKw("else")) { next(); orelse = block(); }
|
|
136
|
+
return { k: "If", line: k.line, test, src, body, orelse };
|
|
137
|
+
}
|
|
138
|
+
function params(close) {
|
|
139
|
+
const ps = [];
|
|
140
|
+
while (!isOp(close)) {
|
|
141
|
+
if (isOp("*") || isOp("**")) fail("*args e **kwargs não são suportados");
|
|
142
|
+
const name = expect("NAME").v;
|
|
143
|
+
if (close === ")" && isOp(":")) { next(); expr(); } // anotação de tipo: ignorada
|
|
144
|
+
let def = null;
|
|
145
|
+
if (isOp("=")) { next(); def = expr(); }
|
|
146
|
+
ps.push({ name, def });
|
|
147
|
+
if (!isOp(close)) expect("OP", ",");
|
|
148
|
+
}
|
|
149
|
+
return ps;
|
|
150
|
+
}
|
|
151
|
+
function funcDef() {
|
|
152
|
+
const k = next(); const name = expect("NAME").v;
|
|
153
|
+
expect("OP", "("); const ps = params(")"); expect("OP", ")");
|
|
154
|
+
if (isOp("->")) { next(); expr(); }
|
|
155
|
+
return { k: "Def", line: k.line, name, params: ps, body: block() };
|
|
156
|
+
}
|
|
157
|
+
function classDef() {
|
|
158
|
+
const k = next(); const name = expect("NAME").v;
|
|
159
|
+
if (isOp("(")) { next(); while (!isOp(")")) next(); next(); }
|
|
160
|
+
return { k: "Class", line: k.line, name, body: block() };
|
|
161
|
+
}
|
|
162
|
+
function small() {
|
|
163
|
+
const k = peek();
|
|
164
|
+
if (k.t === "KW") {
|
|
165
|
+
if (k.v === "pass") { next(); return { k: "Pass", line: k.line }; }
|
|
166
|
+
if (k.v === "break") { next(); return { k: "Break", line: k.line }; }
|
|
167
|
+
if (k.v === "continue") { next(); return { k: "Continue", line: k.line }; }
|
|
168
|
+
if (k.v === "return") { next(); const v = is("NEWLINE") || isOp(";") ? null : testList(); return { k: "Return", line: k.line, v, src: v ? srcOf(toks[toks.indexOf(k) + 1], toks[i - 1]) : "" }; }
|
|
169
|
+
if (k.v === "global" || k.v === "nonlocal") { next(); const names = [expect("NAME").v]; while (isOp(",")) { next(); names.push(expect("NAME").v); } return { k: "Global", line: k.line, names, nonlocal: k.v === "nonlocal" }; }
|
|
170
|
+
if (k.v === "import") { next(); const mods = []; do { if (mods.length) next(); let m = expect("NAME").v; while (isOp(".")) { next(); m += "." + expect("NAME").v; } let as = m; if (isKw("as")) { next(); as = expect("NAME").v; } mods.push({ m, as }); } while (isOp(",")); return { k: "Import", line: k.line, mods }; }
|
|
171
|
+
if (k.v === "from") { next(); let m = expect("NAME").v; while (isOp(".")) { next(); m += "." + expect("NAME").v; } expect("KW", "import"); const names = []; do { if (names.length) next(); const n = expect("NAME").v; let as = n; if (isKw("as")) { next(); as = expect("NAME").v; } names.push({ n, as }); } while (isOp(",")); return { k: "FromImport", line: k.line, m, names }; }
|
|
172
|
+
if (k.v === "del") { next(); const t = testList(); return { k: "Del", line: k.line, targets: t.k === "Tuple" ? t.elts : [t] }; }
|
|
173
|
+
if (k.v === "assert") { next(); const test = expr(); let msg = null; if (isOp(",")) { next(); msg = expr(); } return { k: "Assert", line: k.line, test, msg }; }
|
|
174
|
+
}
|
|
175
|
+
const start = peek();
|
|
176
|
+
const first = testList();
|
|
177
|
+
const aug = ["+=", "-=", "*=", "/=", "//=", "%=", "**=", "&=", "|=", "^=", ">>=", "<<="].find((o) => isOp(o));
|
|
178
|
+
if (aug) { next(); const v = testList(); checkTarget(first); return { k: "Aug", line: k.line, target: first, op: aug.slice(0, -1), v, src: srcOf(start, toks[i - 1]) }; }
|
|
179
|
+
if (isOp("=")) {
|
|
180
|
+
const targets = [first];
|
|
181
|
+
let v;
|
|
182
|
+
while (isOp("=")) { next(); v = testList(); targets.push(v); }
|
|
183
|
+
targets.pop(); targets.forEach(checkTarget);
|
|
184
|
+
return { k: "Assign", line: k.line, targets, v, src: srcOf(start, toks[i - 1]) };
|
|
185
|
+
}
|
|
186
|
+
if (isOp(":")) fail("anotação de tipo solta não é suportada");
|
|
187
|
+
return { k: "Expr", line: k.line, e: first, src: srcOf(start, toks[i - 1]) };
|
|
188
|
+
}
|
|
189
|
+
function checkTarget(t) {
|
|
190
|
+
if (t.k === "Name" || t.k === "Sub" || t.k === "Attr") return;
|
|
191
|
+
if (t.k === "Tuple" || t.k === "List") return t.elts.forEach(checkTarget);
|
|
192
|
+
throw new PyError("não dá para atribuir a isso", t.line);
|
|
193
|
+
}
|
|
194
|
+
function targetList() {
|
|
195
|
+
const first = bitOr();
|
|
196
|
+
if (!isOp(",")) return first;
|
|
197
|
+
const elts = [first];
|
|
198
|
+
while (isOp(",")) { next(); if (isKw("in")) break; elts.push(bitOr()); }
|
|
199
|
+
return { k: "Tuple", elts, line: first.line };
|
|
200
|
+
}
|
|
201
|
+
function testList() {
|
|
202
|
+
const first = expr();
|
|
203
|
+
if (!isOp(",")) return first;
|
|
204
|
+
const elts = [first];
|
|
205
|
+
while (isOp(",")) { next(); if (is("NEWLINE") || isOp("=") || isOp(")") || isOp(";")) break; elts.push(expr()); }
|
|
206
|
+
return { k: "Tuple", elts, line: first.line };
|
|
207
|
+
}
|
|
208
|
+
function expr() {
|
|
209
|
+
if (isKw("lambda")) {
|
|
210
|
+
const k = next(); const ps = params(":"); expect("OP", ":");
|
|
211
|
+
return { k: "Lambda", line: k.line, params: ps, body: expr() };
|
|
212
|
+
}
|
|
213
|
+
const e = orTest();
|
|
214
|
+
if (isKw("if")) { next(); const test = orTest(); expect("KW", "else"); return { k: "IfExp", test, body: e, orelse: expr(), line: e.line }; }
|
|
215
|
+
return e;
|
|
216
|
+
}
|
|
217
|
+
function orTest() { let e = andTest(); while (isKw("or")) { next(); e = { k: "BoolOp", op: "or", l: e, r: andTest(), line: e.line }; } return e; }
|
|
218
|
+
function andTest() { let e = notTest(); while (isKw("and")) { next(); e = { k: "BoolOp", op: "and", l: e, r: notTest(), line: e.line }; } return e; }
|
|
219
|
+
function notTest() { if (isKw("not")) { const k = next(); return { k: "Not", e: notTest(), line: k.line }; } return comparison(); }
|
|
220
|
+
function comparison() {
|
|
221
|
+
const l = bitOr(); const ops = [], rs = [];
|
|
222
|
+
for (;;) {
|
|
223
|
+
let op = null;
|
|
224
|
+
if (["<", ">", "==", "!=", "<=", ">="].some((o) => isOp(o))) op = next().v;
|
|
225
|
+
else if (isKw("in")) { next(); op = "in"; }
|
|
226
|
+
else if (isKw("not") && is("KW", "in", 1)) { next(); next(); op = "not in"; }
|
|
227
|
+
else if (isKw("is")) { next(); if (isKw("not")) { next(); op = "is not"; } else op = "is"; }
|
|
228
|
+
if (!op) break;
|
|
229
|
+
ops.push(op); rs.push(bitOr());
|
|
230
|
+
}
|
|
231
|
+
return ops.length ? { k: "Compare", l, ops, rs, line: l.line } : l;
|
|
232
|
+
}
|
|
233
|
+
const binLevel = (sub, opsList) => () => { let e = sub(); while (opsList.some((o) => isOp(o))) { const op = next().v; e = { k: "Bin", op, l: e, r: sub(), line: e.line }; } return e; };
|
|
234
|
+
function factor() {
|
|
235
|
+
if (isOp("-") || isOp("+") || isOp("~")) { const k = next(); return { k: "Unary", op: k.v, e: factor(), line: k.line }; }
|
|
236
|
+
const b = primary();
|
|
237
|
+
if (isOp("**")) { next(); return { k: "Bin", op: "**", l: b, r: factor(), line: b.line }; }
|
|
238
|
+
return b;
|
|
239
|
+
}
|
|
240
|
+
const term = binLevel(factor, ["*", "/", "//", "%"]);
|
|
241
|
+
const arith = binLevel(term, ["+", "-"]);
|
|
242
|
+
const shift = binLevel(arith, ["<<", ">>"]);
|
|
243
|
+
const bitAnd = binLevel(shift, ["&"]);
|
|
244
|
+
const bitXor = binLevel(bitAnd, ["^"]);
|
|
245
|
+
const bitOr = binLevel(bitXor, ["|"]);
|
|
246
|
+
function primary() {
|
|
247
|
+
let e = atom();
|
|
248
|
+
for (;;) {
|
|
249
|
+
if (isOp("(")) {
|
|
250
|
+
next(); const args = [], kw = [];
|
|
251
|
+
while (!isOp(")")) {
|
|
252
|
+
if (is("NAME") && is("OP", "=", 1)) { const n = next().v; next(); kw.push({ n, v: expr() }); }
|
|
253
|
+
else {
|
|
254
|
+
if (isOp("*")) fail("*args não é suportado");
|
|
255
|
+
const a = expr();
|
|
256
|
+
if (isKw("for")) args.push(comprehension("gen", a)); else args.push(a);
|
|
257
|
+
}
|
|
258
|
+
if (!isOp(")")) expect("OP", ",");
|
|
259
|
+
}
|
|
260
|
+
next();
|
|
261
|
+
e = { k: "Call", f: e, args, kw, line: e.line };
|
|
262
|
+
} else if (isOp("[")) {
|
|
263
|
+
next();
|
|
264
|
+
const idx = subscript();
|
|
265
|
+
expect("OP", "]");
|
|
266
|
+
e = { k: "Sub", v: e, i: idx, line: e.line };
|
|
267
|
+
} else if (isOp(".")) {
|
|
268
|
+
next(); e = { k: "Attr", v: e, name: expect("NAME").v, line: e.line };
|
|
269
|
+
} else return e;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function subscript() {
|
|
273
|
+
const part = () => (isOp(":") || isOp("]") ? null : expr());
|
|
274
|
+
const lo = part();
|
|
275
|
+
if (!isOp(":")) {
|
|
276
|
+
if (isOp(",")) { const elts = [lo]; while (isOp(",")) { next(); if (isOp("]")) break; elts.push(expr()); } return { k: "Tuple", elts, line: lo.line }; }
|
|
277
|
+
return lo;
|
|
278
|
+
}
|
|
279
|
+
next(); const hi = part(); let st = null;
|
|
280
|
+
if (isOp(":")) { next(); st = part(); }
|
|
281
|
+
return { k: "Slice", lo, hi, st };
|
|
282
|
+
}
|
|
283
|
+
function comprehension(kind, elt, key = null) {
|
|
284
|
+
const gens = [];
|
|
285
|
+
while (isKw("for")) {
|
|
286
|
+
next(); const target = targetList(); expect("KW", "in"); const iter = orTest(); const ifs = [];
|
|
287
|
+
while (isKw("if")) { next(); ifs.push(orTestNoCond()); }
|
|
288
|
+
gens.push({ target, iter, ifs });
|
|
289
|
+
}
|
|
290
|
+
return { k: "Comp", kind, elt, key, gens, line: elt.line };
|
|
291
|
+
}
|
|
292
|
+
function orTestNoCond() { return orTest(); }
|
|
293
|
+
function atom() {
|
|
294
|
+
const k = peek();
|
|
295
|
+
if (k.t === "NUM") { next(); return { k: "Const", v: k.v, line: k.line }; }
|
|
296
|
+
if (k.t === "STR" || k.t === "FSTR") {
|
|
297
|
+
const parts = [];
|
|
298
|
+
while (is("STR") || is("FSTR")) { const s = next(); parts.push(s.t === "FSTR" ? fstring(s) : { k: "Const", v: s.v, line: s.line }); }
|
|
299
|
+
return parts.length === 1 ? parts[0] : { k: "Concat", parts, line: k.line };
|
|
300
|
+
}
|
|
301
|
+
if (k.t === "KW") {
|
|
302
|
+
if (k.v === "True") { next(); return { k: "Const", v: true, line: k.line }; }
|
|
303
|
+
if (k.v === "False") { next(); return { k: "Const", v: false, line: k.line }; }
|
|
304
|
+
if (k.v === "None") { next(); return { k: "Const", v: null, line: k.line }; }
|
|
305
|
+
fail(`"${k.v}" fora do lugar`);
|
|
306
|
+
}
|
|
307
|
+
if (k.t === "NAME") { next(); return { k: "Name", id: k.v, line: k.line }; }
|
|
308
|
+
if (isOp("(")) {
|
|
309
|
+
next();
|
|
310
|
+
if (isOp(")")) { next(); return { k: "Tuple", elts: [], line: k.line }; }
|
|
311
|
+
const first = expr();
|
|
312
|
+
if (isKw("for")) { const c = comprehension("gen", first); expect("OP", ")"); return c; }
|
|
313
|
+
if (isOp(")")) { next(); return first; }
|
|
314
|
+
const elts = [first];
|
|
315
|
+
while (isOp(",")) { next(); if (isOp(")")) break; elts.push(expr()); }
|
|
316
|
+
expect("OP", ")");
|
|
317
|
+
return { k: "Tuple", elts, line: k.line };
|
|
318
|
+
}
|
|
319
|
+
if (isOp("[")) {
|
|
320
|
+
next();
|
|
321
|
+
if (isOp("]")) { next(); return { k: "List", elts: [], line: k.line }; }
|
|
322
|
+
const first = expr();
|
|
323
|
+
if (isKw("for")) { const c = comprehension("list", first); expect("OP", "]"); return c; }
|
|
324
|
+
const elts = [first];
|
|
325
|
+
while (isOp(",")) { next(); if (isOp("]")) break; elts.push(expr()); }
|
|
326
|
+
expect("OP", "]");
|
|
327
|
+
return { k: "List", elts, line: k.line };
|
|
328
|
+
}
|
|
329
|
+
if (isOp("{")) {
|
|
330
|
+
next();
|
|
331
|
+
if (isOp("}")) { next(); return { k: "Dict", keys: [], vals: [], line: k.line }; }
|
|
332
|
+
const first = expr();
|
|
333
|
+
if (isOp(":")) {
|
|
334
|
+
next(); const v = expr();
|
|
335
|
+
if (isKw("for")) { const c = comprehension("dict", v, first); expect("OP", "}"); return c; }
|
|
336
|
+
const keys = [first], vals = [v];
|
|
337
|
+
while (isOp(",")) { next(); if (isOp("}")) break; keys.push(expr()); expect("OP", ":"); vals.push(expr()); }
|
|
338
|
+
expect("OP", "}");
|
|
339
|
+
return { k: "Dict", keys, vals, line: k.line };
|
|
340
|
+
}
|
|
341
|
+
if (isKw("for")) { const c = comprehension("set", first); expect("OP", "}"); return c; }
|
|
342
|
+
const elts = [first];
|
|
343
|
+
while (isOp(",")) { next(); if (isOp("}")) break; elts.push(expr()); }
|
|
344
|
+
expect("OP", "}");
|
|
345
|
+
return { k: "SetLit", elts, line: k.line };
|
|
346
|
+
}
|
|
347
|
+
fail(k.t === "EOF" || k.t === "NEWLINE" ? "a linha terminou antes da hora" : `não esperava "${k.v ?? k.t}" aqui`);
|
|
348
|
+
}
|
|
349
|
+
function fstring(tok) {
|
|
350
|
+
const parts = [], s = tok.v;
|
|
351
|
+
let buf = "", p = 0;
|
|
352
|
+
while (p < s.length) {
|
|
353
|
+
if (s[p] === "{" && s[p + 1] === "{") { buf += "{"; p += 2; continue; }
|
|
354
|
+
if (s[p] === "}" && s[p + 1] === "}") { buf += "}"; p += 2; continue; }
|
|
355
|
+
if (s[p] === "{") {
|
|
356
|
+
let q = p + 1, d = 0;
|
|
357
|
+
while (q < s.length && !(s[q] === "}" && d === 0)) { if ("([{".includes(s[q])) d++; if (")]}".includes(s[q])) d--; q++; }
|
|
358
|
+
let code = s.slice(p + 1, q), spec = "";
|
|
359
|
+
const colon = code.search(/:(?![^[]*\])/);
|
|
360
|
+
if (colon > 0 && !code.slice(colon).includes("=")) { spec = code.slice(colon + 1); code = code.slice(0, colon); }
|
|
361
|
+
if (buf) parts.push({ k: "Const", v: buf, line: tok.line }); buf = "";
|
|
362
|
+
parts.push({ k: "Fmt", e: parseExpr(code, tok.line), spec, line: tok.line });
|
|
363
|
+
p = q + 1; continue;
|
|
364
|
+
}
|
|
365
|
+
buf += s[p++];
|
|
366
|
+
}
|
|
367
|
+
if (buf) parts.push({ k: "Const", v: buf, line: tok.line });
|
|
368
|
+
return { k: "Concat", parts, line: tok.line };
|
|
369
|
+
}
|
|
370
|
+
const body = program();
|
|
371
|
+
return { body, comments, lines };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function parseExpr(code, line = 1) {
|
|
375
|
+
let p;
|
|
376
|
+
try { p = parse(String(code).trim()); } catch (e) { throw new PyError(`expressão inválida: ${code}`, line); }
|
|
377
|
+
if (p.body.length !== 1 || p.body[0].k !== "Expr") throw new PyError(`expressão inválida: ${code}`, line);
|
|
378
|
+
return p.body[0].e;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ---------------------------------------------------------------------------------------------------- valores
|
|
382
|
+
const TUPLES = new WeakSet(), DEQUES = new WeakSet();
|
|
383
|
+
export const tuple = (items) => { const a = [...items]; TUPLES.add(a); return a; };
|
|
384
|
+
const deque = (items) => { const a = [...items]; DEQUES.add(a); return a; };
|
|
385
|
+
export const isTuple = (x) => Array.isArray(x) && TUPLES.has(x);
|
|
386
|
+
export const isDeque = (x) => Array.isArray(x) && DEQUES.has(x);
|
|
387
|
+
const keyOf = (k) => (Array.isArray(k) ? "t:" + JSON.stringify(k) : typeof k === "boolean" ? Number(k) : k);
|
|
388
|
+
export class PyDict {
|
|
389
|
+
constructor(entries = []) { this.m = new Map(); for (const [k, v] of entries) this.set(k, v); }
|
|
390
|
+
set(k, v) { if (Array.isArray(k) && !isTuple(k)) throw new PyError("lista não pode ser chave de dicionário (use tupla)"); const n = keyOf(k); const e = this.m.get(n); if (e) e[1] = v; else this.m.set(n, [k, v]); }
|
|
391
|
+
get(k) { return this.m.get(keyOf(k)); }
|
|
392
|
+
has(k) { return this.m.has(keyOf(k)); }
|
|
393
|
+
delete(k) { return this.m.delete(keyOf(k)); }
|
|
394
|
+
keys() { return [...this.m.values()].map((e) => e[0]); }
|
|
395
|
+
values() { return [...this.m.values()].map((e) => e[1]); }
|
|
396
|
+
entries() { return [...this.m.values()]; }
|
|
397
|
+
get size() { return this.m.size; }
|
|
398
|
+
}
|
|
399
|
+
export class PySet {
|
|
400
|
+
constructor(items = []) { this.m = new Map(); for (const x of items) this.add(x); }
|
|
401
|
+
add(x) { if (Array.isArray(x) && !isTuple(x)) throw new PyError("lista não pode entrar num conjunto"); this.m.set(keyOf(x), x); }
|
|
402
|
+
has(x) { return this.m.has(keyOf(x)); }
|
|
403
|
+
delete(x) { return this.m.delete(keyOf(x)); }
|
|
404
|
+
values() { return [...this.m.values()]; }
|
|
405
|
+
get size() { return this.m.size; }
|
|
406
|
+
}
|
|
407
|
+
class PyRange { constructor(a, b, s) { this.a = a; this.b = b; this.s = s; if (!s) throw new PyError("range com passo 0"); } get length() { return Math.max(0, Math.ceil((this.b - this.a) / this.s)); } at(i) { return this.a + i * this.s; } }
|
|
408
|
+
export class PyFunc { constructor(name, params, body, env, line, lambda = false) { Object.assign(this, { name, params, body, env, line, lambda }); } }
|
|
409
|
+
class PyBound { constructor(fn, self) { this.fn = fn; this.self = self; } }
|
|
410
|
+
class PyBuiltin { constructor(name, fn) { this.name = name; this.fn = fn; } }
|
|
411
|
+
export class PyClass { constructor(name, attrs) { this.name = name; this.attrs = attrs; } }
|
|
412
|
+
export class PyObj { constructor(cls) { this.cls = cls; this.attrs = Object.create(null); } }
|
|
413
|
+
class PyModule { constructor(name, attrs) { this.name = name; this.attrs = attrs; } }
|
|
414
|
+
|
|
415
|
+
const BREAK = { t: "break" }, CONTINUE = { t: "continue" };
|
|
416
|
+
class Ret { constructor(v) { this.v = v; } }
|
|
417
|
+
|
|
418
|
+
export function typeName(v) {
|
|
419
|
+
if (v === null || v === undefined) return "NoneType";
|
|
420
|
+
if (typeof v === "boolean") return "bool";
|
|
421
|
+
if (typeof v === "number") return Number.isInteger(v) ? "int" : "float";
|
|
422
|
+
if (typeof v === "string") return "str";
|
|
423
|
+
if (isTuple(v)) return "tuple";
|
|
424
|
+
if (isDeque(v)) return "deque";
|
|
425
|
+
if (Array.isArray(v)) return "list";
|
|
426
|
+
if (v instanceof PyDict) return "dict";
|
|
427
|
+
if (v instanceof PySet) return "set";
|
|
428
|
+
if (v instanceof PyRange) return "range";
|
|
429
|
+
if (v instanceof PyObj) return v.cls.name;
|
|
430
|
+
if (v instanceof PyClass) return "type";
|
|
431
|
+
return "function";
|
|
432
|
+
}
|
|
433
|
+
export function truthy(v) {
|
|
434
|
+
if (v === null || v === undefined || v === false || v === 0 || v === "") return false;
|
|
435
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
436
|
+
if (v instanceof PyDict || v instanceof PySet) return v.size > 0;
|
|
437
|
+
if (v instanceof PyRange) return v.length > 0;
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
const fmtNum = (n) => {
|
|
441
|
+
if (n === Infinity) return "inf"; if (n === -Infinity) return "-inf"; if (Number.isNaN(n)) return "nan";
|
|
442
|
+
if (Number.isInteger(n)) return String(n);
|
|
443
|
+
return String(+n.toPrecision(12));
|
|
444
|
+
};
|
|
445
|
+
export function repr(v, deep = 0, seen = new Set()) {
|
|
446
|
+
if (v === null || v === undefined) return "None";
|
|
447
|
+
if (v === true) return "True"; if (v === false) return "False";
|
|
448
|
+
if (typeof v === "number") return fmtNum(v);
|
|
449
|
+
if (typeof v === "string") return "'" + v.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") + "'";
|
|
450
|
+
if (typeof v === "object" && seen.has(v)) return "…";
|
|
451
|
+
if (typeof v === "object") seen = new Set([...seen, v]);
|
|
452
|
+
if (deep > 6) return "…";
|
|
453
|
+
const r = (x) => repr(x, deep + 1, seen);
|
|
454
|
+
if (isTuple(v)) return `(${v.map(r).join(", ")}${v.length === 1 ? "," : ""})`;
|
|
455
|
+
if (isDeque(v)) return `deque([${v.map(r).join(", ")}])`;
|
|
456
|
+
if (Array.isArray(v)) return `[${v.map(r).join(", ")}]`;
|
|
457
|
+
if (v instanceof PyDict) return `{${v.entries().map(([k, x]) => `${r(k)}: ${r(x)}`).join(", ")}}`;
|
|
458
|
+
if (v instanceof PySet) return v.size ? `{${v.values().map(r).join(", ")}}` : "set()";
|
|
459
|
+
if (v instanceof PyRange) return `range(${v.a}, ${v.b}${v.s !== 1 ? ", " + v.s : ""})`;
|
|
460
|
+
if (v instanceof PyObj) { const lab = nodeLabel(v); return `${v.cls.name}(${lab === undefined ? "" : r(lab)})`; }
|
|
461
|
+
if (v instanceof PyClass) return `<classe ${v.name}>`;
|
|
462
|
+
if (v instanceof PyFunc) return `<função ${v.name}>`;
|
|
463
|
+
if (v instanceof PyBound) return `<método ${v.fn.name}>`;
|
|
464
|
+
if (v instanceof PyBuiltin) return `<função ${v.name}>`;
|
|
465
|
+
if (v instanceof PyModule) return `<módulo ${v.name}>`;
|
|
466
|
+
return String(v);
|
|
467
|
+
}
|
|
468
|
+
export const str = (v) => (typeof v === "string" ? v : repr(v));
|
|
469
|
+
// valor que representa o nó (árvore, lista ligada): o primeiro atributo simples com nome de valor
|
|
470
|
+
const LABEL_KEYS = ["valor", "val", "value", "chave", "key", "dado", "data", "info", "item", "nome", "name", "v", "x"];
|
|
471
|
+
export function nodeLabel(o) {
|
|
472
|
+
const a = o.attrs;
|
|
473
|
+
for (const k of LABEL_KEYS) if (k in a && !(a[k] instanceof PyObj) && !Array.isArray(a[k])) return a[k];
|
|
474
|
+
for (const k in a) { const x = a[k]; if (x === null || typeof x !== "object") return x; }
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function pyEq(a, b) {
|
|
479
|
+
if (a === b) return true;
|
|
480
|
+
if ((typeof a === "number" || typeof a === "boolean") && (typeof b === "number" || typeof b === "boolean")) return Number(a) === Number(b);
|
|
481
|
+
if (Array.isArray(a) && Array.isArray(b)) return isTuple(a) === isTuple(b) && a.length === b.length && a.every((x, i) => pyEq(x, b[i]));
|
|
482
|
+
if (a instanceof PyDict && b instanceof PyDict) return a.size === b.size && a.entries().every(([k, v]) => b.has(k) && pyEq(v, b.get(k)[1]));
|
|
483
|
+
if (a instanceof PySet && b instanceof PySet) return a.size === b.size && a.values().every((x) => b.has(x));
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
function pyLt(a, b, line) {
|
|
487
|
+
if ((typeof a === "number" || typeof a === "boolean") && (typeof b === "number" || typeof b === "boolean")) return Number(a) < Number(b);
|
|
488
|
+
if (typeof a === "string" && typeof b === "string") return a < b;
|
|
489
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
490
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) if (!pyEq(a[i], b[i])) return pyLt(a[i], b[i], line);
|
|
491
|
+
return a.length < b.length;
|
|
492
|
+
}
|
|
493
|
+
throw new PyError(`não dá para comparar ${typeName(a)} com ${typeName(b)} usando <`, line);
|
|
494
|
+
}
|
|
495
|
+
const isNum = (x) => typeof x === "number" || typeof x === "boolean";
|
|
496
|
+
|
|
497
|
+
// ---------------------------------------------------------------------------------------------------- execução
|
|
498
|
+
export function runProgram(source, { call = null, maxOps = 50000, maxSteps = 4000, watch = [] } = {}) {
|
|
499
|
+
const ast = parse(source);
|
|
500
|
+
const steps = [];
|
|
501
|
+
const out = [];
|
|
502
|
+
let ops = 0;
|
|
503
|
+
const ids = new WeakMap(); let nextId = 1;
|
|
504
|
+
const idOf = (o) => { let n = ids.get(o); if (!n) { n = nextId++; ids.set(o, n); } return n; };
|
|
505
|
+
const globals = { vars: Object.create(null), parent: null, globalNames: new Set(), fname: "<programa>" };
|
|
506
|
+
const stack = [{ name: "<programa>", env: globals, line: 0 }];
|
|
507
|
+
let ev = { reads: [], writes: [], matches: [] };
|
|
508
|
+
|
|
509
|
+
const tick = (line) => { if (++ops > maxOps) throw new PyError(`passou de ${maxOps} operações: laço sem fim, ou a entrada é grande demais para o passo a passo`, line); };
|
|
510
|
+
|
|
511
|
+
// ---------------- retrato do estado (para a tela): variáveis de cada chamada e o que elas apontam
|
|
512
|
+
const watched = watch.map((w) => { try { return [w, parseExpr(w)]; } catch { return [w, null]; } });
|
|
513
|
+
function snapshot(kind, line, text, env, extra = {}) {
|
|
514
|
+
if (steps.length >= maxSteps) throw new PyError(`passou de ${maxSteps} passos (laço sem fim? senão, use uma entrada menor)`, line);
|
|
515
|
+
const heap = {};
|
|
516
|
+
const val = (v) => {
|
|
517
|
+
if (v === null || v === undefined) return null;
|
|
518
|
+
if (typeof v !== "object") return v;
|
|
519
|
+
if (v instanceof PyRange || v instanceof PyFunc || v instanceof PyBuiltin || v instanceof PyBound || v instanceof PyClass || v instanceof PyModule) return { repr: repr(v) };
|
|
520
|
+
const id = idOf(v);
|
|
521
|
+
if (!heap[id]) {
|
|
522
|
+
heap[id] = { t: typeName(v) };
|
|
523
|
+
const node = heap[id];
|
|
524
|
+
if (Array.isArray(v)) node.items = v.map(val);
|
|
525
|
+
else if (v instanceof PyDict) node.entries = v.entries().map(([k, x]) => [val(k), val(x)]);
|
|
526
|
+
else if (v instanceof PySet) node.items = v.values().map(val);
|
|
527
|
+
else if (v instanceof PyObj) { node.cls = v.cls.name; node.attrs = Object.fromEntries(Object.keys(v.attrs).map((k) => [k, val(v.attrs[k])])); node.label = val(nodeLabel(v)); }
|
|
528
|
+
}
|
|
529
|
+
return { ref: id };
|
|
530
|
+
};
|
|
531
|
+
const frames = stack.map((f) => ({ name: f.name, params: f.params, vars: Object.fromEntries(Object.keys(f.env.vars).filter((k) => !isHidden(f.env.vars[k])).map((k) => [k, val(f.env.vars[k])])) }));
|
|
532
|
+
const w = {};
|
|
533
|
+
for (const [code, e] of watched) { if (!e) continue; try { const v = evalExprQuiet(e, env); if (v === null || typeof v !== "object") w[code] = v; else w[code] = val(v); } catch { /* ainda não existe nesta chamada */ } }
|
|
534
|
+
const st = { kind, line, text, frames, heap, reads: ev.reads, writes: ev.writes, matches: ev.matches, out: out.slice(-6), watch: w, ...extra };
|
|
535
|
+
steps.push(st);
|
|
536
|
+
ev = { reads: [], writes: [], matches: [] };
|
|
537
|
+
return st;
|
|
538
|
+
}
|
|
539
|
+
const isHidden = (v) => v instanceof PyFunc || v instanceof PyBuiltin || v instanceof PyClass || v instanceof PyModule;
|
|
540
|
+
|
|
541
|
+
// legenda do passo: o comentário da linha (com {expressão}) ou uma descrição do que aconteceu
|
|
542
|
+
function caption(line, env, auto) {
|
|
543
|
+
const c = ast.comments[line];
|
|
544
|
+
if (c) {
|
|
545
|
+
try {
|
|
546
|
+
return c.replace(/\{([^{}]+)\}/g, (_, code) => { try { return str(evalExprQuiet(parseExpr(code, line), env)); } catch { return `{${code}}`; } });
|
|
547
|
+
} catch { return c; }
|
|
548
|
+
}
|
|
549
|
+
return auto;
|
|
550
|
+
}
|
|
551
|
+
const step = (kind, line, env, auto, extra) => snapshot(kind, line, caption(line, env, auto), env, extra);
|
|
552
|
+
|
|
553
|
+
// ---------------- nomes
|
|
554
|
+
function lookup(name, env, line) {
|
|
555
|
+
for (let e = env; e; e = e.parent) if (name in e.vars) return e.vars[name];
|
|
556
|
+
if (name in globals.vars) return globals.vars[name];
|
|
557
|
+
if (name in BUILTINS) return BUILTINS[name];
|
|
558
|
+
throw new PyError(`"${name}" não existe (ainda não recebeu valor?)`, line);
|
|
559
|
+
}
|
|
560
|
+
function assignName(name, v, env) {
|
|
561
|
+
if (env.globalNames.has(name)) { globals.vars[name] = v; return; }
|
|
562
|
+
if (env.nonlocalNames?.has(name)) { for (let e = env.parent; e; e = e.parent) if (name in e.vars) { e.vars[name] = v; return; } }
|
|
563
|
+
env.vars[name] = v;
|
|
564
|
+
}
|
|
565
|
+
function assign(target, v, env) {
|
|
566
|
+
if (target.k === "Name") return assignName(target.id, v, env);
|
|
567
|
+
if (target.k === "Tuple" || target.k === "List") {
|
|
568
|
+
const items = [...iterate(v, target.line)];
|
|
569
|
+
if (items.length !== target.elts.length) throw new PyError(`esperava ${target.elts.length} valores para desempacotar e veio ${items.length}`, target.line);
|
|
570
|
+
return target.elts.forEach((t, i) => assign(t, items[i], env));
|
|
571
|
+
}
|
|
572
|
+
if (target.k === "Sub") {
|
|
573
|
+
const obj = evalExpr(target.v, env), idx = evalExpr(target.i, env);
|
|
574
|
+
if (Array.isArray(obj)) {
|
|
575
|
+
if (isTuple(obj)) throw new PyError("tupla não muda depois de criada", target.line);
|
|
576
|
+
if (target.i.k === "Slice") throw new PyError("atribuir a uma fatia não é suportado", target.line);
|
|
577
|
+
const i = normIndex(obj, idx, target.line);
|
|
578
|
+
obj[i] = v; ev.writes.push([idOf(obj), i]); return;
|
|
579
|
+
}
|
|
580
|
+
if (obj instanceof PyDict) { obj.set(idx, v); ev.writes.push([idOf(obj), keyOf(idx)]); return; }
|
|
581
|
+
if (typeof obj === "string") throw new PyError("texto não muda depois de criado (monte um novo)", target.line);
|
|
582
|
+
throw new PyError(`${typeName(obj)} não aceita [ ] = `, target.line);
|
|
583
|
+
}
|
|
584
|
+
if (target.k === "Attr") {
|
|
585
|
+
const obj = evalExpr(target.v, env);
|
|
586
|
+
if (obj instanceof PyObj) { obj.attrs[target.name] = v; ev.writes.push([idOf(obj), target.name]); return; }
|
|
587
|
+
if (obj instanceof PyClass) { obj.attrs[target.name] = v; return; }
|
|
588
|
+
throw new PyError(`não dá para criar atributo em ${typeName(obj)}`, target.line);
|
|
589
|
+
}
|
|
590
|
+
throw new PyError("não dá para atribuir a isso", target.line);
|
|
591
|
+
}
|
|
592
|
+
function normIndex(arr, idx, line) {
|
|
593
|
+
if (typeof idx === "boolean") idx = Number(idx);
|
|
594
|
+
if (typeof idx !== "number" || !Number.isInteger(idx)) throw new PyError(`índice precisa ser inteiro (veio ${repr(idx)})`, line);
|
|
595
|
+
const n = arr.length, i = idx < 0 ? n + idx : idx;
|
|
596
|
+
if (i < 0 || i >= n) throw new PyError(`índice ${idx} fora da lista (tamanho ${n})`, line);
|
|
597
|
+
return i;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ---------------- expressões
|
|
601
|
+
function evalExpr(e, env) {
|
|
602
|
+
switch (e.k) {
|
|
603
|
+
case "Const": return e.v;
|
|
604
|
+
case "Name": return lookup(e.id, env, e.line);
|
|
605
|
+
case "Concat": return e.parts.map((p) => (p.k === "Fmt" ? format(evalExpr(p.e, env), p.spec, p.line) : evalExpr(p, env))).join("");
|
|
606
|
+
case "Tuple": return tuple(e.elts.map((x) => evalExpr(x, env)));
|
|
607
|
+
case "List": return e.elts.map((x) => evalExpr(x, env));
|
|
608
|
+
case "SetLit": return new PySet(e.elts.map((x) => evalExpr(x, env)));
|
|
609
|
+
case "Dict": return new PyDict(e.keys.map((k, i) => [evalExpr(k, env), evalExpr(e.vals[i], env)]));
|
|
610
|
+
case "Comp": return comprehension(e, env);
|
|
611
|
+
case "Not": return !truthy(evalExpr(e.e, env));
|
|
612
|
+
case "BoolOp": { const l = evalExpr(e.l, env); if (e.op === "and" ? !truthy(l) : truthy(l)) return l; return evalExpr(e.r, env); }
|
|
613
|
+
case "IfExp": return truthy(evalExpr(e.test, env)) ? evalExpr(e.body, env) : evalExpr(e.orelse, env);
|
|
614
|
+
case "Unary": { const v = evalExpr(e.e, env); if (!isNum(v)) throw new PyError(`sinal ${e.op} em ${typeName(v)}`, e.line); return e.op === "-" ? -v : e.op === "+" ? +v : ~v; }
|
|
615
|
+
case "Bin": return binop(e.op, evalExpr(e.l, env), evalExpr(e.r, env), e.line);
|
|
616
|
+
case "Compare": {
|
|
617
|
+
let l = evalExpr(e.l, env);
|
|
618
|
+
const lread = e.l.k === "Sub" ? ev.reads.at(-1) : null;
|
|
619
|
+
for (let k = 0; k < e.ops.length; k++) {
|
|
620
|
+
const r = evalExpr(e.rs[k], env);
|
|
621
|
+
const rread = e.rs[k].k === "Sub" ? ev.reads.at(-1) : null;
|
|
622
|
+
const ok = compare(e.ops[k], l, r, e.line);
|
|
623
|
+
if ((e.ops[k] === "==" || e.ops[k] === "!=") && (lread || rread) && k === 0) ev.matches.push({ reads: [lread, rread].filter(Boolean), eq: pyEq(l, r) });
|
|
624
|
+
if (!ok) return false;
|
|
625
|
+
l = r;
|
|
626
|
+
}
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
case "Sub": {
|
|
630
|
+
const obj = evalExpr(e.v, env);
|
|
631
|
+
if (e.i.k === "Slice") {
|
|
632
|
+
const lo = e.i.lo ? evalExpr(e.i.lo, env) : null, hi = e.i.hi ? evalExpr(e.i.hi, env) : null, st = e.i.st ? evalExpr(e.i.st, env) : null;
|
|
633
|
+
return slice(obj, lo, hi, st, e.line);
|
|
634
|
+
}
|
|
635
|
+
const idx = evalExpr(e.i, env);
|
|
636
|
+
if (Array.isArray(obj)) { const i = normIndex(obj, idx, e.line); ev.reads.push([idOf(obj), i]); return obj[i]; }
|
|
637
|
+
if (typeof obj === "string") { const i = normIndex(obj, idx, e.line); ev.reads.push(["s", obj, i]); return obj[i]; }
|
|
638
|
+
if (obj instanceof PyDict) { const en = obj.get(idx); if (!en) throw new PyError(`a chave ${repr(idx)} não está no dicionário`, e.line); ev.reads.push([idOf(obj), keyOf(idx)]); return en[1]; }
|
|
639
|
+
if (obj instanceof PyRange) { const i = normIndex({ length: obj.length }, idx, e.line); return obj.at(i); }
|
|
640
|
+
throw new PyError(`${typeName(obj)} não aceita [ ]`, e.line);
|
|
641
|
+
}
|
|
642
|
+
case "Attr": return getAttr(evalExpr(e.v, env), e.name, e.line);
|
|
643
|
+
case "Call": {
|
|
644
|
+
const f = evalExpr(e.f, env);
|
|
645
|
+
const args = e.args.map((a) => evalExpr(a, env));
|
|
646
|
+
const kw = Object.fromEntries(e.kw.map((k) => [k.n, evalExpr(k.v, env)]));
|
|
647
|
+
return callValue(f, args, kw, e.line);
|
|
648
|
+
}
|
|
649
|
+
case "Lambda": return new PyFunc("lambda", e.params, e.body, env, e.line, true);
|
|
650
|
+
case "Slice": throw new PyError("fatia fora de [ ]", e.line);
|
|
651
|
+
default: throw new PyError(`expressão não suportada (${e.k})`, e.line);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function format(v, spec, line) {
|
|
655
|
+
if (!spec) return str(v);
|
|
656
|
+
const m = /^(\d*)(?:\.(\d+))?([fdse%]?)$/.exec(spec);
|
|
657
|
+
if (!m) throw new PyError(`formato {:${spec}} não suportado`, line);
|
|
658
|
+
let s = m[3] === "f" || m[2] ? Number(v).toFixed(Number(m[2] ?? 6)) : m[3] === "%" ? (Number(v) * 100).toFixed(Number(m[2] ?? 6)) + "%" : str(v);
|
|
659
|
+
if (m[1]) s = s.padStart(Number(m[1]));
|
|
660
|
+
return s;
|
|
661
|
+
}
|
|
662
|
+
function comprehension(e, env) {
|
|
663
|
+
const inner = { vars: Object.create(null), parent: env, globalNames: new Set(), fname: env.fname };
|
|
664
|
+
const res = [];
|
|
665
|
+
const loop = (g) => {
|
|
666
|
+
if (g === e.gens.length) { res.push(e.kind === "dict" ? [evalExpr(e.key, inner), evalExpr(e.elt, inner)] : evalExpr(e.elt, inner)); return; }
|
|
667
|
+
const gen = e.gens[g];
|
|
668
|
+
for (const x of iterate(evalExpr(gen.iter, inner), e.line)) {
|
|
669
|
+
tick(e.line);
|
|
670
|
+
assign(gen.target, x, inner);
|
|
671
|
+
if (gen.ifs.every((c) => truthy(evalExpr(c, inner)))) loop(g + 1);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
loop(0);
|
|
675
|
+
if (e.kind === "set") return new PySet(res);
|
|
676
|
+
if (e.kind === "dict") return new PyDict(res);
|
|
677
|
+
return res;
|
|
678
|
+
}
|
|
679
|
+
function slice(obj, lo, hi, st, line) {
|
|
680
|
+
const step = st ?? 1;
|
|
681
|
+
if (step === 0) throw new PyError("fatia com passo 0", line);
|
|
682
|
+
const n = obj.length;
|
|
683
|
+
const norm = (x, d) => (x === null ? d : x < 0 ? Math.max(step < 0 ? -1 : 0, n + x) : Math.min(step < 0 ? n - 1 : n, x));
|
|
684
|
+
const a = norm(lo, step > 0 ? 0 : n - 1), b = norm(hi, step > 0 ? n : -1);
|
|
685
|
+
const items = [];
|
|
686
|
+
for (let i = a; step > 0 ? i < b : i > b; i += step) items.push(typeof obj === "string" ? obj[i] : obj[i]);
|
|
687
|
+
if (typeof obj === "string") return items.join("");
|
|
688
|
+
if (Array.isArray(obj)) return isTuple(obj) ? tuple(items) : items;
|
|
689
|
+
throw new PyError(`${typeName(obj)} não aceita fatia`, line);
|
|
690
|
+
}
|
|
691
|
+
function compare(op, a, b, line) {
|
|
692
|
+
switch (op) {
|
|
693
|
+
case "==": return pyEq(a, b);
|
|
694
|
+
case "!=": return !pyEq(a, b);
|
|
695
|
+
case "<": return pyLt(a, b, line);
|
|
696
|
+
case ">": return pyLt(b, a, line);
|
|
697
|
+
case "<=": return !pyLt(b, a, line);
|
|
698
|
+
case ">=": return !pyLt(a, b, line);
|
|
699
|
+
case "is": return a === b || (a == null && b == null);
|
|
700
|
+
case "is not": return !(a === b || (a == null && b == null));
|
|
701
|
+
case "in": return contains(b, a, line);
|
|
702
|
+
case "not in": return !contains(b, a, line);
|
|
703
|
+
}
|
|
704
|
+
throw new PyError(`comparação ${op}`, line);
|
|
705
|
+
}
|
|
706
|
+
function contains(c, x, line) {
|
|
707
|
+
if (typeof c === "string") { if (typeof x !== "string") throw new PyError("in num texto precisa de texto", line); return c.includes(x); }
|
|
708
|
+
if (Array.isArray(c)) return c.some((y) => pyEq(x, y));
|
|
709
|
+
if (c instanceof PyDict || c instanceof PySet) return c.has(x);
|
|
710
|
+
if (c instanceof PyRange) return typeof x === "number" && [...iterate(c)].includes(x);
|
|
711
|
+
throw new PyError(`in não funciona com ${typeName(c)}`, line);
|
|
712
|
+
}
|
|
713
|
+
function binop(op, a, b, line) {
|
|
714
|
+
if (op === "+") {
|
|
715
|
+
if (isNum(a) && isNum(b)) return Number(a) + Number(b);
|
|
716
|
+
if (typeof a === "string" && typeof b === "string") return a + b;
|
|
717
|
+
if (Array.isArray(a) && Array.isArray(b)) { const r = [...a, ...b]; return isTuple(a) ? tuple(r) : r; }
|
|
718
|
+
throw new PyError(`não dá para somar ${typeName(a)} com ${typeName(b)}`, line);
|
|
719
|
+
}
|
|
720
|
+
if (op === "*") {
|
|
721
|
+
if (isNum(a) && isNum(b)) return Number(a) * Number(b);
|
|
722
|
+
const [s, n] = typeof b === "number" ? [a, b] : [b, a];
|
|
723
|
+
if (typeof s === "string" && typeof n === "number") return s.repeat(Math.max(0, n));
|
|
724
|
+
if (Array.isArray(s) && typeof n === "number") { if (s.length * n > 100000) throw new PyError("lista grande demais", line); const r = []; for (let k = 0; k < n; k++) r.push(...s); return isTuple(s) ? tuple(r) : r; }
|
|
725
|
+
throw new PyError(`não dá para multiplicar ${typeName(a)} por ${typeName(b)}`, line);
|
|
726
|
+
}
|
|
727
|
+
if (op === "%" && typeof a === "string") throw new PyError("formatação com % não é suportada; use f-string", line);
|
|
728
|
+
if (!isNum(a) || !isNum(b)) {
|
|
729
|
+
if (op === "-" && a instanceof PySet && b instanceof PySet) return new PySet(a.values().filter((x) => !b.has(x)));
|
|
730
|
+
if (op === "|" && a instanceof PySet && b instanceof PySet) return new PySet([...a.values(), ...b.values()]);
|
|
731
|
+
if (op === "&" && a instanceof PySet && b instanceof PySet) return new PySet(a.values().filter((x) => b.has(x)));
|
|
732
|
+
throw new PyError(`operação ${op} entre ${typeName(a)} e ${typeName(b)}`, line);
|
|
733
|
+
}
|
|
734
|
+
a = Number(a); b = Number(b);
|
|
735
|
+
switch (op) {
|
|
736
|
+
case "-": return a - b;
|
|
737
|
+
case "/": if (b === 0) throw new PyError("divisão por zero", line); return a / b;
|
|
738
|
+
case "//": if (b === 0) throw new PyError("divisão por zero", line); return Math.floor(a / b);
|
|
739
|
+
case "%": if (b === 0) throw new PyError("divisão por zero", line); return ((a % b) + b) % b;
|
|
740
|
+
case "**": return a ** b;
|
|
741
|
+
case "<<": return a << b; case ">>": return a >> b;
|
|
742
|
+
case "&": return a & b; case "|": return a | b; case "^": return a ^ b;
|
|
743
|
+
}
|
|
744
|
+
throw new PyError(`operação ${op}`, line);
|
|
745
|
+
}
|
|
746
|
+
function* iterate(v, line) {
|
|
747
|
+
if (Array.isArray(v)) { for (let i = 0; i < v.length; i++) yield v[i]; return; }
|
|
748
|
+
if (typeof v === "string") { yield* v; return; }
|
|
749
|
+
if (v instanceof PyRange) { for (let i = 0; i < v.length; i++) yield v.at(i); return; }
|
|
750
|
+
if (v instanceof PyDict) { yield* v.keys(); return; }
|
|
751
|
+
if (v instanceof PySet) { yield* v.values(); return; }
|
|
752
|
+
throw new PyError(`não dá para percorrer ${typeName(v)} com for`, line);
|
|
753
|
+
}
|
|
754
|
+
const list = (v, line) => [...iterate(v, line)];
|
|
755
|
+
|
|
756
|
+
function getAttr(o, name, line) {
|
|
757
|
+
if (o instanceof PyObj) {
|
|
758
|
+
if (name in o.attrs) { ev.reads.push([idOf(o), name]); return o.attrs[name]; }
|
|
759
|
+
const m = o.cls.attrs[name];
|
|
760
|
+
if (m instanceof PyFunc) return new PyBound(m, o);
|
|
761
|
+
if (m !== undefined) return m;
|
|
762
|
+
throw new PyError(`${o.cls.name} não tem "${name}"`, line);
|
|
763
|
+
}
|
|
764
|
+
if (o instanceof PyClass) { if (name in o.attrs) return o.attrs[name]; throw new PyError(`${o.name} não tem "${name}"`, line); }
|
|
765
|
+
if (o instanceof PyModule) { if (name in o.attrs) return o.attrs[name]; throw new PyError(`${o.name}.${name} não é suportado`, line); }
|
|
766
|
+
const m = METHODS[typeName(o) === "deque" ? "deque" : typeName(o)]?.[name] ?? (Array.isArray(o) ? METHODS.list[name] : undefined);
|
|
767
|
+
if (m) return new PyBuiltin(name, (args, kw) => m(o, args, kw, line));
|
|
768
|
+
throw new PyError(`${typeName(o)} não tem "${name}"`, line);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function callValue(f, args, kw, line) {
|
|
772
|
+
tick(line);
|
|
773
|
+
if (f instanceof PyBuiltin) return f.fn(args, kw, line);
|
|
774
|
+
if (f instanceof PyBound) return callFunc(f.fn, [f.self, ...args], kw, line);
|
|
775
|
+
if (f instanceof PyFunc) return callFunc(f, args, kw, line);
|
|
776
|
+
if (f instanceof PyClass) {
|
|
777
|
+
const o = new PyObj(f);
|
|
778
|
+
const init = f.attrs.__init__;
|
|
779
|
+
if (init instanceof PyFunc) callFunc(init, [o, ...args], kw, line);
|
|
780
|
+
else if (args.length) throw new PyError(`${f.name}() não recebe valores (falta o __init__)`, line);
|
|
781
|
+
return o;
|
|
782
|
+
}
|
|
783
|
+
throw new PyError(`${typeName(f)} não é função`, line);
|
|
784
|
+
}
|
|
785
|
+
function callFunc(fn, args, kw, line) {
|
|
786
|
+
if (stack.length > 60) throw new PyError("recursão funda demais (mais de 60 chamadas abertas)", line);
|
|
787
|
+
const env = { vars: Object.create(null), parent: fn.env, globalNames: new Set(), nonlocalNames: new Set(), fname: fn.name };
|
|
788
|
+
fn.params.forEach((p, k) => {
|
|
789
|
+
if (k < args.length) env.vars[p.name] = args[k];
|
|
790
|
+
else if (p.name in kw) env.vars[p.name] = kw[p.name];
|
|
791
|
+
else if (p.def) env.vars[p.name] = evalExpr(p.def, fn.env);
|
|
792
|
+
else throw new PyError(`${fn.name}() precisa do valor de "${p.name}"`, line);
|
|
793
|
+
});
|
|
794
|
+
if (args.length > fn.params.length) throw new PyError(`${fn.name}() recebeu valores demais`, line);
|
|
795
|
+
if (fn.lambda) return evalExpr(fn.body, env);
|
|
796
|
+
const shown = fn.params.filter((p) => p.name !== "self").map((p) => repr(env.vars[p.name])).join(", ");
|
|
797
|
+
stack.push({ name: fn.name, env, line: fn.line, params: fn.params.map((p) => p.name) });
|
|
798
|
+
try {
|
|
799
|
+
step("call", fn.line, env, `Chama ${fn.name}(${shown.length > 60 ? shown.slice(0, 57) + "…" : shown}).`);
|
|
800
|
+
const r = execBlock(fn.body, env);
|
|
801
|
+
const v = r instanceof Ret ? r.v : null;
|
|
802
|
+
return v;
|
|
803
|
+
} finally { stack.pop(); }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// ---------------- comandos
|
|
807
|
+
function execBlock(body, env) {
|
|
808
|
+
for (const s of body) {
|
|
809
|
+
const r = exec(s, env);
|
|
810
|
+
if (r) return r;
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
function exec(s, env) {
|
|
815
|
+
tick(s.line);
|
|
816
|
+
switch (s.k) {
|
|
817
|
+
case "Expr": {
|
|
818
|
+
const v = evalExpr(s.e, env);
|
|
819
|
+
step("line", s.line, env, (s.e.k === "Call" ? callText(s.e, env, s.src) : s.src) + (v !== null && v !== undefined ? ` → ${repr(v)}` : ""));
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
case "Assign": {
|
|
823
|
+
const v = evalExpr(s.v, env);
|
|
824
|
+
for (const t of s.targets) assign(t, v, env);
|
|
825
|
+
step("line", s.line, env, describeAssign(s.targets, env, s.src));
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
case "Aug": {
|
|
829
|
+
const cur = evalExpr(s.target, env);
|
|
830
|
+
const v = binop(s.op, cur, evalExpr(s.v, env), s.line);
|
|
831
|
+
if (Array.isArray(cur) && s.op === "+" && !isTuple(cur)) { cur.length = 0; cur.push(...v); }
|
|
832
|
+
else assign(s.target, v, env);
|
|
833
|
+
step("line", s.line, env, describeAssign([s.target], env, s.src));
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
case "If": {
|
|
837
|
+
const ok = truthy(evalExpr(s.test, env));
|
|
838
|
+
step("test", s.line, env, testText(s.test, env, s.src, ok), { test: ok });
|
|
839
|
+
return ok ? execBlock(s.body, env) : execBlock(s.orelse, env);
|
|
840
|
+
}
|
|
841
|
+
case "While": {
|
|
842
|
+
for (;;) {
|
|
843
|
+
const ok = truthy(evalExpr(s.test, env));
|
|
844
|
+
step("test", s.line, env, testText(s.test, env, s.src, ok), { test: ok });
|
|
845
|
+
if (!ok) return null;
|
|
846
|
+
const r = execBlock(s.body, env);
|
|
847
|
+
if (r === BREAK) return null;
|
|
848
|
+
if (r === CONTINUE) continue;
|
|
849
|
+
if (r) return r;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
case "For": {
|
|
853
|
+
const it = evalExpr(s.iter, env);
|
|
854
|
+
const items = Array.isArray(it) ? [...it] : list(it, s.line);
|
|
855
|
+
for (const x of items) {
|
|
856
|
+
tick(s.line);
|
|
857
|
+
assign(s.target, x, env);
|
|
858
|
+
step("loop", s.line, env, describeAssign([s.target], env, s.src));
|
|
859
|
+
const r = execBlock(s.body, env);
|
|
860
|
+
if (r === BREAK) return null;
|
|
861
|
+
if (r === CONTINUE) continue;
|
|
862
|
+
if (r) return r;
|
|
863
|
+
}
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
case "Def": { assignName(s.name, new PyFunc(s.name, s.params, s.body, env, s.line), env); return null; }
|
|
867
|
+
case "Class": {
|
|
868
|
+
const cenv = { vars: Object.create(null), parent: env, globalNames: new Set(), fname: s.name };
|
|
869
|
+
execBlock(s.body, cenv);
|
|
870
|
+
for (const k in cenv.vars) if (cenv.vars[k] instanceof PyFunc) cenv.vars[k].env = env; // método não enxerga a classe como escopo (igual ao Python)
|
|
871
|
+
assignName(s.name, new PyClass(s.name, cenv.vars), env);
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
case "Return": {
|
|
875
|
+
const v = s.v ? evalExpr(s.v, env) : null;
|
|
876
|
+
step("return", s.line, env, `Devolve ${repr(v).length > 60 ? repr(v).slice(0, 57) + "…" : repr(v)}.`, { ret: repr(v) });
|
|
877
|
+
return new Ret(v);
|
|
878
|
+
}
|
|
879
|
+
case "Break": return BREAK;
|
|
880
|
+
case "Continue": return CONTINUE;
|
|
881
|
+
case "Pass": return null;
|
|
882
|
+
case "Global": if (env !== globals) s.names.forEach((n) => (s.nonlocal ? env.nonlocalNames : env.globalNames).add(n)); return null;
|
|
883
|
+
case "Import": for (const { m, as } of s.mods) { if (!MODULES[m]) throw new PyError(`módulo ${m} não é suportado (tem: ${Object.keys(MODULES).join(", ")})`, s.line); assignName(as, MODULES[m], env); } return null;
|
|
884
|
+
case "FromImport": {
|
|
885
|
+
const mod = MODULES[s.m];
|
|
886
|
+
if (!mod) throw new PyError(`módulo ${s.m} não é suportado (tem: ${Object.keys(MODULES).join(", ")})`, s.line);
|
|
887
|
+
for (const { n, as } of s.names) { if (!(n in mod.attrs)) throw new PyError(`${s.m}.${n} não é suportado`, s.line); assignName(as, mod.attrs[n], env); }
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
case "Del": {
|
|
891
|
+
for (const t of s.targets) {
|
|
892
|
+
if (t.k === "Name") delete env.vars[t.id];
|
|
893
|
+
else if (t.k === "Sub") { const o = evalExpr(t.v, env), k = evalExpr(t.i, env); if (Array.isArray(o)) o.splice(normIndex(o, k, s.line), 1); else if (o instanceof PyDict) o.delete(k); }
|
|
894
|
+
}
|
|
895
|
+
step("line", s.line, env, "Apaga.");
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
case "Assert": if (!truthy(evalExpr(s.test, env))) throw new PyError(`assert falhou${s.msg ? ": " + str(evalExpr(s.msg, env)) : ""}`, s.line); return null;
|
|
899
|
+
}
|
|
900
|
+
throw new PyError(`comando não suportado (${s.k})`, s.line);
|
|
901
|
+
}
|
|
902
|
+
function describeAssign(targets, env, src) {
|
|
903
|
+
const parts = [];
|
|
904
|
+
const walk = (t) => {
|
|
905
|
+
if (t.k === "Tuple" || t.k === "List") return t.elts.forEach(walk);
|
|
906
|
+
try { parts.push(pure(t) ? `${exprText(t, env)} = ${repr(evalExprQuiet(t, env))}` : src); } catch { parts.push(src); }
|
|
907
|
+
};
|
|
908
|
+
targets.forEach(walk);
|
|
909
|
+
return parts.join(" · ");
|
|
910
|
+
}
|
|
911
|
+
// texto de um alvo com os índices já calculados: v[j+1] vira v[3]
|
|
912
|
+
function exprText(t, env) {
|
|
913
|
+
if (t.k === "Name") return t.id;
|
|
914
|
+
if (t.k === "Attr") return `${exprText(t.v, env)}.${t.name}`;
|
|
915
|
+
if (t.k === "Sub") return `${exprText(t.v, env)}[${pure(t.i) ? repr(evalExprQuiet(t.i, env)) : "…"}]`;
|
|
916
|
+
if (t.k === "Call" || !pure(t)) return "…";
|
|
917
|
+
return repr(evalExprQuiet(t, env));
|
|
918
|
+
}
|
|
919
|
+
// legendas com os valores: "v[j] > v[j + 1] → 5 > 3: sim." e "ordem.append('B')". Só recalcula o que não tem
|
|
920
|
+
// efeito colateral (nada de chamar função do programa ou método que mexe na lista de novo)
|
|
921
|
+
const PURE = new Set(["len", "abs", "min", "max", "str", "int", "float", "sum", "sorted", "round", "ord", "chr", "bool"]);
|
|
922
|
+
function pure(e) {
|
|
923
|
+
if (!e || typeof e !== "object") return true;
|
|
924
|
+
if (e.k === "Call") return e.f.k === "Name" && PURE.has(e.f.id) && !(e.f.id in globals.vars) && e.args.every(pure) && e.kw.every((k) => pure(k.v));
|
|
925
|
+
if (e.k === "Lambda" || e.k === "Comp") return false;
|
|
926
|
+
return Object.values(e).every((x) => (Array.isArray(x) ? x.every(pure) : x && typeof x === "object" && x.k ? pure(x) : true));
|
|
927
|
+
}
|
|
928
|
+
function valText(e, env) {
|
|
929
|
+
if (e.k === "Compare") {
|
|
930
|
+
const parts = [repr(evalExprQuiet(e.l, env))];
|
|
931
|
+
for (let k = 0; k < e.ops.length; k++) parts.push(e.ops[k], repr(evalExprQuiet(e.rs[k], env)));
|
|
932
|
+
return parts.join(" ");
|
|
933
|
+
}
|
|
934
|
+
if (e.k === "BoolOp") {
|
|
935
|
+
const l = valText(e.l, env), lv = truthy(evalExprQuiet(e.l, env));
|
|
936
|
+
if (e.op === "and" ? !lv : lv) return l;
|
|
937
|
+
return `${l} ${e.op === "and" ? "e" : "ou"} ${valText(e.r, env)}`;
|
|
938
|
+
}
|
|
939
|
+
if (e.k === "Not") return `not ${valText(e.e, env)}`;
|
|
940
|
+
return repr(evalExprQuiet(e, env));
|
|
941
|
+
}
|
|
942
|
+
function testText(test, env, src, ok) {
|
|
943
|
+
let v = null;
|
|
944
|
+
if (!["Name", "Const", "Call", "Attr"].includes(test.k) && pure(test)) { try { v = valText(test, env); } catch { v = null; } }
|
|
945
|
+
return v && v !== src ? `${src} → ${v}: ${ok ? "sim" : "não"}.` : `${src}? ${ok ? "sim" : "não"}.`;
|
|
946
|
+
}
|
|
947
|
+
function callText(e, env, src) {
|
|
948
|
+
if (!e.args.every(pure) || !e.kw.every((k) => pure(k.v))) return src;
|
|
949
|
+
try {
|
|
950
|
+
const f = e.f.k === "Name" ? e.f.id : e.f.k === "Attr" ? `${exprText(e.f.v, env)}.${e.f.name}` : null;
|
|
951
|
+
if (!f) return src;
|
|
952
|
+
return `${f}(${[...e.args.map((a) => repr(evalExprQuiet(a, env))), ...e.kw.map((k) => `${k.n}=${repr(evalExprQuiet(k.v, env))}`)].join(", ")})`;
|
|
953
|
+
} catch { return src; }
|
|
954
|
+
}
|
|
955
|
+
function evalExprQuiet(e, env) { const saved = ev; ev = { reads: [], writes: [], matches: [] }; try { return evalExpr(e, env); } finally { ev = saved; } }
|
|
956
|
+
|
|
957
|
+
// ---------------- funções prontas
|
|
958
|
+
const B = (name, fn) => new PyBuiltin(name, fn);
|
|
959
|
+
const one = (args, name, line) => { if (args.length < 1) throw new PyError(`${name}() precisa de um valor`, line); return args[0]; };
|
|
960
|
+
const minmax = (dir) => (args, kw, line) => {
|
|
961
|
+
const items = args.length === 1 ? list(args[0], line) : args;
|
|
962
|
+
if (!items.length) throw new PyError(`${dir < 0 ? "min" : "max"}() de lista vazia`, line);
|
|
963
|
+
const key = kw.key ? (x) => callValue(kw.key, [x], {}, line) : (x) => x;
|
|
964
|
+
return items.reduce((best, x) => ((dir < 0 ? pyLt(key(x), key(best), line) : pyLt(key(best), key(x), line)) ? x : best));
|
|
965
|
+
};
|
|
966
|
+
const sortList = (items, kw, line) => {
|
|
967
|
+
const key = kw.key ? (x) => callValue(kw.key, [x], {}, line) : (x) => x;
|
|
968
|
+
const keyed = items.map((x) => [key(x), x]);
|
|
969
|
+
keyed.sort((a, b) => (pyLt(a[0], b[0], line) ? -1 : pyLt(b[0], a[0], line) ? 1 : 0));
|
|
970
|
+
const r = keyed.map((p) => p[1]);
|
|
971
|
+
return truthy(kw.reverse) ? r.reverse() : r;
|
|
972
|
+
};
|
|
973
|
+
const BUILTINS = {
|
|
974
|
+
print: B("print", (args, kw) => { out.push(args.map(str).join(kw.sep ?? " ")); return null; }),
|
|
975
|
+
len: B("len", (args, kw, line) => { const v = one(args, "len", line); if (typeof v === "string" || Array.isArray(v)) return v.length; if (v instanceof PyDict || v instanceof PySet) return v.size; if (v instanceof PyRange) return v.length; throw new PyError(`len() de ${typeName(v)}`, line); }),
|
|
976
|
+
range: B("range", (args, kw, line) => { if (!args.every((x) => Number.isInteger(Number(x)))) throw new PyError("range() só com inteiros", line); const [a, b, s] = args.length === 1 ? [0, args[0], 1] : [args[0], args[1], args[2] ?? 1]; return new PyRange(Number(a), Number(b), Number(s)); }),
|
|
977
|
+
min: B("min", minmax(-1)), max: B("max", minmax(1)),
|
|
978
|
+
sum: B("sum", (args, kw, line) => list(args[0], line).reduce((s, x) => binop("+", s, x, line), args[1] ?? 0)),
|
|
979
|
+
abs: B("abs", (args) => Math.abs(args[0])),
|
|
980
|
+
round: B("round", (args) => (args[1] ? +Number(args[0]).toFixed(args[1]) : Math.round(args[0]))),
|
|
981
|
+
int: B("int", (args, kw, line) => { const v = args[0] ?? 0; if (typeof v === "string") { const n = parseInt(v, args[1] ?? 10); if (Number.isNaN(n)) throw new PyError(`int("${v}") não é número`, line); return n; } return Math.trunc(Number(v)); }),
|
|
982
|
+
float: B("float", (args, kw, line) => { const v = args[0] ?? 0; if (typeof v === "string") { const s = v.trim().toLowerCase(); if (s === "inf" || s === "infinity") return Infinity; if (s === "-inf") return -Infinity; const n = Number(s); if (Number.isNaN(n)) throw new PyError(`float("${v}") não é número`, line); return n; } return Number(v); }),
|
|
983
|
+
str: B("str", (args) => str(args[0] ?? "")),
|
|
984
|
+
bool: B("bool", (args) => truthy(args[0])),
|
|
985
|
+
list: B("list", (args, kw, line) => (args.length ? list(args[0], line) : [])),
|
|
986
|
+
tuple: B("tuple", (args, kw, line) => tuple(args.length ? list(args[0], line) : [])),
|
|
987
|
+
set: B("set", (args, kw, line) => new PySet(args.length ? list(args[0], line) : [])),
|
|
988
|
+
dict: B("dict", (args, kw, line) => { const d = new PyDict(args.length ? (args[0] instanceof PyDict ? args[0].entries() : list(args[0], line).map((p) => list(p, line))) : []); for (const k in kw) d.set(k, kw[k]); return d; }),
|
|
989
|
+
sorted: B("sorted", (args, kw, line) => sortList(list(args[0], line), kw, line)),
|
|
990
|
+
reversed: B("reversed", (args, kw, line) => list(args[0], line).reverse()),
|
|
991
|
+
enumerate: B("enumerate", (args, kw, line) => list(args[0], line).map((x, i) => tuple([i + (args[1] ?? 0), x]))),
|
|
992
|
+
zip: B("zip", (args, kw, line) => { const ls = args.map((a) => list(a, line)); const n = Math.min(...ls.map((l) => l.length)); return Array.from({ length: n }, (_, i) => tuple(ls.map((l) => l[i]))); }),
|
|
993
|
+
any: B("any", (args, kw, line) => list(args[0], line).some(truthy)),
|
|
994
|
+
all: B("all", (args, kw, line) => list(args[0], line).every(truthy)),
|
|
995
|
+
ord: B("ord", (args) => String(args[0]).codePointAt(0)),
|
|
996
|
+
chr: B("chr", (args) => String.fromCodePoint(args[0])),
|
|
997
|
+
isinstance: B("isinstance", (args) => { const t = args[1]; const names = { int: "int", float: "float", str: "str", list: "list", dict: "dict", set: "set", tuple: "tuple", bool: "bool" }; if (t instanceof PyClass) return args[0] instanceof PyObj && args[0].cls === t; const n = Object.keys(names).find((k) => BUILTINS[k] === t); return n ? typeName(args[0]) === n || (n === "float" && typeof args[0] === "number") : false; }),
|
|
998
|
+
input: B("input", (args, kw, line) => { throw new PyError("input() não existe numa apresentação: passe os valores na chamada", line); }),
|
|
999
|
+
};
|
|
1000
|
+
const siftUp = (h, i, line) => { while (i > 0) { const p = (i - 1) >> 1; if (!pyLt(h[i], h[p], line)) break; [h[i], h[p]] = [h[p], h[i]]; ev.writes.push([idOf(h), i], [idOf(h), p]); i = p; } };
|
|
1001
|
+
const siftDown = (h, i, line) => { for (;;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < h.length && pyLt(h[l], h[m], line)) m = l; if (r < h.length && pyLt(h[r], h[m], line)) m = r; if (m === i) break; [h[i], h[m]] = [h[m], h[i]]; ev.writes.push([idOf(h), i], [idOf(h), m]); i = m; } };
|
|
1002
|
+
const MODULES = {
|
|
1003
|
+
math: new PyModule("math", { floor: B("floor", (a) => Math.floor(a[0])), ceil: B("ceil", (a) => Math.ceil(a[0])), sqrt: B("sqrt", (a) => Math.sqrt(a[0])), log: B("log", (a) => (a[1] ? Math.log(a[0]) / Math.log(a[1]) : Math.log(a[0]))), log2: B("log2", (a) => Math.log2(a[0])), log10: B("log10", (a) => Math.log10(a[0])), exp: B("exp", (a) => Math.exp(a[0])), sin: B("sin", (a) => Math.sin(a[0])), cos: B("cos", (a) => Math.cos(a[0])), gcd: B("gcd", (a) => { let [x, y] = a.map((n) => Math.abs(n)); while (y) [x, y] = [y, x % y]; return x; }), factorial: B("factorial", (a) => { let r = 1; for (let k = 2; k <= a[0]; k++) r *= k; return r; }), inf: Infinity, pi: Math.PI, e: Math.E }),
|
|
1004
|
+
collections: new PyModule("collections", { deque: B("deque", (args, kw, line) => deque(args.length ? list(args[0], line) : [])) }),
|
|
1005
|
+
heapq: new PyModule("heapq", {
|
|
1006
|
+
heappush: B("heappush", (a, kw, line) => { a[0].push(a[1]); ev.writes.push([idOf(a[0]), a[0].length - 1]); siftUp(a[0], a[0].length - 1, line); return null; }),
|
|
1007
|
+
heappop: B("heappop", (a, kw, line) => { const h = a[0]; if (!h.length) throw new PyError("heappop de lista vazia", line); const top = h[0]; const last = h.pop(); if (h.length) { h[0] = last; siftDown(h, 0, line); } return top; }),
|
|
1008
|
+
heapify: B("heapify", (a, kw, line) => { for (let i = (a[0].length >> 1) - 1; i >= 0; i--) siftDown(a[0], i, line); return null; }),
|
|
1009
|
+
}),
|
|
1010
|
+
};
|
|
1011
|
+
const lst = (fn) => fn;
|
|
1012
|
+
const METHODS = {
|
|
1013
|
+
list: {
|
|
1014
|
+
append: lst((o, a) => { o.push(a[0]); ev.writes.push([idOf(o), o.length - 1]); return null; }),
|
|
1015
|
+
pop: lst((o, a, kw, line) => { if (!o.length) throw new PyError("pop() de lista vazia", line); const i = a.length ? normIndex(o, a[0], line) : o.length - 1; return o.splice(i, 1)[0]; }),
|
|
1016
|
+
insert: lst((o, a) => { const i = a[0] < 0 ? Math.max(0, o.length + a[0]) : Math.min(a[0], o.length); o.splice(i, 0, a[1]); ev.writes.push([idOf(o), i]); return null; }),
|
|
1017
|
+
remove: lst((o, a, kw, line) => { const i = o.findIndex((x) => pyEq(x, a[0])); if (i < 0) throw new PyError(`${repr(a[0])} não está na lista`, line); o.splice(i, 1); return null; }),
|
|
1018
|
+
index: lst((o, a, kw, line) => { const i = o.findIndex((x) => pyEq(x, a[0])); if (i < 0) throw new PyError(`${repr(a[0])} não está na lista`, line); return i; }),
|
|
1019
|
+
count: lst((o, a) => o.filter((x) => pyEq(x, a[0])).length),
|
|
1020
|
+
extend: lst((o, a, kw, line) => { o.push(...list(a[0], line)); return null; }),
|
|
1021
|
+
sort: lst((o, a, kw, line) => { const r = sortList(o, kw, line); o.splice(0, o.length, ...r); return null; }),
|
|
1022
|
+
reverse: lst((o) => { o.reverse(); return null; }),
|
|
1023
|
+
copy: lst((o) => [...o]),
|
|
1024
|
+
clear: lst((o) => { o.length = 0; return null; }),
|
|
1025
|
+
},
|
|
1026
|
+
tuple: { index: (o, a, kw, line) => METHODS.list.index(o, a, kw, line), count: (o, a) => METHODS.list.count(o, a) },
|
|
1027
|
+
deque: {
|
|
1028
|
+
append: (o, a) => { o.push(a[0]); ev.writes.push([idOf(o), o.length - 1]); return null; },
|
|
1029
|
+
appendleft: (o, a) => { o.unshift(a[0]); ev.writes.push([idOf(o), 0]); return null; },
|
|
1030
|
+
pop: (o, a, kw, line) => { if (!o.length) throw new PyError("pop() de deque vazio", line); return o.pop(); },
|
|
1031
|
+
popleft: (o, a, kw, line) => { if (!o.length) throw new PyError("popleft() de deque vazio", line); return o.shift(); },
|
|
1032
|
+
extend: (o, a, kw, line) => { o.push(...list(a[0], line)); return null; },
|
|
1033
|
+
clear: (o) => { o.length = 0; return null; },
|
|
1034
|
+
},
|
|
1035
|
+
dict: {
|
|
1036
|
+
get: (o, a) => { const e = o.get(a[0]); if (e) ev.reads.push([idOf(o), keyOf(a[0])]); return e ? e[1] : a[1] ?? null; },
|
|
1037
|
+
keys: (o) => o.keys(), values: (o) => o.values(), items: (o) => o.entries().map((e) => tuple(e)),
|
|
1038
|
+
pop: (o, a, kw, line) => { const e = o.get(a[0]); if (!e) { if (a.length > 1) return a[1]; throw new PyError(`a chave ${repr(a[0])} não está no dicionário`, line); } o.delete(a[0]); return e[1]; },
|
|
1039
|
+
setdefault: (o, a) => { const e = o.get(a[0]); if (e) return e[1]; o.set(a[0], a[1] ?? null); ev.writes.push([idOf(o), keyOf(a[0])]); return a[1] ?? null; },
|
|
1040
|
+
update: (o, a) => { for (const [k, v] of a[0].entries()) o.set(k, v); return null; },
|
|
1041
|
+
copy: (o) => new PyDict(o.entries()),
|
|
1042
|
+
},
|
|
1043
|
+
set: {
|
|
1044
|
+
add: (o, a) => { o.add(a[0]); ev.writes.push([idOf(o), keyOf(a[0])]); return null; },
|
|
1045
|
+
remove: (o, a, kw, line) => { if (!o.delete(a[0])) throw new PyError(`${repr(a[0])} não está no conjunto`, line); return null; },
|
|
1046
|
+
discard: (o, a) => { o.delete(a[0]); return null; },
|
|
1047
|
+
pop: (o, a, kw, line) => { const v = o.values()[0]; if (v === undefined) throw new PyError("pop() de conjunto vazio", line); o.delete(v); return v; },
|
|
1048
|
+
copy: (o) => new PySet(o.values()),
|
|
1049
|
+
},
|
|
1050
|
+
str: {
|
|
1051
|
+
lower: (o) => o.toLowerCase(), upper: (o) => o.toUpperCase(), strip: (o) => o.trim(),
|
|
1052
|
+
split: (o, a) => (a[0] == null ? o.split(/\s+/).filter(Boolean) : o.split(a[0])),
|
|
1053
|
+
join: (o, a, kw, line) => list(a[0], line).map((x) => { if (typeof x !== "string") throw new PyError("join() só junta textos", line); return x; }).join(o),
|
|
1054
|
+
find: (o, a) => o.indexOf(a[0], a[1] ?? 0), replace: (o, a) => o.split(a[0]).join(a[1]),
|
|
1055
|
+
startswith: (o, a) => o.startsWith(a[0]), endswith: (o, a) => o.endsWith(a[0]),
|
|
1056
|
+
count: (o, a) => (a[0] ? o.split(a[0]).length - 1 : o.length + 1),
|
|
1057
|
+
isdigit: (o) => /^\d+$/.test(o), isalpha: (o) => /^\p{L}+$/u.test(o),
|
|
1058
|
+
},
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// último passo: o desenho do passo anterior (a chamada que acabou de devolver) com o resultado
|
|
1062
|
+
function finish(text, extra) {
|
|
1063
|
+
const last = steps.at(-1);
|
|
1064
|
+
if (!last) return step("done", 0, globals, text, { ...extra, done: true });
|
|
1065
|
+
steps.push({ ...last, kind: "done", line: 0, text, reads: [], writes: [], matches: [], ...extra, done: true });
|
|
1066
|
+
}
|
|
1067
|
+
// ---------------- roda
|
|
1068
|
+
try {
|
|
1069
|
+
execBlock(ast.body, globals);
|
|
1070
|
+
if (call) {
|
|
1071
|
+
const e = parseExpr(call);
|
|
1072
|
+
const v = evalExpr(e, globals);
|
|
1073
|
+
finish(`Resultado: ${repr(v)}.`, { ret: repr(v) });
|
|
1074
|
+
} else finish("Fim.", {});
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
if (err instanceof PyError) { err.steps = steps; throw err; }
|
|
1077
|
+
if (err instanceof RangeError) { const e = new PyError("recursão ou dado grande demais"); e.steps = steps; throw e; }
|
|
1078
|
+
throw err;
|
|
1079
|
+
}
|
|
1080
|
+
return { steps, lines: ast.lines, comments: ast.comments, output: out };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// valor JS (do YAML) → literal Python, para montar a chamada: {A: [B, C]} → {'A': ['B', 'C']}
|
|
1084
|
+
export function pyLiteral(v) {
|
|
1085
|
+
if (v === null || v === undefined) return "None";
|
|
1086
|
+
if (v === true) return "True"; if (v === false) return "False";
|
|
1087
|
+
if (typeof v === "number") return String(v);
|
|
1088
|
+
if (typeof v === "string") return "'" + v.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'";
|
|
1089
|
+
if (Array.isArray(v)) return `[${v.map(pyLiteral).join(", ")}]`;
|
|
1090
|
+
if (typeof v === "object") return `{${Object.entries(v).map(([k, x]) => `${pyLiteral(/^-?\d+(\.\d+)?$/.test(k) ? Number(k) : k)}: ${pyLiteral(x)}`).join(", ")}}`;
|
|
1091
|
+
return "None";
|
|
1092
|
+
}
|