@sema-agent/core 7.45.0 → 7.46.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/CHANGELOG.md +246 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +5 -3
- package/dist/core/allow-entry.js +3 -1
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +5 -0
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +9 -3
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -40
- package/dist/core/permission-rules.js +50 -43
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +11 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +30 -8
- package/dist/core/tool-policy.js +9 -7
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +22 -22
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -3
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
import { argvOf, lexShell, literalTildeSpelling, substitutionsInHeredocBody } from "../../shell/lex.js";
|
|
2
|
+
import { readProgramPosition } from "./bash-program-position.js";
|
|
3
|
+
import { declaredAttributes, programRow, readArgRoles } from "./program-read-model.js";
|
|
4
|
+
import { codeBodies } from "./shell-reentry-text.js";
|
|
5
|
+
export const SHELL_INTEGER_VARIABLES = new Set([
|
|
6
|
+
"RANDOM", "SECONDS", "LINENO", "OPTIND", "MAILCHECK", "HISTCMD", "SRANDOM", "EPOCHSECONDS", "EPOCHREALTIME", "BASHPID", "BASH_MONOSECONDS",
|
|
7
|
+
"BASH_TRAPSIG", "COLUMNS", "LINES", "SHLVL", "ERRNO", "TMOUT", "HISTSIZE", "SAVEHIST", "TRY_BLOCK_ERROR", "TRY_BLOCK_INTERRUPT", "KEYTIMEOUT",
|
|
8
|
+
"LISTMAX", "LOGCHECK", "PERIOD", "FUNCNEST", "UID", "EUID", "GID", "EGID", "REPORTTIME", "REPORTMEMORY", "DIRSTACKSIZE", "BAUD",
|
|
9
|
+
"ZLE_RPROMPT_INDENT", "MBEGIN", "MEND", "PPID", "ARGC", "ZSH_SUBSHELL", "TTYIDLE", "status",
|
|
10
|
+
]);
|
|
11
|
+
const SHELL_SET_VARIABLES = new Set(["PWD", "OLDPWD", "REPLY", "OPTARG", "MAPFILE", "BASH_REMATCH", "PIPESTATUS", "FUNCNAME", "BASH_COMMAND", "BASH_SUBSHELL", "COPROC", "GROUPS", "SHELLOPTS", "BASHOPTS", "BASH_VERSINFO"]);
|
|
12
|
+
export function shellKeepsName(name) {
|
|
13
|
+
return SHELL_INTEGER_VARIABLES.has(name) || SHELL_SET_VARIABLES.has(name);
|
|
14
|
+
}
|
|
15
|
+
const OPENING_WORDS = new Set(["if", "case", "for", "while", "until", "select", "{"]);
|
|
16
|
+
const CLOSING_WORDS = new Set(["fi", "esac", "done", "}"]);
|
|
17
|
+
const SWITCHING_WORDS = new Set(["else", "elif"]);
|
|
18
|
+
const SPECIAL_BUILTINS = new Set([":", ".", "break", "continue", "eval", "exec", "exit", "export", "readonly", "return", "set", "shift", "source", "times", "trap", "unset"]);
|
|
19
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
20
|
+
const ATTRIBUTE_KINDS = { integer: "integer", transformed: "transformed", frozen: "frozen" };
|
|
21
|
+
const EVERY_ATTRIBUTE = Object.values(ATTRIBUTE_KINDS).map((kind) => ({ kind }));
|
|
22
|
+
export function projectSegment(segment) {
|
|
23
|
+
const tokens = [...segment.tokens];
|
|
24
|
+
let grouped = tokens.some((t) => t.kind === "group");
|
|
25
|
+
const definesFunction = dropDefinitionHeads(tokens).some((n) => n > 0);
|
|
26
|
+
dropCasePattern(tokens);
|
|
27
|
+
const words = tokens.filter((t) => {
|
|
28
|
+
if (t.kind !== "word")
|
|
29
|
+
return false;
|
|
30
|
+
if (t.raw === t.folded && (t.folded === "{" || t.folded === "}")) {
|
|
31
|
+
grouped = true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
return { argv: words, redirects: tokens.filter((t) => t.kind === "redirect"), grouped, definesFunction };
|
|
37
|
+
}
|
|
38
|
+
function isDefinitionName(word) {
|
|
39
|
+
return IDENTIFIER.test(word.folded) || (word.atCommandPosition && word.expansions.length === 0 && !/[?*+@!]$/.test(word.raw));
|
|
40
|
+
}
|
|
41
|
+
export function dropDefinitionHeads(tokens) {
|
|
42
|
+
const heads = new Array(tokens.length + 1).fill(0);
|
|
43
|
+
const drop = (k, count) => {
|
|
44
|
+
tokens.splice(k, count);
|
|
45
|
+
heads.splice(k, count);
|
|
46
|
+
heads[k]++;
|
|
47
|
+
};
|
|
48
|
+
for (let k = 0; k + 1 < tokens.length; k++) {
|
|
49
|
+
const [a, b, c, d] = [tokens[k], tokens[k + 1], tokens[k + 2], tokens[k + 3]];
|
|
50
|
+
if (a.kind === "word" && a.raw === "function" && a.atCommandPosition && b.kind === "word") {
|
|
51
|
+
drop(k--, c?.kind === "group" && c.op === "(" && d?.kind === "group" && d.op === ")" ? 4 : 2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (let k = 0; k + 2 < tokens.length; k++) {
|
|
55
|
+
const [a, b, c] = [tokens[k], tokens[k + 1], tokens[k + 2]];
|
|
56
|
+
if (a.kind === "word" && b.kind === "group" && b.op === "(" && c.kind === "group" && c.op === ")" && isDefinitionName(a))
|
|
57
|
+
drop(k, 3);
|
|
58
|
+
}
|
|
59
|
+
return heads;
|
|
60
|
+
}
|
|
61
|
+
function dropCasePattern(tokens) {
|
|
62
|
+
const end = casePatternEnd(tokens);
|
|
63
|
+
if (end >= 0)
|
|
64
|
+
tokens.splice(0, end + 1);
|
|
65
|
+
}
|
|
66
|
+
function casePatternEnd(tokens) {
|
|
67
|
+
const open = [];
|
|
68
|
+
let end = -1;
|
|
69
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
70
|
+
const t = tokens[k];
|
|
71
|
+
if (t.kind === "word" && t.raw === "[[")
|
|
72
|
+
return -1;
|
|
73
|
+
if (t.kind !== "group")
|
|
74
|
+
continue;
|
|
75
|
+
if (t.op === "(") {
|
|
76
|
+
open.push(k);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const from = open.pop();
|
|
80
|
+
if (from !== undefined && extglobParen(tokens, from))
|
|
81
|
+
continue;
|
|
82
|
+
if (tokens[k + 1]?.kind === "word")
|
|
83
|
+
end = k;
|
|
84
|
+
}
|
|
85
|
+
return end;
|
|
86
|
+
}
|
|
87
|
+
function extglobParen(tokens, at) {
|
|
88
|
+
const before = tokens[at - 1];
|
|
89
|
+
return before?.kind === "word" && /[?*+@!]$/.test(before.raw);
|
|
90
|
+
}
|
|
91
|
+
function blockStructure(segments) {
|
|
92
|
+
let depth = 0;
|
|
93
|
+
let bodies = 0;
|
|
94
|
+
const frames = [];
|
|
95
|
+
const deeper = () => {
|
|
96
|
+
depth++;
|
|
97
|
+
for (const f of frames)
|
|
98
|
+
if (!f.opened && depth > f.body.base)
|
|
99
|
+
f.opened = true;
|
|
100
|
+
};
|
|
101
|
+
const shallower = () => {
|
|
102
|
+
depth--;
|
|
103
|
+
while (frames.length > 0 && frames[frames.length - 1].opened && depth <= frames[frames.length - 1].body.base)
|
|
104
|
+
frames.pop();
|
|
105
|
+
};
|
|
106
|
+
return segments.map((segment) => {
|
|
107
|
+
const tokens = [...segment.tokens];
|
|
108
|
+
const heads = dropDefinitionHeads(tokens);
|
|
109
|
+
const startBodies = (k) => {
|
|
110
|
+
for (let n = heads[k]; n > 0; n--)
|
|
111
|
+
frames.push({ body: { id: bodies++, base: depth }, opened: false });
|
|
112
|
+
};
|
|
113
|
+
const arithmetic = isArithmeticCommand(segment);
|
|
114
|
+
const patternEnd = casePatternEnd(tokens);
|
|
115
|
+
const skipped = new Set();
|
|
116
|
+
const open = [];
|
|
117
|
+
let lowest = depth;
|
|
118
|
+
let words;
|
|
119
|
+
let body;
|
|
120
|
+
const switches = [];
|
|
121
|
+
const innermost = () => [...frames].reverse().find((f) => f.opened)?.body;
|
|
122
|
+
const visit = (t, k) => {
|
|
123
|
+
if (t.kind === "group") {
|
|
124
|
+
if (arithmetic || skipped.has(k))
|
|
125
|
+
return;
|
|
126
|
+
if (t.op === "(") {
|
|
127
|
+
if (extglobParen(tokens, k)) {
|
|
128
|
+
skipped.add(k);
|
|
129
|
+
open.push(-1);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
open.push(k);
|
|
133
|
+
if (k <= patternEnd)
|
|
134
|
+
return;
|
|
135
|
+
deeper();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const from = open.pop();
|
|
139
|
+
if (from === -1)
|
|
140
|
+
return;
|
|
141
|
+
if (k <= patternEnd) {
|
|
142
|
+
if (k === patternEnd)
|
|
143
|
+
switches.push(depth);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
shallower();
|
|
147
|
+
lowest = Math.min(lowest, depth);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (t.kind !== "word")
|
|
151
|
+
return;
|
|
152
|
+
const keyword = t.atCommandPosition && t.raw === t.folded ? t.folded : undefined;
|
|
153
|
+
if (keyword !== undefined && OPENING_WORDS.has(keyword))
|
|
154
|
+
deeper();
|
|
155
|
+
else if (keyword !== undefined && CLOSING_WORDS.has(keyword)) {
|
|
156
|
+
shallower();
|
|
157
|
+
lowest = Math.min(lowest, depth);
|
|
158
|
+
}
|
|
159
|
+
else if (keyword !== undefined && SWITCHING_WORDS.has(keyword))
|
|
160
|
+
switches.push(depth);
|
|
161
|
+
else if (k > patternEnd) {
|
|
162
|
+
words ??= depth;
|
|
163
|
+
body = innermost();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
tokens.forEach((t, k) => {
|
|
167
|
+
startBodies(k);
|
|
168
|
+
visit(t, k);
|
|
169
|
+
let settled = false;
|
|
170
|
+
for (let i = frames.length - 1; i >= 0 && !frames[i].opened; i--) {
|
|
171
|
+
const f = frames[i];
|
|
172
|
+
f.opened = true;
|
|
173
|
+
settled = true;
|
|
174
|
+
if (t.kind === "word" && t.raw === "[[" && t.atCommandPosition)
|
|
175
|
+
f.conditional = true;
|
|
176
|
+
else if (t.kind === "group" && t.op === "(" && arithmetic)
|
|
177
|
+
f.parens = 0;
|
|
178
|
+
else
|
|
179
|
+
f.flat = true;
|
|
180
|
+
}
|
|
181
|
+
if (settled && t.kind === "word")
|
|
182
|
+
body = innermost();
|
|
183
|
+
const top = frames[frames.length - 1];
|
|
184
|
+
if (top?.parens !== undefined && t.kind === "group") {
|
|
185
|
+
top.parens += t.op === "(" ? 1 : -1;
|
|
186
|
+
if (top.parens <= 0)
|
|
187
|
+
frames.pop();
|
|
188
|
+
}
|
|
189
|
+
if (t.kind === "word" && t.raw === "]]")
|
|
190
|
+
while (frames.length > 0 && frames[frames.length - 1].conditional === true)
|
|
191
|
+
frames.pop();
|
|
192
|
+
});
|
|
193
|
+
while (frames.length > 0 && frames[frames.length - 1].flat === true)
|
|
194
|
+
frames.pop();
|
|
195
|
+
startBodies(tokens.length);
|
|
196
|
+
return { words: words ?? depth, after: depth, lowest, switches, ...(body !== undefined ? { body } : {}) };
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const ONE_WORD = /^[A-Za-z0-9_./+@:%=,-]+$/;
|
|
200
|
+
const WORD_VALUES_MAX = 64;
|
|
201
|
+
export function wordValues(word, table, quoted, overlay = new Map()) {
|
|
202
|
+
const pieces = word.pieces;
|
|
203
|
+
if (!pieces.some((p) => p.expansion?.kind === "parameter" || p.expansion?.kind === "tilde"))
|
|
204
|
+
return undefined;
|
|
205
|
+
if (table === undefined)
|
|
206
|
+
return "unknown";
|
|
207
|
+
const start = pieces[0]?.span[0] ?? 0;
|
|
208
|
+
const raw = word.raw;
|
|
209
|
+
let combos = [{ text: "", glob: false }];
|
|
210
|
+
const stateOf = (name) => overlay.get(name) ?? table.stateOf(name);
|
|
211
|
+
for (const p of pieces) {
|
|
212
|
+
const e = p.expansion;
|
|
213
|
+
let options;
|
|
214
|
+
if (e === undefined) {
|
|
215
|
+
if (p.literal === undefined)
|
|
216
|
+
return "unknown";
|
|
217
|
+
options = [{ spelled: p.literal }];
|
|
218
|
+
}
|
|
219
|
+
else if (e.kind === "glob") {
|
|
220
|
+
const written = raw === undefined ? undefined : raw.slice(p.span[0] - start, p.span[1] - start);
|
|
221
|
+
if (written === undefined)
|
|
222
|
+
return "unknown";
|
|
223
|
+
options = [{ spelled: written, glob: true }];
|
|
224
|
+
}
|
|
225
|
+
else if (e.kind === "tilde") {
|
|
226
|
+
if (e.body !== "~")
|
|
227
|
+
return "unknown";
|
|
228
|
+
const home = stateOf("HOME");
|
|
229
|
+
if (home === "unknown")
|
|
230
|
+
return "unknown";
|
|
231
|
+
options = home;
|
|
232
|
+
}
|
|
233
|
+
else if (e.kind === "parameter" && e.name !== undefined && (e.body === undefined || e.body === e.name)) {
|
|
234
|
+
const state = stateOf(e.name);
|
|
235
|
+
if (state === "unknown")
|
|
236
|
+
return "unknown";
|
|
237
|
+
if (p.quotedBy === undefined && !quoted && (table.ifsTouched || state.some((v) => v.glob === true || !ONE_WORD.test(v.spelled))))
|
|
238
|
+
return "unknown";
|
|
239
|
+
options = state;
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
return "unknown";
|
|
243
|
+
const next = [];
|
|
244
|
+
for (const c of combos)
|
|
245
|
+
for (const o of options)
|
|
246
|
+
next.push({ text: c.text + o.spelled, glob: c.glob || o.glob === true });
|
|
247
|
+
if (next.length > WORD_VALUES_MAX)
|
|
248
|
+
return "unknown";
|
|
249
|
+
combos = next;
|
|
250
|
+
}
|
|
251
|
+
const out = [];
|
|
252
|
+
for (const c of combos)
|
|
253
|
+
if (!out.some((v) => v.spelled === c.text && (v.glob === true) === c.glob))
|
|
254
|
+
out.push(c.glob ? { spelled: c.text, glob: true } : { spelled: c.text });
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
export function wordFloorValues(word, table) {
|
|
258
|
+
if (table === undefined)
|
|
259
|
+
return [];
|
|
260
|
+
let combos = [{ spelled: "" }];
|
|
261
|
+
for (const p of word.pieces) {
|
|
262
|
+
const e = p.expansion;
|
|
263
|
+
let options;
|
|
264
|
+
if (e === undefined) {
|
|
265
|
+
if (p.literal === undefined)
|
|
266
|
+
return [];
|
|
267
|
+
options = [{ spelled: p.literal }];
|
|
268
|
+
}
|
|
269
|
+
else if (e.kind === "parameter" && e.name !== undefined) {
|
|
270
|
+
const state = table.stateOf(e.name);
|
|
271
|
+
options = state !== "unknown" && (e.body === undefined || e.body === e.name) ? state : table.floorOf(e.name);
|
|
272
|
+
}
|
|
273
|
+
else
|
|
274
|
+
return [];
|
|
275
|
+
if (options.length === 0)
|
|
276
|
+
return [];
|
|
277
|
+
const next = [];
|
|
278
|
+
for (const c of combos)
|
|
279
|
+
for (const o of options)
|
|
280
|
+
next.push(c.glob === true || o.glob === true ? { spelled: c.spelled + o.spelled, glob: true } : { spelled: c.spelled + o.spelled });
|
|
281
|
+
if (next.length > WORD_VALUES_MAX)
|
|
282
|
+
return [];
|
|
283
|
+
combos = next;
|
|
284
|
+
}
|
|
285
|
+
return combos;
|
|
286
|
+
}
|
|
287
|
+
export function valueOf(word, table, quoted, overlay = new Map()) {
|
|
288
|
+
const values = wordValues(word, table, quoted, overlay);
|
|
289
|
+
if (values !== undefined)
|
|
290
|
+
return values;
|
|
291
|
+
if (word.pieces.every((p) => p.expansion === undefined && p.literal !== undefined))
|
|
292
|
+
return [{ spelled: word.pieces.map((p) => p.literal).join("") }];
|
|
293
|
+
const folded = word.folded;
|
|
294
|
+
if (folded !== undefined && word.pieces.every((p) => p.expansion === undefined ? p.literal !== undefined : p.expansion.kind === "glob"))
|
|
295
|
+
return [{ spelled: folded, glob: true }];
|
|
296
|
+
return "unknown";
|
|
297
|
+
}
|
|
298
|
+
export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
299
|
+
const bindings = [];
|
|
300
|
+
let all = shape.definesFunction ? "reset" : undefined;
|
|
301
|
+
const unknownAll = () => void (all = "unknown");
|
|
302
|
+
const overlay = new Map();
|
|
303
|
+
let scope;
|
|
304
|
+
const bind = (b) => {
|
|
305
|
+
bindings.push(scope !== undefined ? { ...b, scope } : b);
|
|
306
|
+
if (b.prefix !== true)
|
|
307
|
+
overlay.set(b.name, b.state);
|
|
308
|
+
};
|
|
309
|
+
const kill = (name) => bind({ name, state: "unknown" });
|
|
310
|
+
const nameOf = (w) => {
|
|
311
|
+
if (w.pieces.some((p) => p.expansion !== undefined || p.literal === undefined))
|
|
312
|
+
return "computed";
|
|
313
|
+
return nameIn(w.pieces.map((p) => p.literal).join(""));
|
|
314
|
+
};
|
|
315
|
+
const killNamed = (name) => {
|
|
316
|
+
if (name === "computed")
|
|
317
|
+
unknownAll();
|
|
318
|
+
else if (name !== undefined)
|
|
319
|
+
kill(name);
|
|
320
|
+
};
|
|
321
|
+
const wordsOfSegment = segment.tokens.flatMap((t) => (t.kind === "word" ? [t] : t.kind === "redirect" && t.target !== undefined ? [t.target] : []));
|
|
322
|
+
for (const w of wordsOfSegment)
|
|
323
|
+
for (const name of assignedByPieces(w.pieces))
|
|
324
|
+
kill(name);
|
|
325
|
+
for (const t of segment.tokens) {
|
|
326
|
+
if (t.kind !== "heredoc" || t.quoted)
|
|
327
|
+
continue;
|
|
328
|
+
if (t.body.includes("${"))
|
|
329
|
+
unknownAll();
|
|
330
|
+
for (const s of substitutionsInHeredocBody(t.body) ?? [])
|
|
331
|
+
if (s.kind === "arithmetic")
|
|
332
|
+
for (const name of arithmeticAssignedNames(s.body))
|
|
333
|
+
kill(name);
|
|
334
|
+
}
|
|
335
|
+
segment.tokens.forEach((t, i) => {
|
|
336
|
+
const next = segment.tokens[i + 1];
|
|
337
|
+
const m = t.kind === "word" && next?.kind === "redirect" ? /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(t.raw) : null;
|
|
338
|
+
if (m !== null)
|
|
339
|
+
kill(m[1]);
|
|
340
|
+
});
|
|
341
|
+
const tokens = segment.tokens;
|
|
342
|
+
if (isArithmeticCommand(segment)) {
|
|
343
|
+
for (const name of arithmeticAssignedNames(argv.map((w) => w.raw).join(" ")))
|
|
344
|
+
kill(name);
|
|
345
|
+
return { bindings, all };
|
|
346
|
+
}
|
|
347
|
+
const head = argv[0];
|
|
348
|
+
const headWord = head !== undefined && head.raw === head.folded ? head.folded : undefined;
|
|
349
|
+
if (headWord === "for" || headWord === "select") {
|
|
350
|
+
if (tokens.some((t) => t.kind === "group"))
|
|
351
|
+
unknownAll();
|
|
352
|
+
const name = argv[1] !== undefined ? nameOf(argv[1]) : undefined;
|
|
353
|
+
if (name === undefined || name === "computed") {
|
|
354
|
+
unknownAll();
|
|
355
|
+
return { bindings, all };
|
|
356
|
+
}
|
|
357
|
+
const inAt = argv.findIndex((w, i) => i >= 2 && w.raw === "in");
|
|
358
|
+
let state = "unknown";
|
|
359
|
+
if (headWord === "for" && inAt === 2 && argv.length > 3) {
|
|
360
|
+
const values = [];
|
|
361
|
+
for (const w of argv.slice(3)) {
|
|
362
|
+
const v = readValue(w, false, overlay);
|
|
363
|
+
if (v === "unknown") {
|
|
364
|
+
values.length = 0;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
values.push(...v);
|
|
368
|
+
}
|
|
369
|
+
if (values.length > 0)
|
|
370
|
+
state = values;
|
|
371
|
+
}
|
|
372
|
+
bind({ name, state, loopVar: true });
|
|
373
|
+
return { bindings, all };
|
|
374
|
+
}
|
|
375
|
+
if (headWord === "coproc" && argv[1] !== undefined)
|
|
376
|
+
killNamed(nameOf(argv[1]));
|
|
377
|
+
const pos = readProgramPosition(argv);
|
|
378
|
+
const program = pos.program;
|
|
379
|
+
const row = program === undefined ? undefined : programRow(program.basename);
|
|
380
|
+
const statement = program === undefined && pos.launchers.length === 0 && pos.refusal === undefined;
|
|
381
|
+
for (const k of pos.assignments) {
|
|
382
|
+
const a = argv[k].assignment;
|
|
383
|
+
if (a === undefined)
|
|
384
|
+
continue;
|
|
385
|
+
if (statement)
|
|
386
|
+
bind({ name: a.name, state: assignedState(a, readValue, overlay), ...(a.form === "compound" ? { floor: compoundFloor(a.value.raw, readValue, overlay) } : {}) });
|
|
387
|
+
else if (program !== undefined && SPECIAL_BUILTINS.has(program.basename))
|
|
388
|
+
bind({ name: a.name, state: assignedState(a, readValue, overlay), prefix: true });
|
|
389
|
+
}
|
|
390
|
+
const programWord = program === undefined ? undefined : argv[program.index];
|
|
391
|
+
if (programWord !== undefined && nameOf(programWord) === "computed" && !programWord.pieces.some((p) => p.literal?.includes("/") === true)) {
|
|
392
|
+
unknownAll();
|
|
393
|
+
return { bindings, all, attributes: EVERY_ATTRIBUTE, references: true };
|
|
394
|
+
}
|
|
395
|
+
if (program === undefined || row === undefined)
|
|
396
|
+
return { bindings, all };
|
|
397
|
+
const args = argv.slice(program.index + 1);
|
|
398
|
+
const folded = args.map((w) => w.folded);
|
|
399
|
+
if (row.reentry?.kind === "shell" && (row.reentry.text === "words" || row.reentry.text === "file-operand"))
|
|
400
|
+
unknownAll();
|
|
401
|
+
for (const body of codeBodies(program.basename, folded)) {
|
|
402
|
+
const inner = namesBoundIn(body);
|
|
403
|
+
if (inner === "all")
|
|
404
|
+
unknownAll();
|
|
405
|
+
else
|
|
406
|
+
for (const name of inner)
|
|
407
|
+
kill(name);
|
|
408
|
+
}
|
|
409
|
+
if (row.builtin === "declaration") {
|
|
410
|
+
scope = args.some((w) => w.folded.startsWith("-") || w.folded.startsWith("+")) ? "unread" : program.basename === "local" ? "local" : undefined;
|
|
411
|
+
const assigns = declaredAttributes(row, folded);
|
|
412
|
+
const kinds = [...assigns].filter((k) => k !== "reference" && k !== "unsettled");
|
|
413
|
+
const storesText = [...assigns].every((k) => k === "frozen");
|
|
414
|
+
let references = assigns.has("reference");
|
|
415
|
+
const attributes = [];
|
|
416
|
+
const expanded = new Map(overlay);
|
|
417
|
+
for (const w of args) {
|
|
418
|
+
const a = w.assignment;
|
|
419
|
+
const name = a?.name ?? nameOf(w);
|
|
420
|
+
if (name === "computed") {
|
|
421
|
+
unknownAll();
|
|
422
|
+
attributes.push(...EVERY_ATTRIBUTE);
|
|
423
|
+
references = true;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (name === undefined)
|
|
427
|
+
continue;
|
|
428
|
+
const refused = attributes.some((x) => x.name === undefined || x.name === name);
|
|
429
|
+
if (a?.form === "compound")
|
|
430
|
+
bind({ name, state: "unknown", floor: compoundFloor(a.value.raw, readValue, expanded) });
|
|
431
|
+
else if (a === undefined || !storesText || refused)
|
|
432
|
+
kill(name);
|
|
433
|
+
else
|
|
434
|
+
bind({ name, state: assignedState(a, readValue, expanded) });
|
|
435
|
+
attributes.push(...kinds.map((kind) => ({ name, kind })));
|
|
436
|
+
}
|
|
437
|
+
const inFunction = program.basename === "local" ? { inFunction: true } : {};
|
|
438
|
+
if (references) {
|
|
439
|
+
unknownAll();
|
|
440
|
+
return { bindings, all, attributes, references: true, ...inFunction };
|
|
441
|
+
}
|
|
442
|
+
return { bindings, all, attributes, ...inFunction };
|
|
443
|
+
}
|
|
444
|
+
for (const e of readArgRoles(row, folded)) {
|
|
445
|
+
const role = e.kind === "value" || e.kind === "operand" ? e.role : e.kind === "option" && typeof e.reading === "object" ? e.reading.role : undefined;
|
|
446
|
+
if (role?.kind === "program-text" && role.language === "arithmetic" && e.kind !== "end-of-options" && e.kind !== "dash-word-operand") {
|
|
447
|
+
for (const name of arithmeticAssignedNames(e.kind === "option" && typeof e.reading === "object" ? e.reading.attached : folded[e.at]))
|
|
448
|
+
kill(name);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (role?.kind !== "binds-name")
|
|
452
|
+
continue;
|
|
453
|
+
if (e.kind === "option" && typeof e.reading === "object") {
|
|
454
|
+
killNamed(nameOf(args[e.at]) === "computed" ? "computed" : nameIn(e.reading.attached));
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
killNamed(nameOf(args[e.at]));
|
|
458
|
+
}
|
|
459
|
+
const nameLetters = new Set(row.grammars.flatMap((g) => Object.entries(g.short).flatMap(([letter, s]) => (s.value?.kind === "binds-name" ? [letter] : []))));
|
|
460
|
+
if (nameLetters.size > 0 && readArgRoles(row, folded).some((e) => e.kind === "option" && e.reading === "speculative" && [...folded[e.at].slice(1)].some((ch) => nameLetters.has(ch))))
|
|
461
|
+
unknownAll();
|
|
462
|
+
return { bindings, all };
|
|
463
|
+
}
|
|
464
|
+
function nameIn(text) {
|
|
465
|
+
return /^([A-Za-z_][A-Za-z0-9_]*)(?:\[.*\])?$/s.exec(text)?.[1];
|
|
466
|
+
}
|
|
467
|
+
function compoundElementWords(raw) {
|
|
468
|
+
const inner = /^\(([\s\S]*)\)$/.exec(raw)?.[1];
|
|
469
|
+
const l = inner === undefined ? undefined : lexShell(inner);
|
|
470
|
+
if (l === undefined || "parseFailure" in l)
|
|
471
|
+
return [];
|
|
472
|
+
return l.segments.flatMap((sg) => argvOf(sg)).flatMap((element) => {
|
|
473
|
+
const prefix = /^\[[^\]]*\]\+?=/.exec(element.raw)?.[0];
|
|
474
|
+
const assigned = prefix === undefined ? undefined : lexShell(`V=${element.raw.slice(prefix.length)}`);
|
|
475
|
+
const value = assigned === undefined || "parseFailure" in assigned ? undefined : argvOf(assigned.segments[0])[0]?.assignment?.value;
|
|
476
|
+
const folded = /^\[[^\]]*\]\+?=/.exec(element.folded)?.[0];
|
|
477
|
+
return [prefix === undefined ? element : value, ...(folded !== undefined ? [{ pieces: element.pieces, folded: element.folded.slice(folded.length), tail: true }] : [])].filter((w) => w !== undefined);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function compoundFloor(raw, readValue, overlay) {
|
|
481
|
+
return compoundElementWords(raw).flatMap((w) => {
|
|
482
|
+
const v = w.tail === true ? "unknown" : readValue(w, true, overlay);
|
|
483
|
+
if (v !== "unknown")
|
|
484
|
+
return [...v];
|
|
485
|
+
const text = w.pieces.some((q) => q.expansion?.kind === "tilde") ? w.folded : literalTildeSpelling(w.folded);
|
|
486
|
+
return [w.pieces.some((q) => q.expansion?.kind === "glob") ? { spelled: text, glob: true } : { spelled: text }];
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function assignedState(a, readValue, overlay) {
|
|
490
|
+
if (a.op !== "=" || a.subscript !== undefined || a.form !== "scalar" || a.value.unresolved !== undefined)
|
|
491
|
+
return "unknown";
|
|
492
|
+
return readValue({ pieces: a.value.pieces }, true, overlay);
|
|
493
|
+
}
|
|
494
|
+
function assignedByPieces(pieces) {
|
|
495
|
+
const out = [];
|
|
496
|
+
for (const p of pieces) {
|
|
497
|
+
const e = p.expansion;
|
|
498
|
+
if (e?.kind === "parameter" && e.name !== undefined && e.body !== undefined && new RegExp(`^${e.name}:?=`).test(e.body))
|
|
499
|
+
out.push(e.name);
|
|
500
|
+
if (e?.kind === "arithmetic" && e.body !== undefined)
|
|
501
|
+
out.push(...arithmeticAssignedNames(e.body));
|
|
502
|
+
}
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
export function arithmeticAssignedNames(text) {
|
|
506
|
+
const assigns = /(?:^|[^=!<>])=(?!=)|\+\+|--/.test(text);
|
|
507
|
+
return assigns ? [...new Set(text.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [])] : [];
|
|
508
|
+
}
|
|
509
|
+
export function namesBoundIn(text, depth = 0) {
|
|
510
|
+
if (depth > 4)
|
|
511
|
+
return "all";
|
|
512
|
+
const lexed = lexShell(text);
|
|
513
|
+
if ("parseFailure" in lexed)
|
|
514
|
+
return "all";
|
|
515
|
+
const names = new Set();
|
|
516
|
+
const nothing = () => "unknown";
|
|
517
|
+
for (const segment of lexed.segments) {
|
|
518
|
+
const p = projectSegment(segment);
|
|
519
|
+
const effect = bindingEffectOf(segment, p.argv, p, nothing);
|
|
520
|
+
if (effect.all === "unknown")
|
|
521
|
+
return "all";
|
|
522
|
+
for (const b of effect.bindings)
|
|
523
|
+
names.add(b.name);
|
|
524
|
+
}
|
|
525
|
+
return names;
|
|
526
|
+
}
|
|
527
|
+
export function textBindsHome(text) {
|
|
528
|
+
const names = namesBoundIn(text);
|
|
529
|
+
return names === "all" || names.has("HOME");
|
|
530
|
+
}
|
|
531
|
+
const NOTHING_INHERITED = { attributes: [], references: false };
|
|
532
|
+
export class BindingThread {
|
|
533
|
+
segments;
|
|
534
|
+
named;
|
|
535
|
+
initial;
|
|
536
|
+
boundAnywhere;
|
|
537
|
+
loops;
|
|
538
|
+
homeStartsTouched;
|
|
539
|
+
entries = [];
|
|
540
|
+
attributesAt;
|
|
541
|
+
unreadable;
|
|
542
|
+
blocks;
|
|
543
|
+
detached;
|
|
544
|
+
constructor(segments, named, initial, boundAnywhere, loops, homeStartsTouched = false, inherited = NOTHING_INHERITED) {
|
|
545
|
+
this.segments = segments;
|
|
546
|
+
this.named = named;
|
|
547
|
+
this.initial = initial;
|
|
548
|
+
this.boundAnywhere = boundAnywhere;
|
|
549
|
+
this.loops = loops;
|
|
550
|
+
this.homeStartsTouched = homeStartsTouched;
|
|
551
|
+
this.blocks = blockStructure(segments);
|
|
552
|
+
this.detached = segments.map((s, i) => s.connector === "|" || segments[i + 1]?.connector === "|" || segments[i + 1]?.connector === "&");
|
|
553
|
+
this.unreadable = [...(inherited.references ? [{ at: -1 }] : []), ...named.flatMap((effect, at) => this.unreadableBy(effect, at))];
|
|
554
|
+
this.attributesAt = [...inherited.attributes.map((a) => ({ ...a, at: -1 })), ...named.flatMap((effect, at) => (effect.attributes ?? []).map((a) => ({ ...a, at })))];
|
|
555
|
+
}
|
|
556
|
+
inheritedAt(j) {
|
|
557
|
+
const holds = (at) => j === undefined || this.mayRunBefore(at, j);
|
|
558
|
+
return {
|
|
559
|
+
attributes: this.attributesAt.filter((x) => holds(x.at)).map(({ name, kind }) => (name !== undefined ? { name, kind } : { kind })),
|
|
560
|
+
references: this.unreadable.some((u) => u.name === undefined && u.body === undefined && holds(u.at)),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
unreadableBy(effect, at) {
|
|
564
|
+
const body = this.blocks[at].body;
|
|
565
|
+
const out = [];
|
|
566
|
+
if (body !== undefined) {
|
|
567
|
+
for (const b of effect.bindings)
|
|
568
|
+
if (b.scope !== "local" && (b.scope === "unread" || !this.declaredLocal(b.name, at, body)))
|
|
569
|
+
out.push({ at, name: b.name, body });
|
|
570
|
+
if (effect.all === "unknown")
|
|
571
|
+
out.push({ at, body });
|
|
572
|
+
}
|
|
573
|
+
if (effect.references === true)
|
|
574
|
+
out.push({ at });
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
mayRunBefore(x, e) {
|
|
578
|
+
if (x < e)
|
|
579
|
+
return true;
|
|
580
|
+
return this.blocks[e].body !== undefined || [...this.loops].some(([head, end]) => head <= e && x < end);
|
|
581
|
+
}
|
|
582
|
+
declaredLocal(name, at, body) {
|
|
583
|
+
for (let k = 0; k < at; k++) {
|
|
584
|
+
const connector = this.segments[k].connector;
|
|
585
|
+
if (this.blocks[k].body?.id !== body.id || this.detached[k] === true || connector === "&&" || connector === "||")
|
|
586
|
+
continue;
|
|
587
|
+
if (this.named[k].bindings.some((b) => b.scope === "local" && b.name === name) && this.heldThrough(k, at))
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
heldThrough(at, j) {
|
|
593
|
+
const depth = this.blocks[at].words;
|
|
594
|
+
if (this.blocks[at].after < depth)
|
|
595
|
+
return false;
|
|
596
|
+
for (let k = at + 1; k <= j; k++)
|
|
597
|
+
if (this.blocks[k].lowest < depth || this.blocks[k].switches.includes(depth))
|
|
598
|
+
return false;
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
apply(at, effect, andRun, loopEnd) {
|
|
602
|
+
const detached = this.detached[at] === true;
|
|
603
|
+
const refused = effect.inFunction === true && this.blocks[at].body === undefined;
|
|
604
|
+
if (effect.all !== undefined)
|
|
605
|
+
this.entries.push({ at, state: effect.all === "reset" ? "reset" : "unknown", detached: false, andRun: false });
|
|
606
|
+
for (const b of effect.bindings) {
|
|
607
|
+
this.entries.push({
|
|
608
|
+
at, name: b.name, state: refused ? "unknown" : b.state, detached: detached || b.prefix === true, andRun,
|
|
609
|
+
...(b.loopVar === true && loopEnd !== undefined ? { certainUntil: loopEnd } : {}),
|
|
610
|
+
...(b.floor !== undefined && b.floor.length > 0 ? { floor: b.floor } : {}),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
leftBody(at, body, j) {
|
|
615
|
+
if (this.blocks[at].after <= body.base)
|
|
616
|
+
return true;
|
|
617
|
+
for (let k = at + 1; k <= j; k++)
|
|
618
|
+
if (this.blocks[k].lowest <= body.base)
|
|
619
|
+
return true;
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
kill(at, names) {
|
|
623
|
+
if (names === "all") {
|
|
624
|
+
this.entries.push({ at, state: "unknown", detached: false, andRun: false });
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
for (const name of names)
|
|
628
|
+
this.entries.push({ at, name, state: "unknown", detached: false, andRun: false });
|
|
629
|
+
}
|
|
630
|
+
tableAt(j, andRunStart) {
|
|
631
|
+
const certain = (e) => {
|
|
632
|
+
if (e.certainUntil !== undefined && j < e.certainUntil)
|
|
633
|
+
return true;
|
|
634
|
+
if (e.detached || !this.heldThrough(e.at, j))
|
|
635
|
+
return false;
|
|
636
|
+
return !e.andRun || e.at >= andRunStart;
|
|
637
|
+
};
|
|
638
|
+
const readerBody = this.blocks[j].body;
|
|
639
|
+
const lost = (u) => {
|
|
640
|
+
if (u.body === undefined)
|
|
641
|
+
return this.mayRunBefore(u.at, j);
|
|
642
|
+
if (readerBody !== undefined && readerBody.id !== u.body.id)
|
|
643
|
+
return true;
|
|
644
|
+
return u.at < j && this.leftBody(u.at, u.body, j);
|
|
645
|
+
};
|
|
646
|
+
const before = (e) => e.at < j || (e.at === j && e.state === "reset");
|
|
647
|
+
const given = (name, at, integer) => this.attributesAt.some((x) => (x.name === undefined || x.name === name) && (integer !== true || x.kind === "integer") && x.at !== at && this.mayRunBefore(x.at, at));
|
|
648
|
+
const stateOf = (name) => {
|
|
649
|
+
if (shellKeepsName(name))
|
|
650
|
+
return "unknown";
|
|
651
|
+
if (this.unreadable.some((u) => (u.name === undefined || u.name === name) && lost(u)))
|
|
652
|
+
return "unknown";
|
|
653
|
+
const values = [];
|
|
654
|
+
const add = (vs) => { for (const v of vs)
|
|
655
|
+
if (!values.some((x) => x.spelled === v.spelled && x.glob === v.glob))
|
|
656
|
+
values.push(v); };
|
|
657
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
658
|
+
const e = this.entries[i];
|
|
659
|
+
if (!before(e) || (e.name !== undefined && e.name !== name))
|
|
660
|
+
continue;
|
|
661
|
+
const state = e.state === "reset" ? (name === "HOME" && this.boundAnywhere("HOME") ? "unknown" : this.initial(name)) : e.state;
|
|
662
|
+
if (state === "unknown")
|
|
663
|
+
return "unknown";
|
|
664
|
+
if (e.name !== undefined && given(e.name, e.at))
|
|
665
|
+
return "unknown";
|
|
666
|
+
add(state);
|
|
667
|
+
if (certain(e))
|
|
668
|
+
return values;
|
|
669
|
+
}
|
|
670
|
+
const start = this.initial(name);
|
|
671
|
+
if (start === "unknown")
|
|
672
|
+
return "unknown";
|
|
673
|
+
add(start);
|
|
674
|
+
return values;
|
|
675
|
+
};
|
|
676
|
+
const touched = (name) => this.entries.some((e) => before(e) && (e.name === undefined || e.name === name));
|
|
677
|
+
return {
|
|
678
|
+
stateOf,
|
|
679
|
+
isInteger: (name) => SHELL_INTEGER_VARIABLES.has(name) || given(name, j, true),
|
|
680
|
+
ifsTouched: touched("IFS"),
|
|
681
|
+
homeTouched: this.homeStartsTouched || touched("HOME"),
|
|
682
|
+
floorOf: (name) => this.entries.filter((e) => e.at < j && e.name === name).flatMap((e) => e.floor ?? []),
|
|
683
|
+
inherited: this.inheritedAt(j),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
export function isArithmeticCommand(segment) {
|
|
688
|
+
const [a, b] = segment.tokens;
|
|
689
|
+
return a?.kind === "group" && a.op === "(" && b?.kind === "group" && b.op === "(";
|
|
690
|
+
}
|
|
691
|
+
export const ARITHMETIC_TEST_OPERATORS = new Set(["-eq", "-ne", "-lt", "-le", "-gt", "-ge"]);
|
|
692
|
+
export function parameterBodySubstitutions(name, body, table) {
|
|
693
|
+
const rest = body.slice(name.length);
|
|
694
|
+
const out = arithmeticSubstitutions(rest.startsWith("[") ? rest.slice(0, rest.indexOf("]") + 1 || undefined) : "", table);
|
|
695
|
+
const offset = /^(?:\[[^\]]*\])?:(?![-=+?])(.*)$/s.exec(rest)?.[1];
|
|
696
|
+
if (offset !== undefined)
|
|
697
|
+
out.push(...substitutionBodies(`${offset}`, true), ...arithmeticSubstitutions(offset, table));
|
|
698
|
+
return [...new Set(out)];
|
|
699
|
+
}
|
|
700
|
+
export function arithmeticSubstitutions(text, table, chain = new Set(), budget = { left: ARITHMETIC_VALUES_MAX }) {
|
|
701
|
+
if (--budget.left < 0)
|
|
702
|
+
return [text];
|
|
703
|
+
const out = [];
|
|
704
|
+
for (const subscript of subscriptsOf(text))
|
|
705
|
+
out.push(...substitutionBodies(subscript));
|
|
706
|
+
if (table !== undefined) {
|
|
707
|
+
for (const name of new Set(text.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [])) {
|
|
708
|
+
if (chain.has(name))
|
|
709
|
+
continue;
|
|
710
|
+
const state = table.stateOf(name);
|
|
711
|
+
const values = state === "unknown" ? table.floorOf(name) : state;
|
|
712
|
+
for (const v of values)
|
|
713
|
+
out.push(...arithmeticSubstitutions(v.spelled, table, new Set([...chain, name]), budget));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return [...new Set(out)];
|
|
717
|
+
}
|
|
718
|
+
const ARITHMETIC_VALUES_MAX = 256;
|
|
719
|
+
export function subscriptsOf(text) {
|
|
720
|
+
const out = [];
|
|
721
|
+
for (let i = 0; i < text.length; i++) {
|
|
722
|
+
if (text[i] !== "[")
|
|
723
|
+
continue;
|
|
724
|
+
let depth = 0;
|
|
725
|
+
let j = i;
|
|
726
|
+
for (; j < text.length; j++) {
|
|
727
|
+
if (text[j] === "[")
|
|
728
|
+
depth++;
|
|
729
|
+
else if (text[j] === "]" && --depth === 0)
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
out.push(text.slice(i + 1, j));
|
|
733
|
+
i = j;
|
|
734
|
+
}
|
|
735
|
+
return out;
|
|
736
|
+
}
|
|
737
|
+
function substitutionBodies(subscript, keepQuotes = false) {
|
|
738
|
+
if (!/\$\(|`|[<>]\(/.test(subscript))
|
|
739
|
+
return [];
|
|
740
|
+
const unquoted = keepQuotes ? subscript : subscript.replace(/["']/g, "");
|
|
741
|
+
const lexed = lexShell(unquoted);
|
|
742
|
+
if ("parseFailure" in lexed)
|
|
743
|
+
return [unquoted];
|
|
744
|
+
const bodies = lexed.segments.flatMap((s) => s.substitutions);
|
|
745
|
+
return bodies.length > 0 ? bodies : [unquoted];
|
|
746
|
+
}
|