@rho-workspace/rho 0.5.3 → 0.5.4
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/README.md +24 -5
- package/dist/capabilities.d.ts +27 -0
- package/dist/capabilities.js +73 -1
- package/dist/cli.js +150 -25
- package/dist/contract.generated.d.ts +112 -12
- package/dist/contracts/owners.generated.js +103 -8
- package/dist/conversation-presentation.d.ts +3 -1
- package/dist/conversation-presentation.js +16 -6
- package/dist/conversations.d.ts +10 -6
- package/dist/conversations.js +17 -13
- package/dist/installs.d.ts +11 -0
- package/dist/installs.js +179 -0
- package/dist/project-notes.js +2 -0
- package/dist/protocol.js +7 -5
- package/dist/screen-check-skill.d.ts +2 -0
- package/dist/screen-check-skill.js +39 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +50 -7
- package/dist/web/.vite/manifest.json +51 -51
- package/dist/web/assets/{choices-B-y2OYvE.js → choices-DU9kHlyQ.js} +1 -1
- package/dist/web/assets/{console-e1P5qYQ5.js → console-Dbdo-5oO.js} +1 -1
- package/dist/web/assets/{dist-Dd3iAGFy.js → dist-B2V0VDO0.js} +1 -1
- package/dist/web/assets/{dist-CH56yNDn.js → dist-BV3hWfn_.js} +1 -1
- package/dist/web/assets/{dist-CBM2oq6g.js → dist-CLNF2g-E.js} +1 -1
- package/dist/web/assets/{dist-DEorbOW0.js → dist-D25Vn17i.js} +1 -1
- package/dist/web/assets/{explore-in-muWXOGOY.js → explore-in-8Q5ftt-E.js} +1 -1
- package/dist/web/assets/{graph-CK-df5lh.js → graph-hS2kvHTI.js} +1 -1
- package/dist/web/assets/{index-BYuKpyoi.js → index-BHQIaDsx.js} +220 -121
- package/dist/web/assets/index-Cj1_iqyK.css +1 -0
- package/dist/web/assets/{notes-meaning-B3HFbh84.js → notes-meaning-DdPbtNzJ.js} +1 -1
- package/dist/web/assets/{overview-D1Ev2OC_.js → overview-BV6ZO5TO.js} +3 -3
- package/dist/web/assets/{preview-HbmQKUGY.js → preview-n5lOOdjN.js} +1 -1
- package/dist/web/assets/{review-BVh9jWMB.js → review-CmWou4Iv.js} +1 -1
- package/dist/web/assets/{skills-DPaEYkln.js → skills-Dy7n3L9d.js} +1 -1
- package/dist/web/assets/{skills-work-dvjfy_tu.js → skills-work-Cj0W6b-I.js} +1 -1
- package/dist/web/assets/{studio-process-BYm8mjRJ.js → studio-process-QOY_n2_8.js} +1 -1
- package/dist/web/assets/{timeline-events-CDZ7wiJX.js → timeline-events-DmX1BCh3.js} +1 -1
- package/dist/web/assets/{work-turns-BeCt303c.js → work-turns-Co0C71rJ.js} +1 -1
- package/dist/web/assets/{workspace-d5Hw_ClZ.js → workspace-BKFefRfJ.js} +2 -2
- package/dist/web/composition.json +73 -73
- package/dist/web/index.html +2 -2
- package/dist/web/renderer.json +1 -1
- package/docs/FRONTEND.md +30 -14
- package/docs/HEADLESS.md +59 -13
- package/example/pa14-qs/.rho/rho/conversations/c-67f77cfebaba2cea.json +1 -1
- package/example/pa14-qs/.rho/rho/conversations/c-91aa2e46b1ac1a6c.json +1 -1
- package/example/pa14-qs/.rho/rho/conversations/c-fa24dfd80dfe4bcb.json +1 -1
- package/node_modules/@hono/node-server/dist/index.js +11 -0
- package/node_modules/@hono/node-server/dist/index.mjs +11 -0
- package/node_modules/@hono/node-server/dist/listener.js +11 -0
- package/node_modules/@hono/node-server/dist/listener.mjs +11 -0
- package/node_modules/@hono/node-server/dist/request.js +11 -0
- package/node_modules/@hono/node-server/dist/request.mjs +11 -0
- package/node_modules/@hono/node-server/dist/server.js +11 -0
- package/node_modules/@hono/node-server/dist/server.mjs +11 -0
- package/node_modules/@hono/node-server/dist/vercel.js +11 -0
- package/node_modules/@hono/node-server/dist/vercel.mjs +11 -0
- package/node_modules/@hono/node-server/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-agent/README.md +3 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +14 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/conversation.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/conversation.js +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.d.ts +11 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.js +7 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +5 -1
- package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-annotations/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-documents/README.md +4 -0
- package/node_modules/@rho-workspace/plugin-documents/dist/index.d.ts +3 -0
- package/node_modules/@rho-workspace/plugin-documents/dist/index.js +1 -1
- package/node_modules/@rho-workspace/plugin-documents/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/README.md +71 -10
- package/node_modules/@rho-workspace/plugin-kernel/dist/code.d.ts +2 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/code.js +4 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +5 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +7 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +55 -8
- package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.d.ts +9 -6
- package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.js +18 -41
- package/node_modules/@rho-workspace/plugin-kernel/dist/operations.js +5 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.d.ts +39 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.js +661 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +3 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.js +10 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.d.ts +38 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.js +194 -97
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +10 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +79 -18
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +115 -45
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.d.ts +63 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.js +344 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +6 -3
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +6 -3
- package/node_modules/express-rate-limit/dist/index.cjs +1 -1
- package/node_modules/express-rate-limit/dist/index.mjs +1 -1
- package/node_modules/express-rate-limit/package.json +1 -1
- package/node_modules/ip-address/dist/ipv6.js +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/playwright-core/LICENSE +202 -0
- package/node_modules/playwright-core/NOTICE +5 -0
- package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
- package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
- package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/browsers.json +52 -0
- package/node_modules/playwright-core/cli.js +22 -0
- package/node_modules/playwright-core/index.js +17 -0
- package/node_modules/playwright-core/index.mjs +28 -0
- package/node_modules/playwright-core/lib/bootstrap.js +149 -0
- package/node_modules/playwright-core/lib/coreBundle.js +78041 -0
- package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
- package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
- package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
- package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
- package/node_modules/playwright-core/lib/package.js +50 -0
- package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/server/electron/loader.js +123 -0
- package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
- package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
- package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
- package/node_modules/playwright-core/lib/tools/cli-client/cli.js +7 -0
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +808 -0
- package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
- package/node_modules/playwright-core/lib/tools/cli-client/output.js +352 -0
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +422 -0
- package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +261 -0
- package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
- package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
- package/node_modules/playwright-core/lib/utilsBundle.js +86078 -0
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DK72OMRi.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-Cn0mmDuv.js +129 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BEB2e8M3.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-D-g2vrvf.js +184 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-B794rD2Z.js +7 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.C8BeHuxd.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.oNry3Hjv.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CYwC_gH1.js +5 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/node_modules/playwright-core/lib/xdg-open +1267 -0
- package/node_modules/playwright-core/package.json +34 -0
- package/package.json +10 -6
- package/protocol/index.json +17 -17
- package/protocol/operations/conversation.settings.check.json +147 -4
- package/protocol/operations/conversation.settings.read.json +145 -3
- package/protocol/operations/conversation.settings.update.json +166 -4
- package/protocol/operations/conversation.start.json +2 -1
- package/protocol/operations/kernel.interrupt.json +20 -0
- package/protocol/operations/kernel.read.json +30 -0
- package/protocol/operations/kernel.run.json +36 -1
- package/protocol/operations/kernel.stop.json +10 -0
- package/scripts/browser-checks.mjs +53 -0
- package/scripts/screen-check.mjs +193 -0
- package/vendor/composition.json +102 -102
- package/vendor/rho-workspace-plugin-agent-0.4.28.tgz +0 -0
- package/vendor/rho-workspace-plugin-annotations-0.5.2.tgz +0 -0
- package/vendor/rho-workspace-plugin-documents-0.4.11.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- package/web/theme.css +17 -2
- package/dist/web/assets/index-DBd77u4a.css +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.27.tgz +0 -0
- package/vendor/rho-workspace-plugin-documents-0.4.10.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.2.tgz +0 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { statements } from './statements.js';
|
|
2
|
+
// The protected resource is the reader's page: a script larger than `bytes` is not read, and at
|
|
3
|
+
// most `steps` steps are drawn; the rest are counted.
|
|
4
|
+
export const scriptFlowLimits = { bytes: 512 * 1024, steps: 150 };
|
|
5
|
+
const rOps = ['<<-', '->>', ':::', '|>', '<-', '->', '<=', '>=', '==', '!=', '&&', '||', '::'];
|
|
6
|
+
const pyOps = ['**=', '//=', '>>=', '<<=', '...', '->', ':=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '@=', '==', '!=', '<=', '>=', '**', '//', '<<', '>>'];
|
|
7
|
+
const rName = /[\p{L}.][\p{L}\p{N}._]*/uy, pyName = /[\p{L}_][\p{L}\p{N}_]*/uy;
|
|
8
|
+
const rNumber = /(?:0[xX][0-9a-fA-F]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)[Li]?/y;
|
|
9
|
+
const pyNumber = /(?:0[xXoObB][0-9a-fA-F_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d+)?)[jJ]?/y;
|
|
10
|
+
const pyString = /([rRbBuUfF]{0,2})("""|'''|"|')/y, rRaw = /[rR](["'])(-*)([([{])/y;
|
|
11
|
+
const at = (re, text, i) => { re.lastIndex = i; return re.exec(text); };
|
|
12
|
+
function tokenize(text, language) {
|
|
13
|
+
const out = [];
|
|
14
|
+
let i = 0, line = 1, head = true;
|
|
15
|
+
const push = (kind, value, f) => { out.push({ kind, text: value, line, head, ...f ? { f } : {} }); head = false; };
|
|
16
|
+
// A string's text runs to its closing quote; its lines count.
|
|
17
|
+
const quoted = (from, close, escapes) => {
|
|
18
|
+
let j = from;
|
|
19
|
+
while (j < text.length && !text.startsWith(close, j)) {
|
|
20
|
+
if (escapes && text[j] === '\\')
|
|
21
|
+
j++;
|
|
22
|
+
j++;
|
|
23
|
+
}
|
|
24
|
+
return j;
|
|
25
|
+
};
|
|
26
|
+
while (i < text.length) {
|
|
27
|
+
const c = text[i];
|
|
28
|
+
if (c === '\n') {
|
|
29
|
+
out.push({ kind: 'newline', text: '\n', line, head: false });
|
|
30
|
+
line++;
|
|
31
|
+
head = true;
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (c === ' ' || c === '\t' || c === '\r' || c === '\f') {
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (c === '#') {
|
|
40
|
+
while (i < text.length && text[i] !== '\n')
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (c === '\\' && language === 'python' && /^\\\r?\n/.test(text.slice(i, i + 3))) {
|
|
45
|
+
i = text.indexOf('\n', i) + 1;
|
|
46
|
+
line++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let m;
|
|
50
|
+
if (language === 'r' && (m = at(rRaw, text, i))) {
|
|
51
|
+
const close = { '(': ')', '[': ']', '{': '}' }[m[3]] + m[2] + m[1];
|
|
52
|
+
const start = i + m[0].length, end = quoted(start, close, false), body = text.slice(start, end);
|
|
53
|
+
push('string', body);
|
|
54
|
+
line += body.split('\n').length - 1;
|
|
55
|
+
i = end + close.length;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (language === 'python' && (m = at(pyString, text, i)) && (!m[1] || /^(?:[rRuUfFbB]|[rR][bBfF]|[bBfF][rR])$/.test(m[1]))) {
|
|
59
|
+
const start = i + m[0].length, end = quoted(start, m[2], !/[rR]/.test(m[1])), body = text.slice(start, end);
|
|
60
|
+
push('string', body, /[fF]/.test(m[1]));
|
|
61
|
+
line += body.split('\n').length - 1;
|
|
62
|
+
i = end + m[2].length;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (c === '"' || c === "'" || (language === 'r' && c === '`')) {
|
|
66
|
+
const end = quoted(i + 1, c, true), body = text.slice(i + 1, end);
|
|
67
|
+
push(c === '`' ? 'name' : 'string', body);
|
|
68
|
+
line += body.split('\n').length - 1;
|
|
69
|
+
i = end + 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if ((m = at(language === 'r' ? rNumber : pyNumber, text, i)) && m[0] && (/\d/.test(c) || /^\.\d/.test(m[0]))) {
|
|
73
|
+
push('number', m[0]);
|
|
74
|
+
i += m[0].length;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if ((m = at(language === 'r' ? rName : pyName, text, i))) {
|
|
78
|
+
push('name', m[0]);
|
|
79
|
+
i += m[0].length;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (language === 'r' && c === '%' && (m = at(/%[^%\n]*%/y, text, i))) {
|
|
83
|
+
push('op', m[0]);
|
|
84
|
+
i += m[0].length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if ('([{'.includes(c)) {
|
|
88
|
+
push('open', c);
|
|
89
|
+
i++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (')]}'.includes(c)) {
|
|
93
|
+
push('close', c);
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const op = (language === 'r' ? rOps : pyOps).find(o => text.startsWith(o, i)) ?? c;
|
|
98
|
+
push('op', op);
|
|
99
|
+
i += op.length;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
// Each token's bracket depth (an open and its close share theirs), the innermost bracket around
|
|
104
|
+
// it and where that opened, and each bracket's partner.
|
|
105
|
+
function brackets(toks) {
|
|
106
|
+
const depth = [], inner = [], openAt = [], match = toks.map(() => -1), stack = [];
|
|
107
|
+
toks.forEach((t, k) => {
|
|
108
|
+
if (t.kind === 'close') {
|
|
109
|
+
const o = stack.pop();
|
|
110
|
+
if (o !== undefined) {
|
|
111
|
+
match[o] = k;
|
|
112
|
+
match[k] = o;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
depth[k] = stack.length;
|
|
116
|
+
openAt[k] = stack.at(-1) ?? -1;
|
|
117
|
+
inner[k] = stack.length ? toks[stack.at(-1)].text : '';
|
|
118
|
+
if (t.kind === 'open')
|
|
119
|
+
stack.push(k);
|
|
120
|
+
});
|
|
121
|
+
return { depth, inner, openAt, match };
|
|
122
|
+
}
|
|
123
|
+
// A string that names a file: with a folder, any extension; alone, one of the usual ones.
|
|
124
|
+
const usual = /\.(csv|tsv|txt|tab|dat|gz|bz2|xz|zip|rds|rda|rdata|qs|json|jsonl|ya?ml|toml|xml|html?|md|xlsx?|parquet|feather|arrow|h5|hdf5|h5ad|loom|mtx|npy|npz|pkl|pickle|joblib|mat|sav|dta|sqlite|db|png|pdf|svg|jpe?g|tiff?|gif|eps|ps|r|py|ipynb|fasta|fa|fastq|fq|bam|sam|vcf|bed|gtf|gff3?|log)$/i;
|
|
125
|
+
export function looksLikePath(s) {
|
|
126
|
+
if (!s || s.length >= 512 || /[\n\r\t]|:\/\//.test(s) || !/^[\p{L}\p{N}_@~.\/\\: +-]+$/u.test(s))
|
|
127
|
+
return false;
|
|
128
|
+
return /\//.test(s) ? /[^/]\.[A-Za-z][A-Za-z0-9]{0,7}$/.test(s) : usual.test(s) && /\p{L}/u.test(s.replace(usual, ''));
|
|
129
|
+
}
|
|
130
|
+
// What a call does with a path it is given, by its name.
|
|
131
|
+
const builds = new Set(['file.path', 'paste0', 'paste', 'here', 'path', 'normalizePath', 'path.expand', 'join', 'Path', 'PurePath', 'joinpath', 'abspath', 'expanduser', 'normpath', 'fspath', 'str']);
|
|
132
|
+
const writes = /^(write|save|ggsave|export|dump|dput|sink|fwrite|png|pdf|svg|jpeg|tiff|bmp|cairo_pdf|cairo_ps|postscript|dev\.copy|dir\.create|file\.create|to_|savefig|savez|savetxt|imwrite|imsave|mkdir|makedirs)|_write$|[._](write|save)/i;
|
|
133
|
+
const reads = /^(read|load|fread|vroom|scan|source|import|open|imread|fromfile|genfromtxt|readRDS|readLines|readxl|Read10X|qread)|[._](read|load)/i;
|
|
134
|
+
// What one statement makes, reads and names. `paths` holds the script's values that are a file's
|
|
135
|
+
// path (out <- "results/x.csv"), so a call given one reads or writes that file.
|
|
136
|
+
function analyze(toks, language, lineIndent, paths) {
|
|
137
|
+
const { depth, inner, openAt, match } = brackets(toks);
|
|
138
|
+
const nextOf = (k) => { let j = k + 1; while (toks[j]?.kind === 'newline')
|
|
139
|
+
j++; return j; };
|
|
140
|
+
const prevOf = (k) => { let j = k - 1; while (toks[j]?.kind === 'newline')
|
|
141
|
+
j--; return j; };
|
|
142
|
+
const keywords = language === 'r' ? rKeywords : pyKeywords;
|
|
143
|
+
const isName = (k) => toks[k]?.kind === 'name' && !keywords.has(toks[k].text);
|
|
144
|
+
// Where an expression starting at k ends: a newline that ends a line (not one inside brackets or
|
|
145
|
+
// after an operator), a ; or , at its depth, or the bracket that closes around it.
|
|
146
|
+
const endOf = (k) => {
|
|
147
|
+
const d = depth[k];
|
|
148
|
+
for (let j = k; j < toks.length; j++) {
|
|
149
|
+
const t = toks[j];
|
|
150
|
+
if (t.kind === 'close' && depth[j] < d)
|
|
151
|
+
return j - 1;
|
|
152
|
+
if (depth[j] !== d)
|
|
153
|
+
continue;
|
|
154
|
+
if (t.text === ';' || t.text === ',')
|
|
155
|
+
return j - 1;
|
|
156
|
+
if (t.kind === 'newline' && (inner[j] === '{' || inner[j] === '') && j > k) {
|
|
157
|
+
const before = toks[prevOf(j)];
|
|
158
|
+
if (!(before && (before.kind === 'open' || (before.kind === 'op' && before.text !== '...'))))
|
|
159
|
+
return j - 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return toks.length - 1;
|
|
163
|
+
};
|
|
164
|
+
// Scopes: a function's (or lambda's, class's or comprehension's) tokens and the names it keeps.
|
|
165
|
+
const scopes = [];
|
|
166
|
+
const within = (k) => scopes.filter(s => k > s.from && k <= s.to);
|
|
167
|
+
// Assignments outside any scope: the name, whether it is bare (x <- …), where its target is and
|
|
168
|
+
// where the assignment ends.
|
|
169
|
+
const top = [];
|
|
170
|
+
const targets = new Set();
|
|
171
|
+
const assign = (name, k, bare, end, global = false) => {
|
|
172
|
+
const holders = within(k);
|
|
173
|
+
if (bare)
|
|
174
|
+
targets.add(k);
|
|
175
|
+
if (holders.length && !global)
|
|
176
|
+
for (const s of holders)
|
|
177
|
+
s.own.add(name);
|
|
178
|
+
else
|
|
179
|
+
top.push({ name, bare, at: k, end });
|
|
180
|
+
};
|
|
181
|
+
const functions = [];
|
|
182
|
+
if (language === 'r') {
|
|
183
|
+
for (let k = 0; k < toks.length; k++) {
|
|
184
|
+
const t = toks[k];
|
|
185
|
+
if (!((t.kind === 'name' && t.text === 'function') || (t.kind === 'op' && t.text === '\\')))
|
|
186
|
+
continue;
|
|
187
|
+
const open = nextOf(k);
|
|
188
|
+
if (toks[open]?.text !== '(' || match[open] < 0)
|
|
189
|
+
continue;
|
|
190
|
+
const close = match[open], own = new Set();
|
|
191
|
+
for (let j = open + 1; j < close; j++)
|
|
192
|
+
if (depth[j] === depth[open] + 1 && toks[j].kind === 'name' && [',', ')', '='].includes(toks[nextOf(j)]?.text ?? ''))
|
|
193
|
+
own.add(toks[j].text);
|
|
194
|
+
const body = nextOf(close);
|
|
195
|
+
const to = toks[body]?.text === '{' && match[body] >= 0 ? match[body] : endOf(body);
|
|
196
|
+
scopes.push({ from: k, to, own });
|
|
197
|
+
}
|
|
198
|
+
for (let k = 0; k < toks.length; k++) {
|
|
199
|
+
const t = toks[k];
|
|
200
|
+
if (t.kind === 'name' && t.text === 'for' && toks[nextOf(k)]?.text === '(') {
|
|
201
|
+
const v = nextOf(nextOf(k));
|
|
202
|
+
if (isName(v) && toks[nextOf(v)]?.text === 'in')
|
|
203
|
+
assign(toks[v].text, v, true, match[nextOf(k)]);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (t.kind !== 'op' || !['<-', '<<-', '=', '->', '->>'].includes(t.text))
|
|
207
|
+
continue;
|
|
208
|
+
if (t.text === '=' && inner[k] !== '{' && inner[k] !== '')
|
|
209
|
+
continue;
|
|
210
|
+
let s, e;
|
|
211
|
+
if (t.text === '->' || t.text === '->>') {
|
|
212
|
+
s = nextOf(k);
|
|
213
|
+
e = s;
|
|
214
|
+
for (;;) {
|
|
215
|
+
const n = nextOf(e);
|
|
216
|
+
if (toks[n]?.kind === 'open' && toks[n].text !== '{' && match[n] >= 0)
|
|
217
|
+
e = match[n];
|
|
218
|
+
else if (['$', '@'].includes(toks[n]?.text ?? '') && toks[nextOf(n)])
|
|
219
|
+
e = nextOf(n);
|
|
220
|
+
else
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
e = prevOf(k);
|
|
226
|
+
s = e;
|
|
227
|
+
while (s >= 0) {
|
|
228
|
+
const u = toks[s];
|
|
229
|
+
if (u.kind === 'close') {
|
|
230
|
+
const o = match[s], before = toks[prevOf(o)];
|
|
231
|
+
if (o < 0 || (u.text === ')' && before && ['if', 'for', 'while', 'function'].includes(before.text)) || u.text === '}') {
|
|
232
|
+
s++;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
s = prevOf(o);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (u.kind === 'name' || u.kind === 'string') {
|
|
239
|
+
const p = toks[prevOf(s)];
|
|
240
|
+
if (p && ['$', '@', '::', ':::'].includes(p.text)) {
|
|
241
|
+
s = prevOf(prevOf(s));
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
s++;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
if (s < 0)
|
|
250
|
+
s = 0;
|
|
251
|
+
}
|
|
252
|
+
if (s > e || !toks[s])
|
|
253
|
+
continue;
|
|
254
|
+
const side = [];
|
|
255
|
+
for (let j = s; j <= e; j++)
|
|
256
|
+
if (toks[j].kind !== 'newline')
|
|
257
|
+
side.push(j);
|
|
258
|
+
const bare = side.length === 1 && (toks[s].kind === 'name' || toks[s].kind === 'string');
|
|
259
|
+
const named = bare ? s : side.find(j => toks[j].kind === 'name' && !keywords.has(toks[j].text) && toks[nextOf(j)]?.text !== '(' && !['$', '@', '::', ':::'].includes(toks[prevOf(j)]?.text ?? '') && !['::', ':::'].includes(toks[nextOf(j)]?.text ?? ''));
|
|
260
|
+
if (named === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
const end = t.text.startsWith('-') ? e : endOf(nextOf(k));
|
|
263
|
+
assign(toks[named].text, named, bare, end, t.text === '<<-' || t.text === '->>');
|
|
264
|
+
const value = nextOf(k);
|
|
265
|
+
if (bare && !within(k).length && (toks[value]?.text === 'function' || (toks[value]?.kind === 'op' && toks[value].text === '\\')))
|
|
266
|
+
functions.push(toks[named].text);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// The simple statements, each from its first token: a line's first token outside brackets,
|
|
271
|
+
// and what follows a ; or a block header's colon.
|
|
272
|
+
const compound = new Set(['if', 'elif', 'else', 'for', 'while', 'with', 'try', 'except', 'finally', 'def', 'class', 'async', 'match', 'case']);
|
|
273
|
+
const starts = [];
|
|
274
|
+
let from = -1;
|
|
275
|
+
for (let k = 0; k < toks.length; k++) {
|
|
276
|
+
const t = toks[k];
|
|
277
|
+
if (t.kind === 'newline') {
|
|
278
|
+
if (depth[k] === 0)
|
|
279
|
+
from = -1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (from < 0) {
|
|
283
|
+
from = k;
|
|
284
|
+
starts.push(k);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (depth[k] === 0 && t.text === ';') {
|
|
288
|
+
from = -1;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (depth[k] === 0 && t.text === ':' && compound.has(toks[from].text) && !toks.slice(from, k).some(u => u.text === 'lambda'))
|
|
292
|
+
from = -1;
|
|
293
|
+
}
|
|
294
|
+
const simple = starts.map((s, i) => {
|
|
295
|
+
let e = (starts[i + 1] ?? toks.length) - 1;
|
|
296
|
+
while (e > s && (toks[e].kind === 'newline' || (depth[e] === 0 && (toks[e].text === ';' || (toks[e].text === ':' && compound.has(toks[s].text))))))
|
|
297
|
+
e--;
|
|
298
|
+
return { s, e };
|
|
299
|
+
});
|
|
300
|
+
const indent = (k) => lineIndent[toks[k].line - 1] ?? 0;
|
|
301
|
+
// def and class: their parameters and their body, by indentation; lambdas; comprehensions.
|
|
302
|
+
for (const { s, e } of simple) {
|
|
303
|
+
let k = s;
|
|
304
|
+
if (toks[k].text === 'async')
|
|
305
|
+
k = nextOf(k);
|
|
306
|
+
if (toks[k].text !== 'def' && toks[k].text !== 'class')
|
|
307
|
+
continue;
|
|
308
|
+
const name = nextOf(k), own = new Set();
|
|
309
|
+
const open = nextOf(name);
|
|
310
|
+
if (toks[open]?.text === '(' && toks[k].text === 'def')
|
|
311
|
+
for (let j = open + 1; j < match[open]; j++)
|
|
312
|
+
if (depth[j] === depth[open] + 1 && toks[j].kind === 'name' && [',', ')', '=', ':'].includes(toks[nextOf(j)]?.text ?? ''))
|
|
313
|
+
own.add(toks[j].text);
|
|
314
|
+
let to = e;
|
|
315
|
+
for (let j = e + 1; j < toks.length; j++) {
|
|
316
|
+
const u = toks[j];
|
|
317
|
+
if (u.kind === 'newline')
|
|
318
|
+
continue;
|
|
319
|
+
if (u.head && depth[j] === 0 && indent(j) <= indent(s))
|
|
320
|
+
break;
|
|
321
|
+
to = j;
|
|
322
|
+
}
|
|
323
|
+
if (isName(name)) {
|
|
324
|
+
if (!within(name).length && toks[k].text === 'def')
|
|
325
|
+
functions.push(toks[name].text);
|
|
326
|
+
assign(toks[name].text, name, true, to);
|
|
327
|
+
}
|
|
328
|
+
scopes.push({ from: name, to, own });
|
|
329
|
+
}
|
|
330
|
+
for (let k = 0; k < toks.length; k++) {
|
|
331
|
+
const t = toks[k];
|
|
332
|
+
if (t.kind === 'name' && t.text === 'lambda') {
|
|
333
|
+
const own = new Set();
|
|
334
|
+
let j = k + 1;
|
|
335
|
+
for (; j < toks.length && !(toks[j].text === ':' && depth[j] === depth[k]); j++)
|
|
336
|
+
if (toks[j].kind === 'name')
|
|
337
|
+
own.add(toks[j].text);
|
|
338
|
+
scopes.push({ from: k, to: endOf(j + 1), own });
|
|
339
|
+
}
|
|
340
|
+
if (t.kind === 'open' && match[k] > k) {
|
|
341
|
+
const own = new Set();
|
|
342
|
+
for (let j = k + 1; j < match[k]; j++) {
|
|
343
|
+
if (depth[j] !== depth[k] + 1 || toks[j].text !== 'for')
|
|
344
|
+
continue;
|
|
345
|
+
for (let v = j + 1; v < match[k] && toks[v].text !== 'in'; v++)
|
|
346
|
+
if (toks[v].kind === 'name')
|
|
347
|
+
own.add(toks[v].text);
|
|
348
|
+
}
|
|
349
|
+
if (own.size)
|
|
350
|
+
scopes.push({ from: k, to: match[k], own });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Targets: of =, augmented and annotated assignment, :=, for … in, with … as, except … as.
|
|
354
|
+
// Imports bind names that are not the script's values.
|
|
355
|
+
const declared = new Set();
|
|
356
|
+
const element = (a, b, end, change = false) => {
|
|
357
|
+
const parts = [];
|
|
358
|
+
let p = a;
|
|
359
|
+
for (let j = a; j <= b + 1; j++)
|
|
360
|
+
if (j > b || (toks[j].text === ',' && depth[j] === depth[a])) {
|
|
361
|
+
if (p <= j - 1)
|
|
362
|
+
parts.push([p, j - 1]);
|
|
363
|
+
p = j + 1;
|
|
364
|
+
}
|
|
365
|
+
for (let [x, y] of parts) {
|
|
366
|
+
while (toks[x]?.kind === 'newline')
|
|
367
|
+
x++;
|
|
368
|
+
if (toks[x]?.text === '*')
|
|
369
|
+
x++;
|
|
370
|
+
if (x > y)
|
|
371
|
+
continue;
|
|
372
|
+
if (toks[x].kind === 'open' && match[x] === y) {
|
|
373
|
+
element(x + 1, y - 1, end, change);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (x === y && isName(x)) {
|
|
377
|
+
assign(toks[x].text, x, !change, end);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (isName(x))
|
|
381
|
+
assign(toks[x].text, x, false, end);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
for (const { s, e } of simple) {
|
|
385
|
+
const first = toks[s].text;
|
|
386
|
+
if (first === 'import' || first === 'from') {
|
|
387
|
+
for (let j = s; j <= e; j++)
|
|
388
|
+
if (isName(j) && toks[j - 1]?.text !== '.' && toks[j + 1]?.text !== '.') {
|
|
389
|
+
const inner = within(j);
|
|
390
|
+
for (const sc of inner)
|
|
391
|
+
sc.own.add(toks[j].text);
|
|
392
|
+
targets.add(j);
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (first === 'global' || first === 'nonlocal') {
|
|
397
|
+
for (let j = s + 1; j <= e; j++)
|
|
398
|
+
if (isName(j)) {
|
|
399
|
+
declared.add(toks[j].text);
|
|
400
|
+
targets.add(j);
|
|
401
|
+
}
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
let k = s;
|
|
405
|
+
if (first === 'async')
|
|
406
|
+
k = nextOf(k);
|
|
407
|
+
if (toks[k].text === 'for') {
|
|
408
|
+
let j = k + 1;
|
|
409
|
+
while (j <= e && toks[j].text !== 'in')
|
|
410
|
+
j++;
|
|
411
|
+
element(k + 1, j - 1, e);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (toks[k].text === 'with' || toks[k].text === 'except') {
|
|
415
|
+
for (let j = k; j <= e; j++)
|
|
416
|
+
if (toks[j].text === 'as' && depth[j] === depth[k])
|
|
417
|
+
element(j + 1, Math.min(e, Math.max(j + 1, endOf(j + 1))), e);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
for (let j = s; j <= e; j++)
|
|
421
|
+
if (toks[j].text === ':=' && isName(j - 1))
|
|
422
|
+
assign(toks[j - 1].text, j - 1, true, e);
|
|
423
|
+
if (compound.has(toks[k].text))
|
|
424
|
+
continue;
|
|
425
|
+
const ops = [];
|
|
426
|
+
for (let j = s; j <= e; j++)
|
|
427
|
+
if (depth[j] === depth[s] && toks[j].kind === 'op' && (toks[j].text === '=' || /^(?:[-+*/%&|^@]|\*\*|\/\/|>>|<<)=$/.test(toks[j].text)))
|
|
428
|
+
ops.push(j);
|
|
429
|
+
let a = s;
|
|
430
|
+
for (const op of ops) {
|
|
431
|
+
let b = op - 1;
|
|
432
|
+
// An annotation (x: int = …) leaves the name.
|
|
433
|
+
for (let j = a; j < op; j++)
|
|
434
|
+
if (toks[j].text === ':' && depth[j] === depth[s]) {
|
|
435
|
+
b = j - 1;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
element(a, b, e, toks[op].text !== '=');
|
|
439
|
+
a = op + 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
for (const sc of scopes)
|
|
443
|
+
for (const name of declared)
|
|
444
|
+
sc.own.delete(name);
|
|
445
|
+
}
|
|
446
|
+
// In R, the names in aes() and vars() and in a formula's argument (y ~ x) are a table's columns.
|
|
447
|
+
const columns = new Set();
|
|
448
|
+
if (language === 'r')
|
|
449
|
+
for (let o = 0; o < toks.length; o++) {
|
|
450
|
+
if (toks[o].text !== '(' || match[o] < 0)
|
|
451
|
+
continue;
|
|
452
|
+
const aes = ['aes', 'aes_', 'vars'].includes(toks[prevOf(o)]?.text ?? '');
|
|
453
|
+
let a = o + 1;
|
|
454
|
+
for (let j = o + 1; j <= match[o]; j++) {
|
|
455
|
+
if (j < match[o] && !(toks[j].text === ',' && depth[j] === depth[o] + 1))
|
|
456
|
+
continue;
|
|
457
|
+
if (aes || toks.slice(a, j).some((u, i) => u.text === '~' && depth[a + i] === depth[o] + 1))
|
|
458
|
+
for (let x = a; x < j; x++)
|
|
459
|
+
columns.add(x);
|
|
460
|
+
a = j + 1;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Reads: names outside what a scope keeps, not a field or an argument's name, not a target, and
|
|
464
|
+
// not one this statement assigned before (once that assignment ended).
|
|
465
|
+
const used = [];
|
|
466
|
+
for (let k = 0; k < toks.length; k++) {
|
|
467
|
+
if (!isName(k) || targets.has(k) || columns.has(k))
|
|
468
|
+
continue;
|
|
469
|
+
const name = toks[k].text, prev = toks[prevOf(k)], next = toks[nextOf(k)];
|
|
470
|
+
if (language === 'r' && prev && ['$', '@', '::', ':::'].includes(prev.text))
|
|
471
|
+
continue;
|
|
472
|
+
if (language === 'r' && next && ['::', ':::'].includes(next.text))
|
|
473
|
+
continue;
|
|
474
|
+
if (language === 'python' && toks[k - 1]?.text === '.')
|
|
475
|
+
continue;
|
|
476
|
+
if (next?.text === '=' && inner[k] === '(')
|
|
477
|
+
continue;
|
|
478
|
+
if (within(k).some(s => s.own.has(name)))
|
|
479
|
+
continue;
|
|
480
|
+
if (top.some(a => a.name === name && a.end < k && a.at !== k))
|
|
481
|
+
continue;
|
|
482
|
+
if (!used.includes(name))
|
|
483
|
+
used.push(name);
|
|
484
|
+
}
|
|
485
|
+
// The files its strings name, as read or written by the call they are given to.
|
|
486
|
+
const how = (k) => {
|
|
487
|
+
for (let o = openAt[k]; o >= 0; o = openAt[o]) {
|
|
488
|
+
const open = toks[o];
|
|
489
|
+
if (open.text !== '(')
|
|
490
|
+
return null;
|
|
491
|
+
const callee = toks[prevOf(o)];
|
|
492
|
+
if (!callee || callee.kind !== 'name')
|
|
493
|
+
return null;
|
|
494
|
+
if (builds.has(callee.text))
|
|
495
|
+
continue;
|
|
496
|
+
if (callee.text === 'open' && language === 'python') {
|
|
497
|
+
for (let j = o + 1; j < match[o]; j++)
|
|
498
|
+
if (j !== k && toks[j].kind === 'string' && depth[j] === depth[o] + 1 && /^[rwxabt+]{1,3}$/.test(toks[j].text))
|
|
499
|
+
return /[wxa]/.test(toks[j].text) ? 'wrote' : 'read';
|
|
500
|
+
return 'read';
|
|
501
|
+
}
|
|
502
|
+
const file = toks[prevOf(k)]?.text === '=' ? toks[prevOf(prevOf(k))]?.text : undefined;
|
|
503
|
+
if (file === 'file' && ['cat', 'capture.output', 'print'].includes(callee.text))
|
|
504
|
+
return 'wrote';
|
|
505
|
+
return writes.test(callee.text) ? 'wrote' : reads.test(callee.text) ? 'read' : null;
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
};
|
|
509
|
+
const files = [];
|
|
510
|
+
const name = (path, h) => { if (h && !files.some(f => f.path === path && f.how === h))
|
|
511
|
+
files.push({ path, how: h }); };
|
|
512
|
+
for (let k = 0; k < toks.length; k++) {
|
|
513
|
+
const t = toks[k];
|
|
514
|
+
if (t.kind === 'string' && !t.f && inner[k] !== '[' && !(language === 'python' && inner[k] === '{' && toks[nextOf(k)]?.text === ':') && looksLikePath(t.text.replace(/^\.\//, '')))
|
|
515
|
+
name(t.text.replace(/^\.\//, ''), how(k));
|
|
516
|
+
else if (isName(k) && paths.has(t.text) && !targets.has(k))
|
|
517
|
+
name(paths.get(t.text), how(k));
|
|
518
|
+
}
|
|
519
|
+
// A value that is a path: name <- "x/y.csv", or file.path("x", "y.csv").
|
|
520
|
+
const own = new Map();
|
|
521
|
+
for (const a of top) {
|
|
522
|
+
if (!a.bare)
|
|
523
|
+
continue;
|
|
524
|
+
const value = [];
|
|
525
|
+
for (let j = nextOf(nextOf(a.at)); j <= a.end && j < toks.length; j++)
|
|
526
|
+
if (toks[j].kind !== 'newline')
|
|
527
|
+
value.push(toks[j]);
|
|
528
|
+
const strings = value.filter(v => v.kind === 'string');
|
|
529
|
+
const joined = value.length === 1 && strings.length === 1 ? strings[0].text
|
|
530
|
+
: value[0]?.kind === 'name' && builds.has(value[0].text) && value[1]?.text === '(' && strings.length && value.every(v => v.kind === 'string' || [',', '(', ')'].includes(v.text) || v === value[0]) ? strings.map(s => s.text.replace(/\/$/, '')).join('/') : null;
|
|
531
|
+
if (joined && looksLikePath(joined))
|
|
532
|
+
own.set(a.name, joined);
|
|
533
|
+
}
|
|
534
|
+
for (const [n, p] of own)
|
|
535
|
+
paths.set(n, p);
|
|
536
|
+
const defined = [...new Set(top.map(a => a.name))];
|
|
537
|
+
const changed = new Set(top.filter(a => !a.bare || used.includes(a.name)).map(a => a.name));
|
|
538
|
+
for (const n of defined)
|
|
539
|
+
if (!own.has(n))
|
|
540
|
+
paths.delete(n);
|
|
541
|
+
return { defined, changed, used, files, functions: functions.filter(f => defined.includes(f)), paths: own };
|
|
542
|
+
}
|
|
543
|
+
const rKeywords = new Set(['if', 'else', 'repeat', 'while', 'function', 'for', 'in', 'next', 'break', 'TRUE', 'FALSE', 'NULL', 'Inf', 'NaN', 'NA', 'NA_integer_', 'NA_real_', 'NA_character_', 'NA_complex_', 'library', 'require', 'requireNamespace', 'suppressPackageStartupMessages']);
|
|
544
|
+
const pyKeywords = new Set(['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield', 'self']);
|
|
545
|
+
// What the code says a value or file is: a comment line `# means: …` directly above the statement
|
|
546
|
+
// that first makes the value or names the file, or `# means name: …` for the one value or file
|
|
547
|
+
// `name` in a statement that makes or names several. The words are the code's author's (an Agent's,
|
|
548
|
+
// mostly) and sit beside the code they describe. The first statement that makes or names a key
|
|
549
|
+
// decides its meaning, so a later change does not replace it. Nothing here runs code.
|
|
550
|
+
const meaningLimit = 200;
|
|
551
|
+
const markerLine = /^\s*#\s*means(?:\s+([^\s:#]+))?\s*:\s*(\S.*?)\s*$/i;
|
|
552
|
+
// The markers on the comment lines directly above the statement that starts at `line` (1-based).
|
|
553
|
+
export function markersAbove(lines, line) {
|
|
554
|
+
const block = [];
|
|
555
|
+
for (let i = line - 2; i >= 0 && /^\s*#/.test(lines[i]); i--)
|
|
556
|
+
block.unshift(lines[i]);
|
|
557
|
+
return block.flatMap(text => { const m = markerLine.exec(text); return m ? [{ name: m[1] ?? null, text: m[2].slice(0, meaningLimit) }] : []; });
|
|
558
|
+
}
|
|
559
|
+
// The unnamed marker above a statement, as the field a read carries (none when there is none).
|
|
560
|
+
const unnamedMeaning = (lines, line) => { const text = markersAbove(lines, line).find(m => !m.name)?.text; return text ? { means: text } : {}; };
|
|
561
|
+
// The script's steps, in order, with lines from each value (and written file) to the steps that
|
|
562
|
+
// read that version of it.
|
|
563
|
+
// `outside` names the values there before the script runs (a session's, for a cell's code): the
|
|
564
|
+
// script reads them too, until it makes its own.
|
|
565
|
+
export function scriptFlow(text, language, limits = scriptFlowLimits, outside = []) {
|
|
566
|
+
const empty = { steps: [], links: [], layers: 0, omitted: { values: 0, plots: 0, files: 0 }, quiet: 0, cut: 0, tooLong: false, outside: [], quietReads: [], means: new Map() };
|
|
567
|
+
if (new TextEncoder().encode(text).length > limits.bytes)
|
|
568
|
+
return { ...empty, tooLong: true };
|
|
569
|
+
const lines = text.split('\n'), lineIndent = lines.map(l => l.length - l.trimStart().length);
|
|
570
|
+
const toks = tokenize(text, language), paths = new Map();
|
|
571
|
+
let quiet = 0, t = 0;
|
|
572
|
+
// What the code says each value and file is (see markersAbove), and the keys already decided.
|
|
573
|
+
const means = new Map(), claimed = new Set();
|
|
574
|
+
const quietReads = [];
|
|
575
|
+
const groups = [], known = new Set(outside);
|
|
576
|
+
for (const statement of statements(text, language)) {
|
|
577
|
+
while (t < toks.length && toks[t].line < statement.from)
|
|
578
|
+
t++;
|
|
579
|
+
const start = t;
|
|
580
|
+
while (t < toks.length && toks[t].line <= statement.to)
|
|
581
|
+
t++;
|
|
582
|
+
const made = analyze(toks.slice(start, t), language, lineIndent, paths);
|
|
583
|
+
// The first statement that makes a value, or reads or writes a file, decides its meaning. A
|
|
584
|
+
// named marker goes to that value or file; an unnamed one to the values the statement makes, else
|
|
585
|
+
// to the files it reads or writes.
|
|
586
|
+
const marks = markersAbove(lines, statement.from);
|
|
587
|
+
for (const k of [...made.defined.map(name => ({ key: 'value:' + name, name, value: true })), ...made.files.map(f => ({ key: 'file:' + f.path, name: f.path, value: false }))]) {
|
|
588
|
+
if (claimed.has(k.key))
|
|
589
|
+
continue;
|
|
590
|
+
claimed.add(k.key);
|
|
591
|
+
const mark = marks.find(m => m.name === k.name) ?? (k.value === (made.defined.length > 0) ? marks.find(m => !m.name) : undefined);
|
|
592
|
+
if (mark)
|
|
593
|
+
means.set(k.key, mark.text);
|
|
594
|
+
}
|
|
595
|
+
// Only the script's own values are read from it; the rest are the session's or a package's.
|
|
596
|
+
const used = made.used.filter(n => known.has(n));
|
|
597
|
+
const wrote = made.files.filter(f => f.how === 'wrote').map(f => f.path), read = made.files.filter(f => f.how === 'read').map(f => f.path);
|
|
598
|
+
for (const n of made.defined)
|
|
599
|
+
known.add(n);
|
|
600
|
+
if (!made.defined.length && !wrote.length) {
|
|
601
|
+
quiet++;
|
|
602
|
+
if (used.length)
|
|
603
|
+
quietReads.push({ from: statement.from, to: statement.to, used, ...unnamedMeaning(lines, statement.from) });
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const last = groups.at(-1);
|
|
607
|
+
const continues = last && !wrote.length && !last.wrote.length && (
|
|
608
|
+
// It changes only what the step made, or it makes something from nothing after a step that did.
|
|
609
|
+
made.defined.every(n => last.defined.includes(n) && made.changed.has(n)) ||
|
|
610
|
+
(!used.length && !read.length && !last.used.length && !last.reads.length && !made.defined.some(n => last.defined.includes(n))));
|
|
611
|
+
if (continues) {
|
|
612
|
+
last.to = statement.to;
|
|
613
|
+
for (const n of used)
|
|
614
|
+
if (!last.defined.includes(n) && !last.used.includes(n))
|
|
615
|
+
last.used.push(n);
|
|
616
|
+
for (const n of made.defined)
|
|
617
|
+
if (!last.defined.includes(n))
|
|
618
|
+
last.defined.push(n);
|
|
619
|
+
for (const p of read)
|
|
620
|
+
if (!last.reads.includes(p))
|
|
621
|
+
last.reads.push(p);
|
|
622
|
+
for (const f of made.functions)
|
|
623
|
+
if (!last.functions.includes(f))
|
|
624
|
+
last.functions.push(f);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
groups.push({ from: statement.from, to: statement.to, defined: [...made.defined], changed: made.changed, used, reads: read, wrote, functions: made.functions });
|
|
628
|
+
}
|
|
629
|
+
const kept = groups.slice(0, limits.steps);
|
|
630
|
+
// Who made each value and wrote each file so far, as the steps go.
|
|
631
|
+
const maker = new Map(), writer = new Map(), links = [], before = [];
|
|
632
|
+
for (const n of outside)
|
|
633
|
+
maker.set(n, '');
|
|
634
|
+
const steps = kept.map(g => {
|
|
635
|
+
const id = 'lines:' + g.from;
|
|
636
|
+
const cell = { cellId: 'L' + g.from, number: g.from, state: 'done', code: lines.slice(g.from - 1, g.to).join('\n'), purpose: null, plots: 0, defined: g.defined, used: g.used, files: [...g.reads.map(path => ({ path, how: 'read' })), ...g.wrote.map(path => ({ path, how: 'wrote' }))] };
|
|
637
|
+
const reads = [];
|
|
638
|
+
for (const n of g.used) {
|
|
639
|
+
const from = maker.get(n);
|
|
640
|
+
if (from)
|
|
641
|
+
links.push({ from, to: id });
|
|
642
|
+
else if (from === '')
|
|
643
|
+
before.push({ name: n, to: id });
|
|
644
|
+
}
|
|
645
|
+
for (const p of g.reads) {
|
|
646
|
+
const from = writer.get(p);
|
|
647
|
+
if (from)
|
|
648
|
+
links.push({ from, to: id });
|
|
649
|
+
else
|
|
650
|
+
reads.push(p);
|
|
651
|
+
}
|
|
652
|
+
const changed = g.defined.filter(n => maker.has(n) && (g.changed.has(n) || g.used.includes(n)));
|
|
653
|
+
const nodes = [...g.defined.map(n => ({ id: `value:${n}@${g.from}`, kind: 'value', name: n, step: id })), ...g.wrote.map(p => ({ id: `wrote:${p}@${g.from}`, kind: 'file', name: p, step: id }))];
|
|
654
|
+
for (const n of g.defined)
|
|
655
|
+
maker.set(n, `value:${n}@${g.from}`);
|
|
656
|
+
for (const p of g.wrote)
|
|
657
|
+
writer.set(p, `wrote:${p}@${g.from}`);
|
|
658
|
+
return { id, kind: 'cell', cell, earlier: [], nodes, reads, latest: false, unknownInputs: false, stale: [], layer: 0, order: 0, from: g.from, to: g.to, changed, functions: g.functions };
|
|
659
|
+
});
|
|
660
|
+
return { steps, links, layers: 0, omitted: { values: 0, plots: 0, files: 0 }, quiet, cut: groups.length - kept.length, tooLong: false, outside: before, quietReads, means };
|
|
661
|
+
}
|
|
@@ -113,6 +113,9 @@ export interface KernelShell {
|
|
|
113
113
|
export declare const ShellProvider: import("react").Provider<KernelShell>;
|
|
114
114
|
export declare const useShell: () => KernelShell;
|
|
115
115
|
export declare const withShell: (shell: KernelShell | undefined, node: ReactNode) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
116
|
+
type Session = KernelOutputs['kernel.read']['sessions'][number];
|
|
117
|
+
export declare const environmentOf: (session: Pick<Session, "program">) => string | null;
|
|
118
|
+
export declare const versionOf: (session: Pick<Session, "program" | "version" | "language">) => string;
|
|
116
119
|
export declare const clock: (ms: number, seconds?: boolean) => string;
|
|
117
120
|
export declare function durationText(ms: number): string;
|
|
118
121
|
export declare const agentName: (client?: string | null) => "Codex" | "Claude Code" | "Agent";
|