@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
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTildeSpelling, } from "../../shell/lex.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTextOf, literalTildeSpelling, } from "../../shell/lex.js";
|
|
2
|
+
import { readOfText, readsOf, rewritten, spelledWithHome, isArithmeticBody, unreadableSentenceOf } from "./shell-read-word.js";
|
|
3
|
+
import { assignmentArithmetic, bare, listedVerbCandidates, pathSubcommandOf, programTextSentence, pushAssignment, roleReads, subscriptOnly, unlistedWords } from "./shell-read-argv.js";
|
|
4
|
+
import { ARITHMETIC_TEST_OPERATORS, BindingThread, arithmeticSubstitutions, isArithmeticCommand, bindingEffectOf, parameterBodySubstitutions, projectSegment, valueOf, } from "./shell-read-bindings.js";
|
|
5
|
+
import { programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
|
|
6
|
+
import { POLL_LOOP_MAX_BEATS, attachedOptionPayloads, resolveOperandLexically, isPathShapedToken, programTextSlotFiles, } from "./bash-readonly-classifier.js";
|
|
7
|
+
import { BLOCK_WORD_NAMES, commandLineOptionSpellings, declaredAttributes, isDeclarationBuiltin, isInertBuiltin, launcherOptionPathRole, launcherOptionStep, readOptionRole, programLanguageOf, programTextOpensOf, selectedOptions, movesBaseUnfollowed, opensNoOperand, programDelivery, programRow, readArgRoles, runsShellText, } from "./program-read-model.js";
|
|
5
8
|
import { isAbsolutePathForm } from "./safety.js";
|
|
6
|
-
import { codeBodies, reentryText } from "./shell-reentry-text.js";
|
|
9
|
+
import { codeBodies, pipedTextOf, reentryText, scriptFileAt, stdinTextsOf } from "./shell-reentry-text.js";
|
|
7
10
|
export const READ_CANDIDATE_PROVENANCES = Object.freeze([
|
|
8
11
|
"operand",
|
|
9
12
|
"option_value",
|
|
@@ -15,28 +18,52 @@ export const READ_CANDIDATE_PROVENANCES = Object.freeze([
|
|
|
15
18
|
"cd_target",
|
|
16
19
|
"program_path",
|
|
17
20
|
"script_slot_file",
|
|
21
|
+
"mount_source",
|
|
22
|
+
"object_path",
|
|
18
23
|
]);
|
|
19
24
|
const NESTING_MAX = 4;
|
|
20
25
|
const BASE_CANDIDATE_MAX = 256;
|
|
21
|
-
export function planShellReads(command, decl, depth = 0,
|
|
26
|
+
export function planShellReads(command, decl, depth = 0, start) {
|
|
22
27
|
const shape = { grouped: false, backgrounded: false, strayRedirection: false };
|
|
23
28
|
if (depth > NESTING_MAX)
|
|
24
29
|
return { segments: [], unreadable: `command text nested more than ${NESTING_MAX} levels deep`, shape };
|
|
25
30
|
const lexed = lexShell(command);
|
|
26
31
|
if ("parseFailure" in lexed)
|
|
27
32
|
return { segments: [], unreadable: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason], shape };
|
|
28
|
-
const
|
|
29
|
-
const unreadable = unreadableSentenceOf(lexed, command, !arithmetic);
|
|
30
|
-
const nestedPlan = (text, arithmetic = false) => planShellReads(text, decl, depth + 1, home === undefined, arithmetic);
|
|
33
|
+
const unreadable = unreadableSentenceOf(lexed, command);
|
|
31
34
|
const projected = lexed.segments.map((segment) => projectSegment(segment));
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
35
|
+
const named = projected.map((p, si) => bindingEffectOf(lexed.segments[si], p.argv, p, () => "unknown"));
|
|
36
|
+
const boundHere = (name) => named.some((e) => e.all === "unknown" || e.bindings.some((b) => b.name === name));
|
|
37
|
+
const boundAnywhere = (name) => boundHere(name) || start?.boundAnywhere(name) === true;
|
|
38
|
+
const declaredHome = decl.homeDir !== undefined ? [{ spelled: decl.homeDir }] : "unknown";
|
|
39
|
+
const initial = start?.initial ?? ((name) => (name === "HOME" ? declaredHome : "unknown"));
|
|
40
|
+
const loopShapes = projected.map((p) => loopShapeOf(p));
|
|
41
|
+
const loopEndOf = new Map();
|
|
42
|
+
{
|
|
43
|
+
const open = [];
|
|
44
|
+
loopShapes.forEach((l, si) => {
|
|
45
|
+
if (l === "head")
|
|
46
|
+
open.push(si);
|
|
47
|
+
else if (l === "end") {
|
|
48
|
+
const h = open.pop();
|
|
49
|
+
if (h !== undefined)
|
|
50
|
+
loopEndOf.set(h, si);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const names = new BindingThread(lexed.segments, named, initial, boundAnywhere, loopEndOf, start?.homeTouched === true, start?.inherited);
|
|
55
|
+
const laterStart = { initial: (name) => (name === "HOME" && !boundAnywhere("HOME") ? declaredHome : "unknown"), boundAnywhere, homeTouched: boundAnywhere("HOME"), inherited: names.inheritedAt() };
|
|
56
|
+
const nestedPlanner = (table) => (text, kind) => planShellReads(text, decl, depth + 1, kind === "substitution" ? { initial: (name) => table.stateOf(name), boundAnywhere, homeTouched: table.homeTouched, inherited: table.inherited } : laterStart);
|
|
57
|
+
const empty = lexed.segments.map((segment, si) => segmentIsEmpty(segment, projected[si]));
|
|
58
|
+
const facts = projected.map(() => undefined);
|
|
59
|
+
const tables = projected.map(() => undefined);
|
|
60
|
+
const factsAt = (si, table) => {
|
|
61
|
+
tables[si] ??= table;
|
|
62
|
+
const piped = si > 0 && lexed.segments[si].connector === "|" ? pipedTextOf(lexed.segments[si - 1], projected[si - 1].argv) : undefined;
|
|
63
|
+
return (facts[si] ??= segmentFacts(lexed.segments[si], projected[si], decl, table, nestedPlanner(table), piped));
|
|
64
|
+
};
|
|
65
|
+
const basesOf = projected.map(() => new Set());
|
|
66
|
+
const unmodelled = projected.map(() => false);
|
|
40
67
|
let candidates = [{ base: decl.cwd, skippedCd: -1 }];
|
|
41
68
|
let baseLost = false;
|
|
42
69
|
let floorBases;
|
|
@@ -48,22 +75,25 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false, ar
|
|
|
48
75
|
let crossedAllAnd = true;
|
|
49
76
|
let crossedAnyOr = false;
|
|
50
77
|
let previousEmitted = -1;
|
|
51
|
-
const reaches =
|
|
78
|
+
const reaches = projected.map(() => undefined);
|
|
52
79
|
const loops = [];
|
|
53
80
|
const thread = (si, replay) => {
|
|
54
|
-
const
|
|
81
|
+
const connector = lexed.segments[si].connector;
|
|
55
82
|
if (previousEmitted >= 0) {
|
|
56
|
-
const connector = f.connector;
|
|
57
83
|
if (replay || connector !== "&&")
|
|
58
84
|
crossedAllAnd = false;
|
|
59
85
|
if (!replay && connector === "||")
|
|
60
86
|
crossedAnyOr = true;
|
|
61
87
|
}
|
|
62
|
-
if (
|
|
88
|
+
if (empty[si]) {
|
|
89
|
+
if (!replay)
|
|
90
|
+
factsAt(si, names.tableAt(si, andRunStart));
|
|
63
91
|
return;
|
|
92
|
+
}
|
|
64
93
|
if (previousEmitted >= 0 && !crossedAllAnd)
|
|
65
94
|
andRunStart = crossedAnyOr ? si + 1 : si;
|
|
66
|
-
const
|
|
95
|
+
const f = replay ? facts[si] : factsAt(si, names.tableAt(si, andRunStart));
|
|
96
|
+
const pipeFed = f.connector === "|" || lexed.segments[si + 1]?.connector === "|";
|
|
67
97
|
reaches[si] ??= { andJoined: previousEmitted >= 0 && crossedAllAnd, orBroken: !crossedAllAnd && crossedAnyOr, moveProvable: f.moveProvable && !replay && !pipeFed };
|
|
68
98
|
crossedAllAnd = true;
|
|
69
99
|
crossedAnyOr = false;
|
|
@@ -107,46 +137,67 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false, ar
|
|
|
107
137
|
else
|
|
108
138
|
candidates = next;
|
|
109
139
|
};
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
const bind = (si) => {
|
|
141
|
+
const table = tables[si];
|
|
142
|
+
if (table === undefined)
|
|
143
|
+
return;
|
|
144
|
+
const end = loopEndOf.get(si);
|
|
145
|
+
if (end !== undefined) {
|
|
146
|
+
const body = named.slice(si + 1, end);
|
|
147
|
+
names.kill(si, body.some((e) => e.all === "unknown") ? "all" : body.flatMap((e) => e.bindings.map((b) => b.name)));
|
|
148
|
+
}
|
|
149
|
+
const segment = lexed.segments[si];
|
|
150
|
+
const effect = bindingEffectOf(segment, projected[si].argv, projected[si], (w, quoted, overlay) => valueOf(w, table, quoted, overlay));
|
|
151
|
+
const { all, ...rest } = effect;
|
|
152
|
+
names.apply(si, all === "reset" ? rest : effect, segment.connector === "&&" || segment.connector === "||", end);
|
|
153
|
+
};
|
|
154
|
+
for (let si = 0; si < projected.length; si++) {
|
|
155
|
+
const shapeOfLoop = loopShapes[si];
|
|
156
|
+
if (named[si].all === "reset")
|
|
157
|
+
names.apply(si, { bindings: [], all: "reset" }, false);
|
|
158
|
+
thread(si, false);
|
|
159
|
+
bind(si);
|
|
160
|
+
if (shapeOfLoop === "head") {
|
|
161
|
+
loops.push({ bodyStart: si + 1, beats: facts[si].loopHead?.beats });
|
|
115
162
|
continue;
|
|
116
163
|
}
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
for (let beat = 1; beat < frame.beats && !baseLost; beat++) {
|
|
133
|
-
for (let k = frame.bodyStart; k < si; k++)
|
|
134
|
-
thread(k, true);
|
|
135
|
-
}
|
|
164
|
+
if (shapeOfLoop !== "end")
|
|
165
|
+
continue;
|
|
166
|
+
const frame = loops.pop();
|
|
167
|
+
if (frame === undefined)
|
|
168
|
+
continue;
|
|
169
|
+
const body = facts.slice(frame.bodyStart, si);
|
|
170
|
+
const moves = body.some((b) => b?.baseMove !== undefined);
|
|
171
|
+
if (!moves)
|
|
172
|
+
continue;
|
|
173
|
+
if (frame.beats === undefined) {
|
|
174
|
+
for (let k = frame.bodyStart; k < si; k++)
|
|
175
|
+
unmodelled[k] = true;
|
|
176
|
+
loseBase(candidates.filter((c) => c.skippedCd < andRunStart));
|
|
136
177
|
continue;
|
|
137
178
|
}
|
|
138
|
-
|
|
179
|
+
for (let beat = 1; beat < frame.beats && !baseLost; beat++) {
|
|
180
|
+
for (let k = frame.bodyStart; k < si; k++)
|
|
181
|
+
thread(k, true);
|
|
182
|
+
}
|
|
139
183
|
}
|
|
184
|
+
const planned = facts.map((f) => f);
|
|
185
|
+
const inert = depth === 0 && unreadable === undefined && planned.every(segmentOpensNothing);
|
|
186
|
+
const settled = inert ? planned.map((f) => ({ ...f, reads: f.reads.filter((c) => c.provenance !== "env_assignment") })) : planned;
|
|
187
|
+
shape.grouped = projected.some((p) => p.grouped);
|
|
188
|
+
shape.backgrounded = lexed.segments.some((s) => s.connector === "&") || settled.some((f) => f.backgrounds === true);
|
|
189
|
+
shape.strayRedirection = projected.some((p) => p.argv.length === 0 && p.redirects.length > 0);
|
|
140
190
|
const segments = [];
|
|
141
|
-
for (let si = 0; si <
|
|
142
|
-
const f =
|
|
191
|
+
for (let si = 0; si < settled.length; si++) {
|
|
192
|
+
const f = settled[si];
|
|
143
193
|
if (f.empty)
|
|
144
194
|
continue;
|
|
145
195
|
const bases = unmodelled[si] ? "unmodelled" : [...basesOf[si]];
|
|
146
196
|
const floor = unmodelled[si] && floorBases !== undefined && floorBases.length > 0 ? floorBases : undefined;
|
|
147
197
|
const withBases = (c) => {
|
|
148
198
|
const relative = relativeSpelling(c.spelled);
|
|
149
|
-
|
|
199
|
+
const here = c.provenance === "object_path" && bases !== "unmodelled" ? [...new Set(bases.flatMap(ancestorsOf))] : bases;
|
|
200
|
+
return { ...c, bases: relative ? here : [], ...(relative && floor !== undefined ? { floorBases: floor } : {}) };
|
|
150
201
|
};
|
|
151
202
|
segments.push({
|
|
152
203
|
text: f.text,
|
|
@@ -156,6 +207,7 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false, ar
|
|
|
156
207
|
...(f.verb !== undefined ? { verb: f.verb } : {}),
|
|
157
208
|
...(f.reentry !== undefined ? { reentry: f.reentry } : {}),
|
|
158
209
|
...(f.unreadProgramText !== undefined ? { unreadProgramText: f.unreadProgramText } : {}),
|
|
210
|
+
...(f.stdin !== undefined ? { stdin: f.stdin } : {}),
|
|
159
211
|
declines: f.declines,
|
|
160
212
|
reads: dedupe(f.reads).map(withBases),
|
|
161
213
|
redirects: f.redirects.map((r) => (r.target === undefined ? { op: r.op, direction: r.direction } : { op: r.op, direction: r.direction, target: withBases(r.target) })),
|
|
@@ -167,57 +219,88 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false, ar
|
|
|
167
219
|
}
|
|
168
220
|
return { segments, shape, ...(unreadable !== undefined ? { unreadable } : {}) };
|
|
169
221
|
}
|
|
222
|
+
function ancestorsOf(dir) {
|
|
223
|
+
const out = [dir];
|
|
224
|
+
for (let d = dir, up = parentOf(d); up !== d; d = up, up = parentOf(d))
|
|
225
|
+
out.push(up);
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function parentOf(dir) {
|
|
229
|
+
return resolveOperandLexically(dir, "..", undefined) ?? dir;
|
|
230
|
+
}
|
|
231
|
+
function loopShapeOf(p) {
|
|
232
|
+
const head = p.argv[0];
|
|
233
|
+
if (head === undefined)
|
|
234
|
+
return undefined;
|
|
235
|
+
if (head.raw === head.folded && (head.folded === "for" || head.folded === "select") && BLOCK_WORD_NAMES.has(head.folded))
|
|
236
|
+
return "head";
|
|
237
|
+
if (head.raw === head.folded && head.folded === "done" && BLOCK_WORD_NAMES.has(head.folded))
|
|
238
|
+
return "end";
|
|
239
|
+
const pos = readProgramPosition(p.argv);
|
|
240
|
+
return pos.controlWords.some((i) => p.argv[i].folded === "while" || p.argv[i].folded === "until") ? "head" : undefined;
|
|
241
|
+
}
|
|
242
|
+
function segmentIsEmpty(segment, p) {
|
|
243
|
+
if (p.argv.length > 0 || p.redirects.length > 0 || segment.substitutions.length > 0)
|
|
244
|
+
return false;
|
|
245
|
+
return !segment.tokens.some((t) => t.kind === "heredoc" && !t.quoted && (substitutionsInHeredocBody(t.body)?.length ?? 1) > 0);
|
|
246
|
+
}
|
|
170
247
|
function segmentOpensNothing(f) {
|
|
171
248
|
if (f.empty)
|
|
172
249
|
return true;
|
|
173
250
|
const program = f.verb;
|
|
174
251
|
const at = program === undefined ? -1 : f.argv.findIndex((w) => w.folded === program.name);
|
|
175
|
-
const inertProgram = program === undefined || (program.name === program.basename &&
|
|
252
|
+
const inertProgram = program === undefined || (program.name === program.basename && isInertBuiltin(program.basename) && !(isDeclarationBuiltin(program.basename) && f.argv.slice(at + 1).some((w) => /^[-+]/.test(w.folded))));
|
|
176
253
|
return inertProgram && f.argv.every((w) => !/[[\]$`]/.test(w.raw)) && f.nested.length === 0 && f.reentry === undefined && f.declines.length === 0 && f.launcherChain.length === 0 && f.redirects.every((r) => r.direction !== "in") && f.reads.every((c) => c.provenance === "env_assignment");
|
|
177
254
|
}
|
|
178
|
-
function
|
|
179
|
-
const
|
|
180
|
-
let grouped = tokens.some((t) => t.kind === "group");
|
|
181
|
-
dropDefinitionHeads(tokens);
|
|
182
|
-
const words = tokens.filter((t) => {
|
|
183
|
-
if (t.kind !== "word")
|
|
184
|
-
return false;
|
|
185
|
-
if (t.raw === t.folded && (t.folded === "{" || t.folded === "}")) {
|
|
186
|
-
grouped = true;
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
return true;
|
|
190
|
-
});
|
|
191
|
-
return {
|
|
192
|
-
argv: words,
|
|
193
|
-
redirects: tokens.filter((t) => t.kind === "redirect"),
|
|
194
|
-
grouped,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function segmentFacts(segment, p, decl, home, nestedPlan) {
|
|
198
|
-
const facts = segmentFactsRead(segment, p, decl, home, nestedPlan);
|
|
255
|
+
function segmentFacts(segment, p, decl, table, nestedPlan, piped) {
|
|
256
|
+
const facts = segmentFactsRead(segment, p, decl, table, nestedPlan, piped);
|
|
199
257
|
const unresolvedRaw = new Set();
|
|
200
258
|
for (const t of segment.tokens) {
|
|
201
259
|
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
202
260
|
if (w !== undefined && w.pieces.some((p) => p.literal === undefined && p.expansion === undefined))
|
|
203
261
|
unresolvedRaw.add(w.raw);
|
|
204
262
|
}
|
|
205
|
-
const homeWithheld =
|
|
206
|
-
const floor = (c) => {
|
|
263
|
+
const homeWithheld = decl.homeDir !== undefined && table.stateOf("HOME") === "unknown";
|
|
264
|
+
const floor = (c) => {
|
|
265
|
+
if (c.floorSpelled !== undefined)
|
|
266
|
+
return c;
|
|
267
|
+
const f = (c.expands === "unresolvable" || c.spelled.startsWith("./~")) && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined;
|
|
268
|
+
return f === undefined ? c : { ...c, floorSpelled: f };
|
|
269
|
+
};
|
|
207
270
|
const unsettle = (c) => floor((unresolvedRaw.has(c.raw) || (homeWithheld && c.spelled.startsWith("~"))) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
|
|
271
|
+
const each = (c) => {
|
|
272
|
+
const { alternatives, floors, ...one } = c;
|
|
273
|
+
const shadows = (floors ?? []).map((v) => ({ spelled: v.spelled, raw: one.raw, provenance: one.provenance, expands: v.glob === true ? "glob" : false, denyOnly: true }));
|
|
274
|
+
return [...[one, ...(alternatives ?? []).map((spelled) => ({ ...one, spelled }))].map(unsettle), ...shadows];
|
|
275
|
+
};
|
|
208
276
|
const move = facts.baseMove;
|
|
209
277
|
return {
|
|
210
278
|
...facts,
|
|
211
|
-
reads: facts.reads.
|
|
212
|
-
|
|
279
|
+
reads: facts.reads.flatMap(each),
|
|
280
|
+
redirects: facts.redirects.map((r) => (r.target === undefined ? r : { ...r, target: unsettle(r.target) })),
|
|
281
|
+
...(move !== undefined && typeof move.target !== "string" ? { baseMove: { target: unsettle(move.target.alternatives !== undefined ? { ...each(move.target)[0], expands: "unresolvable" } : move.target) } } : {}),
|
|
213
282
|
};
|
|
214
283
|
}
|
|
215
|
-
function segmentFactsRead(segment, p, decl,
|
|
284
|
+
function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
216
285
|
const nested = [];
|
|
286
|
+
const ctx = { table, reentered: [], programTextSeen: false };
|
|
217
287
|
const arithmetic = segment.tokens.flatMap((t) => (t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined)?.pieces ?? []).flatMap((q) => (q.expansion?.kind === "arithmetic" && q.expansion.body !== undefined && isArithmeticBody(q.expansion.body) ? [q.expansion.body] : []));
|
|
218
288
|
const count = (list, body) => list.filter((b) => b === body).length;
|
|
219
|
-
|
|
220
|
-
|
|
289
|
+
const arithmeticBody = (body) => {
|
|
290
|
+
const l = lexShell(body);
|
|
291
|
+
if ("parseFailure" in l)
|
|
292
|
+
nested.push(nestedPlan(body, "substitution"));
|
|
293
|
+
else
|
|
294
|
+
for (const s of l.segments.flatMap((sg) => sg.substitutions))
|
|
295
|
+
nested.push(nestedPlan(s, "substitution"));
|
|
296
|
+
ctx.reentered.push(...arithmeticSubstitutions(body, table));
|
|
297
|
+
};
|
|
298
|
+
for (const body of segment.substitutions) {
|
|
299
|
+
if (count(arithmetic, body) > 0 && count(arithmetic, body) === count(segment.substitutions, body))
|
|
300
|
+
arithmeticBody(body);
|
|
301
|
+
else
|
|
302
|
+
nested.push(nestedPlan(body, "substitution"));
|
|
303
|
+
}
|
|
221
304
|
let unreadableHeredoc = false;
|
|
222
305
|
for (const t of segment.tokens) {
|
|
223
306
|
if (t.kind !== "heredoc" || t.quoted)
|
|
@@ -227,12 +310,24 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
227
310
|
unreadableHeredoc = true;
|
|
228
311
|
continue;
|
|
229
312
|
}
|
|
230
|
-
for (const s of inner)
|
|
231
|
-
|
|
313
|
+
for (const s of inner) {
|
|
314
|
+
if (s.kind === "arithmetic" && isArithmeticBody(s.body))
|
|
315
|
+
arithmeticBody(s.body);
|
|
316
|
+
else
|
|
317
|
+
nested.push(nestedPlan(s.body, "substitution"));
|
|
318
|
+
}
|
|
232
319
|
}
|
|
233
320
|
if (unreadableHeredoc)
|
|
234
321
|
nested.push({ segments: [], unreadable: "a here-document body carries a substitution this lexer cannot delimit", shape: { grouped: false, backgrounded: false, strayRedirection: false } });
|
|
235
|
-
|
|
322
|
+
if (isArithmeticCommand(segment))
|
|
323
|
+
ctx.reentered.push(...arithmeticSubstitutions(p.argv.map((w) => w.folded).join(" "), table));
|
|
324
|
+
for (const t of segment.tokens) {
|
|
325
|
+
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
326
|
+
for (const q of w?.pieces ?? [])
|
|
327
|
+
if (q.expansion?.kind === "parameter" && q.expansion.name !== undefined && q.expansion.body !== undefined)
|
|
328
|
+
ctx.reentered.push(...parameterBodySubstitutions(q.expansion.name, q.expansion.body, table));
|
|
329
|
+
}
|
|
330
|
+
const redirects = p.redirects.map((r) => redirectFact(r, table, decl.homeDir));
|
|
236
331
|
const reads = [];
|
|
237
332
|
for (const r of redirects)
|
|
238
333
|
if (r.direction === "in" && r.target !== undefined)
|
|
@@ -244,42 +339,60 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
244
339
|
redirects,
|
|
245
340
|
nested,
|
|
246
341
|
};
|
|
247
|
-
const toks = readsOf(p.argv,
|
|
342
|
+
const toks = readsOf(p.argv, table, decl.homeDir);
|
|
343
|
+
const done = (f) => {
|
|
344
|
+
const reentered = [...new Set(ctx.reentered)];
|
|
345
|
+
for (const body of reentered)
|
|
346
|
+
nested.push(nestedPlan(body, "later"));
|
|
347
|
+
return {
|
|
348
|
+
...base,
|
|
349
|
+
...f,
|
|
350
|
+
...(reentered.length > 0 ? { reentry: "shell" } : {}),
|
|
351
|
+
...(f.unreadProgramText === undefined && ctx.programText !== undefined ? { unreadProgramText: ctx.programText } : {}),
|
|
352
|
+
};
|
|
353
|
+
};
|
|
248
354
|
const head = toks[0]?.folded;
|
|
249
355
|
if (head === "coproc" && toks[0].raw === head && toks.length > 1) {
|
|
250
|
-
const tail = (from) => ({ argv: p.argv.slice(from)
|
|
251
|
-
const first = segmentFacts(segment, tail(1), decl,
|
|
356
|
+
const tail = (from) => ({ ...p, argv: p.argv.slice(from) });
|
|
357
|
+
const first = segmentFacts(segment, tail(1), decl, table, nestedPlan, piped);
|
|
252
358
|
if (toks.length < 3)
|
|
253
359
|
return { ...first, backgrounds: true };
|
|
254
|
-
const second = segmentFacts(segment, tail(2), decl,
|
|
360
|
+
const second = segmentFacts(segment, tail(2), decl, table, nestedPlan, piped);
|
|
255
361
|
const seen = new Set(first.nested.map((n) => JSON.stringify(n.segments.map((sg) => sg.text))));
|
|
256
362
|
const extraNested = second.nested.filter((n) => !seen.has(JSON.stringify(n.segments.map((sg) => sg.text))));
|
|
257
363
|
return { ...first, ...(second.reentry === "shell" ? { reentry: second.reentry } : {}), reads: [...first.reads, ...second.reads], nested: [...first.nested, ...extraNested], backgrounds: true };
|
|
258
364
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
365
|
+
const loop = loopShapeOf(p);
|
|
366
|
+
if (head !== undefined && toks[0].raw === head && BLOCK_WORD_NAMES.has(head)) {
|
|
367
|
+
if (loop === "head")
|
|
368
|
+
return done({ empty: false, launcherChain: [], declines: [], reads, moveProvable: false, loopHead: { beats: literalBeats(toks) } });
|
|
369
|
+
if (head === "[[") {
|
|
370
|
+
toks.forEach((r, i) => {
|
|
371
|
+
if (ARITHMETIC_TEST_OPERATORS.has(toks[i - 1]?.folded ?? "") || ARITHMETIC_TEST_OPERATORS.has(toks[i + 1]?.folded ?? "") || toks[i - 1]?.folded === "-v")
|
|
372
|
+
ctx.reentered.push(...arithmeticSubstitutions(r.folded, table));
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return done({ empty: false, launcherChain: [], declines: [], reads, moveProvable: false });
|
|
265
376
|
}
|
|
266
377
|
const pos = readProgramPosition(p.argv);
|
|
267
378
|
const written = pos.written;
|
|
268
379
|
if (written === undefined)
|
|
269
|
-
return {
|
|
270
|
-
const loopHead =
|
|
380
|
+
return done({ empty: segmentIsEmpty(segment, p), launcherChain: [], declines: [], reads, moveProvable: false });
|
|
381
|
+
const loopHead = loop === "head" ? { beats: undefined } : undefined;
|
|
271
382
|
const launcherChain = pos.launchers.map((l) => l.name);
|
|
272
383
|
const launcherIndices = new Set(pos.launchers.map((l) => l.index));
|
|
273
384
|
const declines = [];
|
|
274
|
-
const decline = shellScanDeclineOf(pos,
|
|
385
|
+
const decline = shellScanDeclineOf(pos, opensNoOperand);
|
|
275
386
|
if (decline !== undefined)
|
|
276
387
|
declines.push(decline);
|
|
277
388
|
const program = pos.program;
|
|
278
389
|
const basename = program?.basename;
|
|
279
390
|
const listed = basename !== undefined && (decl.readers.has(basename) || basename === "cd");
|
|
280
391
|
const verb = program === undefined ? undefined : { name: program.name, basename: program.basename, listed };
|
|
281
|
-
for (const k of pos.assignments)
|
|
392
|
+
for (const k of pos.assignments) {
|
|
282
393
|
pushAssignment(reads, toks[k].word, "env_assignment");
|
|
394
|
+
assignmentArithmetic(toks[k].word, table.isInteger(toks[k].word.assignment?.name ?? ""), ctx);
|
|
395
|
+
}
|
|
283
396
|
for (const l of pos.launchers) {
|
|
284
397
|
const r = toks[l.index];
|
|
285
398
|
if (isPathShapedToken(r.folded))
|
|
@@ -290,19 +403,30 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
290
403
|
const assignmentIndices = new Set(pos.assignments);
|
|
291
404
|
const chainEnd = pos.program?.index ?? toks.length;
|
|
292
405
|
const launcherValueIndices = new Set();
|
|
406
|
+
const launcherPathValues = new Map();
|
|
293
407
|
for (const l of pos.launchers) {
|
|
294
|
-
const row =
|
|
295
|
-
if (row === undefined)
|
|
408
|
+
const row = programRow(programBasename(l.name));
|
|
409
|
+
if (row?.launcher === undefined)
|
|
296
410
|
continue;
|
|
297
411
|
for (let j = l.index + 1; j < chainEnd; j++) {
|
|
298
412
|
const w = toks[j].folded;
|
|
299
413
|
if (!w.startsWith("-") || w === "-")
|
|
300
414
|
break;
|
|
301
|
-
const
|
|
302
|
-
if (
|
|
415
|
+
const step = launcherOptionStep(row, w);
|
|
416
|
+
if (step === undefined)
|
|
303
417
|
break;
|
|
304
|
-
if (
|
|
305
|
-
|
|
418
|
+
if (step === 2) {
|
|
419
|
+
const role = launcherOptionPathRole(row, w);
|
|
420
|
+
if (role === undefined)
|
|
421
|
+
launcherValueIndices.add(j + 1);
|
|
422
|
+
else
|
|
423
|
+
launcherPathValues.set(j + 1, { value: toks[j + 1].folded, role });
|
|
424
|
+
j++;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const own = readOptionRole(row, w);
|
|
428
|
+
if (own.form === "attached" && own.role?.kind === "path")
|
|
429
|
+
launcherPathValues.set(j, { value: literalTildeSpelling(own.value), role: own.role });
|
|
306
430
|
}
|
|
307
431
|
}
|
|
308
432
|
for (let j = 0; j < chainEnd; j++) {
|
|
@@ -310,6 +434,11 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
310
434
|
continue;
|
|
311
435
|
const r = toks[j];
|
|
312
436
|
const word = r.folded;
|
|
437
|
+
const pathValue = launcherPathValues.get(j);
|
|
438
|
+
if (pathValue !== undefined) {
|
|
439
|
+
reads.push(...roleReads(r, "option_value", pathValue.value, pathValue.role, "literal", ctx));
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
313
442
|
if (word.startsWith("-") && word !== "-") {
|
|
314
443
|
for (const payload of attachedOptionPayloads(word))
|
|
315
444
|
reads.push(bare(r, "unlisted_word", payload));
|
|
@@ -332,74 +461,82 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
332
461
|
if (pos.argvFromStdin)
|
|
333
462
|
reentry = "argv";
|
|
334
463
|
if (pos.refusal !== undefined) {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const text = commandStringValue(row.commandString, toks, pos.refusal.option);
|
|
464
|
+
const commandLine = commandLineOptionSpellings(programRow(programBasename(pos.refusal.owner)));
|
|
465
|
+
if (pos.refusal.kind === "option_at_program_position" && commandLine.length > 0) {
|
|
466
|
+
const text = commandStringValue(commandLine, toks, pos.refusal.option);
|
|
339
467
|
if (text !== undefined) {
|
|
340
|
-
nested.push(nestedPlan(text.value));
|
|
468
|
+
nested.push(nestedPlan(text.value, "later"));
|
|
341
469
|
reentry = "shell";
|
|
342
|
-
unlistedWords(reads, toks, text.next, launcherIndices, "unlisted_word");
|
|
343
|
-
return {
|
|
470
|
+
unlistedWords(reads, toks, text.next, launcherIndices, "unlisted_word", ctx);
|
|
471
|
+
return done({ empty: false, launcherChain, ...(verb !== undefined ? { verb } : {}), reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}) });
|
|
344
472
|
}
|
|
345
473
|
}
|
|
346
|
-
if (programPositionRunsShell(pos,
|
|
474
|
+
if (programPositionRunsShell(pos, runsShellText)) {
|
|
347
475
|
reentry = "shell";
|
|
348
476
|
const first = pos.launchers[0]?.index ?? written;
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
477
|
+
const runs = pos.refusal.kind === "control_syntax_at_program_position" ? pos.refusal : undefined;
|
|
478
|
+
const text = runs?.handsText === true ? toks.slice(first + 1).map((r) => r.folded).filter((w) => !w.startsWith("-") || w === "-").join(" ") : "";
|
|
479
|
+
const shell = { name: runs?.word ?? "sh", basename: "sh", listed: decl.readers.has("sh") };
|
|
480
|
+
const stdin = text.length > 0 ? [] : stdinTextsOf(segment, piped);
|
|
481
|
+
for (const t of new Set([text, ...stdin.map((s) => (s.literal === undefined ? "" : reentryText(shell.basename, [], s.literal) ?? ""))]))
|
|
482
|
+
if (t.length > 0)
|
|
483
|
+
nested.push(nestedPlan(t, "later"));
|
|
484
|
+
return done({ empty: false, launcherChain, verb: shell, reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), ...(stdin.length > 0 ? { stdin } : {}) });
|
|
353
485
|
}
|
|
354
486
|
const from = pos.launchers[0]?.index ?? written;
|
|
355
|
-
unlistedWords(reads, toks, from, launcherIndices, "unlisted_word");
|
|
356
|
-
return {
|
|
487
|
+
unlistedWords(reads, toks, from, launcherIndices, "unlisted_word", ctx);
|
|
488
|
+
return done({ empty: false, launcherChain, ...(verb !== undefined ? { verb } : {}), ...(reentry !== undefined ? { reentry } : {}), declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}) });
|
|
357
489
|
}
|
|
358
490
|
if (program === undefined) {
|
|
359
491
|
if (pos.launchers.length > 0)
|
|
360
|
-
unlistedWords(reads, toks, pos.launchers[0].index, launcherIndices, "unlisted_word");
|
|
361
|
-
return {
|
|
492
|
+
unlistedWords(reads, toks, pos.launchers[0].index, launcherIndices, "unlisted_word", ctx);
|
|
493
|
+
return done({ empty: false, launcherChain, ...(reentry !== undefined ? { reentry } : {}), declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}) });
|
|
362
494
|
}
|
|
363
495
|
const argReads = toks.slice(program.index + 1);
|
|
364
496
|
const args = argReads.map((r) => r.folded);
|
|
365
|
-
const texts = argReads.map((r) => (r.word.pieces
|
|
497
|
+
const texts = argReads.map((r) => literalTextOf(r.word.pieces) ?? r.folded);
|
|
366
498
|
const programRead = toks[program.index];
|
|
367
499
|
if (rewritten(programRead) || isPathShapedToken(programRead.folded))
|
|
368
500
|
reads.push(bare(programRead, "program_path"));
|
|
369
|
-
if (
|
|
501
|
+
if (runsShellText(basename)) {
|
|
370
502
|
reentry = "shell";
|
|
503
|
+
const stdin = stdinTextsOf(segment, piped);
|
|
371
504
|
const text = reentryText(basename, texts);
|
|
372
|
-
|
|
505
|
+
const scripts = [text, reentryText(basename, args), ...stdin.map((s) => (s.literal === undefined ? undefined : reentryText(basename, texts, s.literal)))];
|
|
506
|
+
for (const t of new Set(scripts))
|
|
373
507
|
if (t !== undefined)
|
|
374
|
-
nested.push(nestedPlan(t));
|
|
508
|
+
nested.push(nestedPlan(t, "later"));
|
|
375
509
|
if (basename === "source" || basename === ".") {
|
|
376
510
|
const file = argReads.find((a) => !a.folded.startsWith("-") || a.folded === "-");
|
|
377
511
|
if (file !== undefined)
|
|
378
512
|
reads.push(bare(file, "operand"));
|
|
379
513
|
}
|
|
380
|
-
else
|
|
381
|
-
const file =
|
|
382
|
-
if (file !== undefined
|
|
383
|
-
reads.push(bare(file, "operand"));
|
|
514
|
+
else {
|
|
515
|
+
const file = scriptFileAt(basename, args);
|
|
516
|
+
if (file !== undefined)
|
|
517
|
+
reads.push(bare(argReads[file], "operand"));
|
|
384
518
|
}
|
|
385
|
-
return {
|
|
519
|
+
return done({ empty: false, launcherChain, verb: verb, reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), ...(stdin.length > 0 ? { stdin } : {}) });
|
|
386
520
|
}
|
|
387
|
-
const
|
|
388
|
-
|
|
521
|
+
const row = programRow(basename);
|
|
522
|
+
const delivery = programDelivery(row, args);
|
|
523
|
+
if (delivery !== undefined)
|
|
389
524
|
reentry = "interpreter";
|
|
390
525
|
const moveProvable = pos.controlWords.length === 0 && pos.launchers.length === 0 && pos.assignments.length === 0 && program.name === basename;
|
|
391
526
|
let baseMove;
|
|
392
527
|
if (basename === "cd") {
|
|
393
528
|
baseMove = cdMove(argReads, reads);
|
|
394
529
|
}
|
|
395
|
-
else if (
|
|
530
|
+
else if (movesBaseUnfollowed(basename)) {
|
|
396
531
|
baseMove = { target: "unmodelled" };
|
|
397
|
-
unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", program);
|
|
532
|
+
unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", ctx, program);
|
|
398
533
|
}
|
|
399
534
|
else if (listed) {
|
|
400
|
-
listedVerbCandidates(basename, argReads, reads);
|
|
535
|
+
listedVerbCandidates(basename, argReads, reads, ctx);
|
|
401
536
|
}
|
|
402
|
-
else if (
|
|
537
|
+
else if (isDeclarationBuiltin(basename) && program.name === basename) {
|
|
538
|
+
const integer = declaredAttributes(row, args).has("integer");
|
|
539
|
+
const asWritten = row?.operands?.rest?.kind === "binds-name" && row.operands.rest.asWritten === true;
|
|
403
540
|
for (const r of argReads) {
|
|
404
541
|
const a = r.word.assignment;
|
|
405
542
|
if (a !== undefined && (r.expands !== "unresolvable" || a.value.expansions.length > 0))
|
|
@@ -408,20 +545,39 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
408
545
|
reads.push(bare(r, "unlisted_word"));
|
|
409
546
|
pushAssignment(reads, r.word, "unlisted_word");
|
|
410
547
|
}
|
|
548
|
+
assignmentArithmetic(r.word, integer || (a !== undefined && table.isInteger(a.name)), ctx, asWritten);
|
|
411
549
|
}
|
|
412
550
|
}
|
|
413
551
|
else {
|
|
414
|
-
unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", program);
|
|
552
|
+
unlistedWords(reads, toks, program.index, launcherIndices, "unlisted_word", ctx, program);
|
|
553
|
+
}
|
|
554
|
+
for (const e of row === undefined ? [] : readArgRoles(row, args)) {
|
|
555
|
+
const role = e.kind === "value" || e.kind === "operand" ? e.role : e.kind === "option" && typeof e.reading === "object" ? e.reading.role : undefined;
|
|
556
|
+
if (role?.kind !== "binds-name" || role.asWritten === true)
|
|
557
|
+
continue;
|
|
558
|
+
const text = e.kind === "option" && typeof e.reading === "object" ? e.reading.attached : args[e.at];
|
|
559
|
+
ctx.reentered.push(...arithmeticSubstitutions(subscriptOnly(text), table));
|
|
560
|
+
}
|
|
561
|
+
const language = programLanguageOf(row);
|
|
562
|
+
if (language !== undefined && (delivery === "stdin" || (delivery === "text" && !ctx.programTextSeen))) {
|
|
563
|
+
const opens = programTextOpensOf(language);
|
|
564
|
+
if (opens === "any" || typeof opens !== "string")
|
|
565
|
+
ctx.programText ??= delivery === "stdin" ? `reads the ${language} program it runs from its standard input, which this station does not read` : programTextSentence(language);
|
|
415
566
|
}
|
|
567
|
+
const effectSub = pathSubcommandOf(row, args);
|
|
568
|
+
const effectRow = effectSub?.row ?? row;
|
|
569
|
+
const effectArgs = effectSub === undefined ? args : args.slice(effectSub.at + 1);
|
|
570
|
+
if (effectRow !== undefined && selectedOptions(effectRow, effectArgs).some((o) => o.effect === "executes"))
|
|
571
|
+
reentry = "shell";
|
|
416
572
|
let unreadProgramText;
|
|
417
|
-
|
|
418
|
-
|
|
573
|
+
const slot = row === undefined ? undefined : programTextSlotFiles(row, args);
|
|
574
|
+
if (slot !== undefined) {
|
|
419
575
|
for (const f of slot.reads)
|
|
420
576
|
reads.push(bare(readOfText(f), "script_slot_file"));
|
|
421
577
|
for (const f of slot.writes)
|
|
422
578
|
reads.push(bare(readOfText(f), "write_redirect_target"));
|
|
423
579
|
for (const c of slot.commands)
|
|
424
|
-
nested.push(nestedPlan(c));
|
|
580
|
+
nested.push(nestedPlan(c, "later"));
|
|
425
581
|
if (slot.commands.length > 0 || slot.executesPatternSpace)
|
|
426
582
|
reentry = "shell";
|
|
427
583
|
unreadProgramText = slot.unread;
|
|
@@ -430,9 +586,8 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
430
586
|
if (bodies.size > 0)
|
|
431
587
|
reentry = "shell";
|
|
432
588
|
for (const body of bodies)
|
|
433
|
-
nested.push(nestedPlan(body));
|
|
434
|
-
return {
|
|
435
|
-
...base,
|
|
589
|
+
nested.push(nestedPlan(body, "later"));
|
|
590
|
+
return done({
|
|
436
591
|
empty: false,
|
|
437
592
|
launcherChain,
|
|
438
593
|
verb: verb,
|
|
@@ -443,8 +598,7 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
443
598
|
...(baseMove !== undefined ? { baseMove } : {}),
|
|
444
599
|
moveProvable,
|
|
445
600
|
...(loopHead !== undefined ? { loopHead } : {}),
|
|
446
|
-
|
|
447
|
-
};
|
|
601
|
+
});
|
|
448
602
|
}
|
|
449
603
|
function rebasePlan(plan, from, to) {
|
|
450
604
|
if (to !== "unmodelled" && to.length === 1 && to[0] === from)
|
|
@@ -490,165 +644,20 @@ function dedupe(reads) {
|
|
|
490
644
|
}
|
|
491
645
|
return out;
|
|
492
646
|
}
|
|
493
|
-
function bare(r, provenance, spelled = r.folded) {
|
|
494
|
-
return { spelled, raw: r.raw, provenance, expands: r.expands };
|
|
495
|
-
}
|
|
496
647
|
function relativeSpelling(spelled) {
|
|
497
648
|
return !(isAbsolutePathForm(spelled) || spelled.startsWith("~"));
|
|
498
649
|
}
|
|
499
650
|
function joinBase(base, target, homeDir) {
|
|
500
651
|
return resolveOperandLexically(base, target, homeDir);
|
|
501
652
|
}
|
|
502
|
-
function redirectFact(r,
|
|
653
|
+
function redirectFact(r, table, homeDir) {
|
|
503
654
|
if (r.target === undefined)
|
|
504
655
|
return { op: r.op, direction: "dup" };
|
|
505
656
|
if (r.op === "<")
|
|
506
|
-
return { op: r.op, direction: "in", target: bare(readsOf([r.target],
|
|
657
|
+
return { op: r.op, direction: "in", target: bare(readsOf([r.target], table, homeDir)[0], "stdin_redirect") };
|
|
507
658
|
if (r.op === "<<<")
|
|
508
659
|
return { op: r.op, direction: "in" };
|
|
509
|
-
return { op: r.op, direction: "out", target: bare(readsOf([r.target],
|
|
510
|
-
}
|
|
511
|
-
function pushAssignment(reads, w, provenance) {
|
|
512
|
-
const a = w.assignment;
|
|
513
|
-
if (a === undefined)
|
|
514
|
-
return;
|
|
515
|
-
const expandsTilde = a.value.expansions.some((p) => p.expansion?.kind === "tilde");
|
|
516
|
-
const value = expandsTilde ? a.value.folded : literalTildeSpelling(a.value.folded);
|
|
517
|
-
const settled = a.op === "=" && !expandsTilde;
|
|
518
|
-
const named = a.value.folded === "" || (a.op === "+=" ? expandsTilde : !settled) ? undefined : value;
|
|
519
|
-
if (a.value.folded === "" && settled)
|
|
520
|
-
return;
|
|
521
|
-
const v = { spelled: value, raw: a.bare ? a.value.raw : w.raw, provenance };
|
|
522
|
-
const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? { ...v, expands: expandsOf(a.value) } : undefined;
|
|
523
|
-
if (!settled && unsettled !== undefined)
|
|
524
|
-
reads.push(unsettled);
|
|
525
|
-
const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
|
|
526
|
-
if (named !== undefined && !substitutes)
|
|
527
|
-
reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
|
|
528
|
-
if (a.value.folded.includes(":"))
|
|
529
|
-
for (const m of a.value.folded.split(":"))
|
|
530
|
-
if (m !== "") {
|
|
531
|
-
const r = readOfText(m);
|
|
532
|
-
reads.push({ spelled: r.folded, raw: r.raw, provenance, expands: r.expands, denyOnly: true });
|
|
533
|
-
}
|
|
534
|
-
if (a.form === "compound")
|
|
535
|
-
for (const e of compoundArrayElements(a.value))
|
|
536
|
-
reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands, ...(e.denyOnly === true ? { denyOnly: true } : {}) });
|
|
537
|
-
}
|
|
538
|
-
function unlistedWords(reads, toks, from, launcherIndices, provenance, program) {
|
|
539
|
-
const argv = program !== undefined ? { program: program.basename, from: program.index } : argvProgramOf(toks.map((r) => r.word));
|
|
540
|
-
const argvStart = argv?.from ?? toks.length;
|
|
541
|
-
const slotVerb = argv?.program;
|
|
542
|
-
const message = slotVerb === undefined || argv === undefined ? new Set() : messageSlotWords(slotVerb, toks, argvStart);
|
|
543
|
-
const declaredNotPaths = ASSIGNMENT_VALUE_NOT_PATHS.has(slotVerb ?? "");
|
|
544
|
-
const noPathOperands = slotVerb !== undefined && NO_PATH_OPERAND_COMMANDS.has(slotVerb);
|
|
545
|
-
const permutes = slotVerb === undefined || !verbOptionsArePositionIndependent(slotVerb);
|
|
546
|
-
const expanding = new Set();
|
|
547
|
-
for (let j = from; j < toks.length; j++) {
|
|
548
|
-
if (launcherIndices.has(j))
|
|
549
|
-
continue;
|
|
550
|
-
if (noPathOperands && j > argvStart && !NO_PATH_OPERAND_EVALUATORS.has(slotVerb))
|
|
551
|
-
break;
|
|
552
|
-
if (toks[j].expands !== "unresolvable")
|
|
553
|
-
continue;
|
|
554
|
-
expanding.add(j);
|
|
555
|
-
reads.push(bare(toks[j], j === argvStart ? "program_path" : provenance));
|
|
556
|
-
if (j < argvStart)
|
|
557
|
-
pushAssignment(reads, toks[j].word, "env_assignment");
|
|
558
|
-
else if (j > argvStart && !declaredNotPaths)
|
|
559
|
-
pushAssignment(reads, toks[j].word, provenance);
|
|
560
|
-
}
|
|
561
|
-
const positional = [];
|
|
562
|
-
let stdinOperand = false;
|
|
563
|
-
let endOfOptions = false;
|
|
564
|
-
let sawArgvOperand = false;
|
|
565
|
-
for (let j = from; j < toks.length; j++) {
|
|
566
|
-
if (launcherIndices.has(j) || expanding.has(j) || message.has(j))
|
|
567
|
-
continue;
|
|
568
|
-
const r = toks[j];
|
|
569
|
-
const word = r.folded;
|
|
570
|
-
if (j > from && !endOfOptions && word === "--") {
|
|
571
|
-
endOfOptions = true;
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
if (noPathOperands && j >= argvStart)
|
|
575
|
-
break;
|
|
576
|
-
if (j === argvStart && !isPathShapedToken(word))
|
|
577
|
-
continue;
|
|
578
|
-
const optionShaped = !endOfOptions && word.startsWith("-") && word !== "-";
|
|
579
|
-
if (optionShaped && j > argvStart) {
|
|
580
|
-
const slot = slotVerb === undefined ? undefined : readVerbOptionSlot(slotVerb, word);
|
|
581
|
-
if (sawArgvOperand && permutes) {
|
|
582
|
-
reads.push(bare(r, provenance));
|
|
583
|
-
if (slot !== undefined && slot.form === "separated") {
|
|
584
|
-
const ahead = toks[j + 1];
|
|
585
|
-
if (ahead !== undefined && ahead.expands !== "unresolvable")
|
|
586
|
-
for (const c of slotValueCandidates(ahead.folded, slot.kind))
|
|
587
|
-
reads.push(bare(ahead, "option_value", c));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
if (slot !== undefined && slot.form !== "unexplained" && !(slot.form === "separated" && sawArgvOperand && permutes)) {
|
|
591
|
-
if (slot.form === "attached") {
|
|
592
|
-
for (const c of slotValueCandidates(literalTildeSpelling(slot.value), slot.kind))
|
|
593
|
-
reads.push(bare(r, "option_value", c));
|
|
594
|
-
}
|
|
595
|
-
else if (slot.form === "separated") {
|
|
596
|
-
j++;
|
|
597
|
-
const value = toks[j];
|
|
598
|
-
if (value !== undefined && !expanding.has(j)) {
|
|
599
|
-
const named = slotValueCandidates(value.folded, slot.kind);
|
|
600
|
-
for (const c of named)
|
|
601
|
-
reads.push(bare(value, "option_value", c));
|
|
602
|
-
if (named.length > 0)
|
|
603
|
-
positional.push(value);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
for (const payload of attachedOptionPayloads(word))
|
|
609
|
-
reads.push(bare(r, provenance, payload));
|
|
610
|
-
if (isPathShapedToken(word))
|
|
611
|
-
reads.push(bare(r, provenance));
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
if (optionShaped) {
|
|
615
|
-
for (const payload of attachedOptionPayloads(word))
|
|
616
|
-
reads.push(bare(r, provenance, payload));
|
|
617
|
-
if (isPathShapedToken(word))
|
|
618
|
-
reads.push(bare(r, provenance));
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
if (j > argvStart) {
|
|
622
|
-
sawArgvOperand = true;
|
|
623
|
-
if (word === "-") {
|
|
624
|
-
const m = slotVerb === undefined ? undefined : RECURSIVE_READ_FORMS[slotVerb];
|
|
625
|
-
if (m === undefined || m.dashIsStdin === true)
|
|
626
|
-
stdinOperand = true;
|
|
627
|
-
else
|
|
628
|
-
positional.push(r);
|
|
629
|
-
}
|
|
630
|
-
else
|
|
631
|
-
positional.push(r);
|
|
632
|
-
}
|
|
633
|
-
reads.push(bare(r, j === argvStart ? "program_path" : provenance));
|
|
634
|
-
if (j < argvStart)
|
|
635
|
-
pushAssignment(reads, r.word, "env_assignment");
|
|
636
|
-
else if (!declaredNotPaths)
|
|
637
|
-
pushAssignment(reads, r.word, provenance);
|
|
638
|
-
}
|
|
639
|
-
if (slotVerb !== undefined && argv !== undefined && segmentSelectsRecursiveRead(slotVerb, toks.slice(argvStart + 1).map((x) => x.folded))) {
|
|
640
|
-
const roots = positional.length > 0 ? positional : stdinOperand ? [] : [readOfText(".")];
|
|
641
|
-
for (const root of roots)
|
|
642
|
-
reads.push(bare(root, "traversal_root"));
|
|
643
|
-
}
|
|
644
|
-
for (let j = from; j < toks.length; j++) {
|
|
645
|
-
if (launcherIndices.has(j) || j === argvStart)
|
|
646
|
-
continue;
|
|
647
|
-
if (noPathOperands && j > argvStart)
|
|
648
|
-
break;
|
|
649
|
-
if (toks[j].expands === "glob")
|
|
650
|
-
reads.push(bare(toks[j], provenance));
|
|
651
|
-
}
|
|
660
|
+
return { op: r.op, direction: "out", target: bare(readsOf([r.target], table, homeDir)[0], "write_redirect_target") };
|
|
652
661
|
}
|
|
653
662
|
function commandStringValue(options, toks, option) {
|
|
654
663
|
const at = toks.findIndex((r) => r.folded === option);
|
|
@@ -696,106 +705,3 @@ function cdMove(args, reads) {
|
|
|
696
705
|
reads.push(c);
|
|
697
706
|
return { target: c };
|
|
698
707
|
}
|
|
699
|
-
function listedVerbCandidates(name, argReads, reads) {
|
|
700
|
-
if (NO_PATH_OPERAND_COMMANDS.has(name))
|
|
701
|
-
return;
|
|
702
|
-
const args = argReads.map((r) => r.folded);
|
|
703
|
-
const expanding = new Set();
|
|
704
|
-
argReads.forEach((r, k) => {
|
|
705
|
-
if (!rewritten(r))
|
|
706
|
-
return;
|
|
707
|
-
expanding.add(k);
|
|
708
|
-
reads.push({ spelled: r.folded, raw: r.raw, provenance: "operand", expands: "unresolvable" });
|
|
709
|
-
});
|
|
710
|
-
const eoo = args.indexOf("--");
|
|
711
|
-
const firstOperandSlot = verbFirstOperandSlot(name);
|
|
712
|
-
const firstOperandSuppliedByFlag = firstOperandSlot !== undefined && (eoo === -1 ? args : args.slice(0, eoo)).some((t) => suppliesFirstOperandSlot(firstOperandSlot, t));
|
|
713
|
-
const positional = [];
|
|
714
|
-
let sawOperand = false;
|
|
715
|
-
let stdinOperand = false;
|
|
716
|
-
let endOfOptions = false;
|
|
717
|
-
const permutes = !verbOptionsArePositionIndependent(name);
|
|
718
|
-
const push = (k, text, provenance) => {
|
|
719
|
-
reads.push(bare(argReads[k], provenance, text));
|
|
720
|
-
};
|
|
721
|
-
for (let k = 0; k < args.length; k++) {
|
|
722
|
-
const t = args[k];
|
|
723
|
-
if (expanding.has(k))
|
|
724
|
-
continue;
|
|
725
|
-
if (!endOfOptions) {
|
|
726
|
-
if (t === "--") {
|
|
727
|
-
endOfOptions = true;
|
|
728
|
-
continue;
|
|
729
|
-
}
|
|
730
|
-
if (t.startsWith("-") && t !== "-") {
|
|
731
|
-
const slot = readVerbOptionSlot(name, t);
|
|
732
|
-
if (sawOperand && permutes) {
|
|
733
|
-
push(k, t, "operand");
|
|
734
|
-
if (slot !== undefined && slot.form === "separated") {
|
|
735
|
-
const ahead = args[k + 1];
|
|
736
|
-
if (ahead !== undefined && !expanding.has(k + 1))
|
|
737
|
-
for (const c of slotValueCandidates(ahead, slot.kind))
|
|
738
|
-
push(k + 1, c, "option_value");
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (slot !== undefined && slot.form !== "unexplained" && !(slot.form === "separated" && sawOperand && permutes)) {
|
|
742
|
-
if (slot.form === "attached") {
|
|
743
|
-
for (const c of slotValueCandidates(literalTildeSpelling(slot.value), slot.kind))
|
|
744
|
-
push(k, c, "option_value");
|
|
745
|
-
}
|
|
746
|
-
else if (slot.form === "separated") {
|
|
747
|
-
const value = args[++k];
|
|
748
|
-
if (value !== undefined && !expanding.has(k)) {
|
|
749
|
-
const named = slotValueCandidates(value, slot.kind);
|
|
750
|
-
for (const c of named)
|
|
751
|
-
push(k, c, "option_value");
|
|
752
|
-
if (named.length > 0)
|
|
753
|
-
positional.push(argReads[k]);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
for (const payload of attachedOptionPayloads(t))
|
|
759
|
-
push(k, payload, "option_value");
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
const inFirstOperandSlot = firstOperandSlot !== undefined && !firstOperandSuppliedByFlag && !sawOperand;
|
|
764
|
-
sawOperand = true;
|
|
765
|
-
if (t === "-") {
|
|
766
|
-
if (inFirstOperandSlot)
|
|
767
|
-
continue;
|
|
768
|
-
const m = RECURSIVE_READ_FORMS[name];
|
|
769
|
-
if (m === undefined || m.dashIsStdin === true) {
|
|
770
|
-
stdinOperand = true;
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
773
|
-
positional.push(argReads[k]);
|
|
774
|
-
continue;
|
|
775
|
-
}
|
|
776
|
-
if (inFirstOperandSlot) {
|
|
777
|
-
const named = slotValueCandidates(t, firstOperandSlot.kind);
|
|
778
|
-
for (const c of named)
|
|
779
|
-
push(k, c, "operand");
|
|
780
|
-
if (named.length > 0)
|
|
781
|
-
positional.push(argReads[k]);
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
positional.push(argReads[k]);
|
|
785
|
-
push(k, t, "operand");
|
|
786
|
-
if (!ASSIGNMENT_VALUE_NOT_PATHS.has(name)) {
|
|
787
|
-
pushAssignment(reads, argReads[k].word, "operand");
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
for (let k = 0; k < args.length; k++) {
|
|
791
|
-
if (expanding.has(k))
|
|
792
|
-
continue;
|
|
793
|
-
if (argReads[k].expands === "glob")
|
|
794
|
-
reads.push(bare(argReads[k], "operand"));
|
|
795
|
-
}
|
|
796
|
-
if (segmentSelectsRecursiveRead(name, args)) {
|
|
797
|
-
const roots = positional.length > 0 ? positional : stdinOperand ? [] : [readOfText(".")];
|
|
798
|
-
for (const root of roots)
|
|
799
|
-
reads.push(bare(root, "traversal_root"));
|
|
800
|
-
}
|
|
801
|
-
}
|