@sema-agent/core 7.47.0 → 7.48.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 +54 -0
- package/dist/core/allow-entry.js +1 -1
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-table.d.ts +2 -2
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-policy.js +4 -5
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.js +30 -4
- package/dist/tools/fs/bash-program-position.d.ts +7 -0
- package/dist/tools/fs/bash-program-position.js +5 -2
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +60 -5
- package/dist/tools/fs/program-read-model.js +100 -36
- package/dist/tools/fs/program-read-model.rows.js +13 -5
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +12 -3
- package/dist/tools/fs/shell-read-argv.d.ts +4 -7
- package/dist/tools/fs/shell-read-argv.js +5 -7
- package/dist/tools/fs/shell-read-bindings.d.ts +34 -9
- package/dist/tools/fs/shell-read-bindings.js +166 -60
- package/dist/tools/fs/shell-read-establish.js +3 -4
- package/dist/tools/fs/shell-read-plan.d.ts +1 -14
- package/dist/tools/fs/shell-read-plan.js +102 -101
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +35 -6
- package/dist/tools/fs/shell-read-word.js +85 -9
- package/dist/tools/fs/shell-reentry-text.d.ts +23 -21
- package/dist/tools/fs/shell-reentry-text.js +31 -26
- package/package.json +1 -1
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -10,11 +10,9 @@ import { type Read } from "./shell-read-word.js";
|
|
|
10
10
|
import { type BindingTable, type FloorValue } from "./shell-read-bindings.js";
|
|
11
11
|
import { subcommandOf, type ArgRole, type ProgramRow } from "./program-read-model.js";
|
|
12
12
|
import type { ReadCandidate, ReadCandidateProvenance } from "./shell-read-plan.js";
|
|
13
|
-
/** A candidate before the threading fills its bases. `
|
|
14
|
-
*
|
|
15
|
-
* are read. */
|
|
13
|
+
/** A candidate before the threading fills its bases. `floors`: the spellings its word's names left for the deny judge
|
|
14
|
+
* alone (a word whose names are unknown here). */
|
|
16
15
|
export type Bare = Omit<ReadCandidate, "bases"> & {
|
|
17
|
-
readonly alternatives?: readonly Pick<ReadCandidate, "spelled" | "pattern">[];
|
|
18
16
|
readonly floors?: readonly FloorValue[];
|
|
19
17
|
};
|
|
20
18
|
/** What reading a segment's words found beside its candidates. */
|
|
@@ -36,9 +34,8 @@ export declare function pathSubcommandOf(row: ProgramRow | undefined, args: read
|
|
|
36
34
|
/** The sentence of inline program text the station does not read. */
|
|
37
35
|
export declare const programTextSentence: (language: string) => string;
|
|
38
36
|
/** One candidate without its bases (filled by the threading pass): the word's spelling — or `spelled`, a caller's
|
|
39
|
-
* reading of PART of it (an option payload, a `key=value` value) — with the word's own expansion facts.
|
|
40
|
-
*
|
|
41
|
-
* (it is unresolvable). */
|
|
37
|
+
* reading of PART of it (an option payload, a `key=value` value) — with the word's own expansion facts. The word is read
|
|
38
|
+
* in one reading of the segment's names (`segmentReadingsOf`): its names hold one value each, or are unknown. */
|
|
42
39
|
export declare function bare(r: Read, provenance: ReadCandidateProvenance, spelled?: string): Bare;
|
|
43
40
|
/** The reads one value of a declared role hands over: a candidate per file it names, or — a value the program expands
|
|
44
41
|
* past the listed bound — the word itself, unresolvable. A mount source is a read the daemon makes of the whole tree
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { literalTildeSpelling } from "../../shell/lex.js";
|
|
2
|
-
import { expandsOf,
|
|
2
|
+
import { expandsOf, partPattern, readOfText, rewritten } from "./shell-read-word.js";
|
|
3
3
|
import { arithmeticSubstitutions } from "./shell-read-bindings.js";
|
|
4
4
|
import { argvProgramOf, attachedOptionPayloads, isPathShapedToken } from "./bash-readonly-classifier.js";
|
|
5
5
|
import { assignmentArgumentsAreNotPaths, evaluatesOperands, messageWords, moduleTailOf, objectPathsOf, opensNoOperand, programRow, programTextMayOpen, programTextOpensOf, readArgs, selectsContentsTraversal, slotValueCandidates, subcommandOf, valuesAreLiteral, walksCwdWithoutOperand, wordsReadBySomeImplementations, } from "./program-read-model.js";
|
|
@@ -10,16 +10,13 @@ export function pathSubcommandOf(row, args) {
|
|
|
10
10
|
export const programTextSentence = (language) => `runs inline ${language} program text, which may open files this station does not read`;
|
|
11
11
|
export function bare(r, provenance, spelled = r.folded) {
|
|
12
12
|
const whole = spelled === r.folded;
|
|
13
|
-
const
|
|
14
|
-
if (!whole && alternatives !== undefined)
|
|
15
|
-
return { spelled, raw: r.raw, provenance, expands: "unresolvable" };
|
|
13
|
+
const pattern = whole ? r.pattern : partPattern(r, spelled);
|
|
16
14
|
return {
|
|
17
15
|
spelled,
|
|
18
16
|
raw: r.raw,
|
|
19
17
|
provenance,
|
|
20
18
|
expands: r.expands,
|
|
21
|
-
...(
|
|
22
|
-
...(whole && alternatives !== undefined ? { alternatives } : {}),
|
|
19
|
+
...(pattern !== undefined ? { pattern } : {}),
|
|
23
20
|
...(whole && r.homeFloor !== undefined ? { floorSpelled: r.homeFloor } : {}),
|
|
24
21
|
...(whole && r.floorValues !== undefined ? { floors: r.floorValues } : {}),
|
|
25
22
|
};
|
|
@@ -64,8 +61,9 @@ export function pushAssignment(reads, w, provenance) {
|
|
|
64
61
|
if (!settled && unsettled !== undefined)
|
|
65
62
|
reads.push(unsettled);
|
|
66
63
|
const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
|
|
64
|
+
const pattern = named === undefined ? undefined : partPattern({ folded: w.folded, ...(w.judged !== undefined ? { pattern: w.judged } : {}) }, named);
|
|
67
65
|
if (named !== undefined && !substitutes)
|
|
68
|
-
reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
|
|
66
|
+
reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w), ...(pattern !== undefined ? { pattern } : {}) });
|
|
69
67
|
if (a.value.folded.includes(":"))
|
|
70
68
|
for (const m of a.value.folded.split(":"))
|
|
71
69
|
if (m !== "") {
|
|
@@ -36,10 +36,12 @@ export type BoundValue = {
|
|
|
36
36
|
readonly spelled: string;
|
|
37
37
|
readonly glob?: never;
|
|
38
38
|
readonly parts?: never;
|
|
39
|
+
readonly fields?: readonly string[];
|
|
39
40
|
} | {
|
|
40
41
|
readonly spelled: string;
|
|
41
42
|
readonly glob: true;
|
|
42
43
|
readonly parts: readonly ValuePart[];
|
|
44
|
+
readonly fields?: never;
|
|
43
45
|
};
|
|
44
46
|
/** One run of a pattern {@link BoundValue}: literal text, or a pattern — the shell expands its characters — `text` as
|
|
45
47
|
* written (what a shell re-reading it matches by), `judged` as a reader judges the names it matches. */
|
|
@@ -56,7 +58,14 @@ export type ValuePart = {
|
|
|
56
58
|
export interface FloorValue {
|
|
57
59
|
readonly spelled: string;
|
|
58
60
|
readonly glob?: true;
|
|
61
|
+
/** A pattern's spelling as a reader judges the names it matches, where it is not `spelled` ({@link judgedSpellingOf}). */
|
|
62
|
+
readonly pattern?: string;
|
|
59
63
|
}
|
|
64
|
+
/** A pattern value's names as a reader judges them, where that is not its spelling: its runs, a class the judge widens
|
|
65
|
+
* as `?` (the lexer's `WordExpansion.pattern.judged`), a leading `~` the literal tilde it is. */
|
|
66
|
+
export declare function judgedSpellingOf(v: BoundValue): string | undefined;
|
|
67
|
+
/** A value as a deny-floor spelling, its judged spelling with it. */
|
|
68
|
+
export declare function floorOf(v: BoundValue): FloorValue;
|
|
60
69
|
/** What a name holds at one point of a text: its possible values, or `"unknown"`. */
|
|
61
70
|
type NameState = readonly BoundValue[] | "unknown";
|
|
62
71
|
/**
|
|
@@ -133,25 +142,36 @@ export declare function dropDefinitionHeads(tokens: LexedToken[]): readonly numb
|
|
|
133
142
|
* where it refers to no name (it reads as written), else {@link valueOf}. `table.stateOf` is read after `overlay` (the
|
|
134
143
|
* names the same segment bound earlier).
|
|
135
144
|
*/
|
|
136
|
-
export declare function wordValues(word: Pick<LexedWord, "pieces">, table: BindingTable | undefined, quoted: boolean, overlay?: ReadonlyMap<string, NameState
|
|
145
|
+
export declare function wordValues(word: Pick<LexedWord, "pieces">, table: BindingTable | undefined, quoted: boolean, overlay?: ReadonlyMap<string, NameState>, split?: boolean): readonly BoundValue[] | "unknown" | undefined;
|
|
146
|
+
/** The names a word's expansions read as values — a bare `$NAME` / `${NAME}`, and one written inside a bracket the shell
|
|
147
|
+
* forms its class from after expanding it ({@link valueOf}) — in the order they stand. */
|
|
148
|
+
export declare function namesIn(word: Pick<LexedWord, "pieces">): string[];
|
|
137
149
|
/**
|
|
138
150
|
* What a VALUE word holds (an assignment's value, a `for` list word, a word handed to a program) — THE piece walk, the one
|
|
139
|
-
* producer of {@link BoundValue}s: every combination of the values its pieces take
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
151
|
+
* producer of {@link BoundValue}s: every combination of the values its pieces take, each field's pattern formed once the
|
|
152
|
+
* whole field has expanded ({@link formedField}) — a literal piece its text, a glob piece its pattern as written (a bracket
|
|
153
|
+
* holding an expansion its brackets and its body, the body's names substituted in place), `~` and `$NAME` the values the
|
|
154
|
+
* table gives:
|
|
155
|
+
* · its values — a field the shell globs makes the value a pattern;
|
|
143
156
|
* · `"unknown"` — a name it refers to is not settled here, it carries an expansion no name answers (a modifier, a
|
|
144
|
-
* substitution, a brace list, `~user
|
|
145
|
-
*
|
|
146
|
-
* split), or it would list more than
|
|
157
|
+
* substitution, a brace list, `~user`), an UNQUOTED expansion's value is no {@link READABLE_VALUE} (or holds
|
|
158
|
+
* whitespace outside `split`), `IFS` may have changed (`quoted` = the whole word is read as one value: an assignment's
|
|
159
|
+
* value, which is never split), a field is not formed or a split one forms a pattern, or it would list more than
|
|
160
|
+
* {@link WORD_VALUES_MAX} values.
|
|
161
|
+
* `split` = the word is read as the ARGUMENTS it becomes: an unquoted value is split into its words on the default `IFS`
|
|
162
|
+
* (a value then carries its {@link BoundValue} `fields`).
|
|
147
163
|
*/
|
|
148
|
-
export declare function valueOf(word: Pick<LexedWord, "pieces">, table: BindingTable | undefined, quoted: boolean, overlay?: ReadonlyMap<string, NameState
|
|
164
|
+
export declare function valueOf(word: Pick<LexedWord, "pieces">, table: BindingTable | undefined, quoted: boolean, overlay?: ReadonlyMap<string, NameState>, split?: boolean): NameState;
|
|
149
165
|
/**
|
|
150
166
|
* The spellings a word the table leaves unknown may still take through the deny floors of the names it refers to
|
|
151
167
|
* ({@link BindingTable.floorOf}) — `${A[k]}`, `${A[@]}`, `$A` reading an array's elements — or through their values where
|
|
152
168
|
* known: for the deny judge alone. Empty where some name it refers to leaves neither.
|
|
153
169
|
*/
|
|
154
170
|
export declare function wordFloorValues(word: Pick<LexedWord, "pieces">, table: BindingTable | undefined): readonly FloorValue[];
|
|
171
|
+
/** Where a segment's argv heads a `for` / `select` loop: the index of that bare word, at the segment's start or past the
|
|
172
|
+
* bare control words in front of it (`do for x in …`, `then for …` — a loop nested in a body or a branch); `undefined`
|
|
173
|
+
* for any other segment. */
|
|
174
|
+
export declare function loopListHeadAt(argv: readonly LexedWord[]): number | undefined;
|
|
155
175
|
/**
|
|
156
176
|
* What ONE segment does to the names (see the module header). `argv` = the segment's argv projection (structure
|
|
157
177
|
* dropped); `readValue` folds a value word against the table (a reader that answers `unknown` gives the names alone).
|
|
@@ -180,6 +200,9 @@ export type NameReader = (name: string) => NameState;
|
|
|
180
200
|
export interface InheritedNames {
|
|
181
201
|
readonly attributes: readonly NameAttribute[];
|
|
182
202
|
readonly references: boolean;
|
|
203
|
+
/** The deny floors a name starts the text with ({@link BindingTable.floorOf}): what the words handing it this text as
|
|
204
|
+
* written may name there — or, for a substitution body, the floors its segment reads. */
|
|
205
|
+
readonly floors?: ReadonlyMap<string, readonly FloorValue[]>;
|
|
183
206
|
}
|
|
184
207
|
/** What the thread answers for one reader: what a name may hold, and whether a name carries the integer attribute. */
|
|
185
208
|
export interface BindingTable {
|
|
@@ -235,6 +258,8 @@ export declare class BindingThread {
|
|
|
235
258
|
private readonly blocks;
|
|
236
259
|
/** Segments that are a pipeline member or run in the background. */
|
|
237
260
|
private readonly detached;
|
|
261
|
+
/** The floors the names start the text with ({@link InheritedNames.floors}). */
|
|
262
|
+
private readonly startFloors;
|
|
238
263
|
constructor(segments: readonly LexedSegment[],
|
|
239
264
|
/** What each segment binds, its names alone (a value is the reader's to read). */
|
|
240
265
|
named: readonly BindingEffect[],
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { argvOf, lexShell, literalTildeSpelling, substitutionsInHeredocBody } from "../../shell/lex.js";
|
|
2
|
-
import { readProgramPosition } from "./bash-program-position.js";
|
|
2
|
+
import { isShellSyntaxWord, LOOP_LIST_HEADS, readProgramPosition } from "./bash-program-position.js";
|
|
3
3
|
import { declaredAttributes, programRow, readArgRoles } from "./program-read-model.js";
|
|
4
4
|
import { codeBodies } from "./shell-reentry-text.js";
|
|
5
|
+
export function judgedSpellingOf(v) {
|
|
6
|
+
if (v.glob !== true)
|
|
7
|
+
return undefined;
|
|
8
|
+
const judged = literalTildeSpelling(v.parts.map((p) => (p.pattern ? p.judged : p.text)).join(""));
|
|
9
|
+
return judged === literalTildeSpelling(v.spelled) ? undefined : judged;
|
|
10
|
+
}
|
|
11
|
+
export function floorOf(v) {
|
|
12
|
+
const pattern = judgedSpellingOf(v);
|
|
13
|
+
return v.glob === true ? { spelled: v.spelled, glob: true, ...(pattern !== undefined ? { pattern } : {}) } : { spelled: v.spelled };
|
|
14
|
+
}
|
|
5
15
|
function sameValue(a, b) {
|
|
6
|
-
if (a.spelled !== b.spelled)
|
|
16
|
+
if (a.spelled !== b.spelled || a.fields?.length !== b.fields?.length)
|
|
7
17
|
return false;
|
|
8
18
|
if (a.glob !== true || b.glob !== true)
|
|
9
19
|
return a.glob === b.glob;
|
|
@@ -203,14 +213,51 @@ function blockStructure(segments) {
|
|
|
203
213
|
return { words: words ?? depth, after: depth, lowest, switches, ...(body !== undefined ? { body } : {}) };
|
|
204
214
|
});
|
|
205
215
|
}
|
|
206
|
-
const
|
|
216
|
+
const READABLE_VALUE = /^[^\\~()]*$/;
|
|
207
217
|
const WORD_VALUES_MAX = 64;
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
const quotedRunsOf = (v) => v.glob === true ? v.parts.map((r) => (r.pattern ? { text: r.text, mode: "set", judged: r.judged } : { text: r.text, mode: "inactive" })) : [{ text: v.spelled, mode: "inactive" }];
|
|
219
|
+
function formedField(f) {
|
|
220
|
+
const runs = f.runs;
|
|
221
|
+
if (!runs.some((r) => r.mode === "active" && /[[\]*?]/.test(r.text))) {
|
|
222
|
+
if (!runs.some((r) => r.mode === "raw" || r.mode === "set"))
|
|
223
|
+
return { spelled: f.text };
|
|
224
|
+
return { spelled: f.text, glob: true, parts: joinRuns([], runs.map((r) => (r.mode === "raw" || r.mode === "set" ? { text: r.text, pattern: true, judged: r.judged } : { text: r.text, pattern: false }))) };
|
|
225
|
+
}
|
|
226
|
+
if (runs.some((r) => r.mode === "active" && /[ \t\n\\]/.test(r.text)) || (runs.some((r) => r.mode === "set") && runs.some((r) => r.mode === "active" && /[[\]]/.test(r.text))))
|
|
210
227
|
return undefined;
|
|
211
|
-
|
|
228
|
+
const escaped = runs.map((r) => (r.mode === "raw" || r.mode === "set" ? r.text : r.text.replace(r.mode === "active" ? /[^A-Za-z0-9_./*?[\]!^:=,+@%-]/g : /[^A-Za-z0-9_./]/g, (c) => `\\${c}`))).join("");
|
|
229
|
+
const lexed = lexShell(`: ${escaped}`);
|
|
230
|
+
const words = "parseFailure" in lexed || lexed.segments.length !== 1 ? undefined : argvOf(lexed.segments[0]);
|
|
231
|
+
if (words?.length !== 2)
|
|
232
|
+
return undefined;
|
|
233
|
+
let parts = [];
|
|
234
|
+
for (const p of words[1].pieces) {
|
|
235
|
+
if (p.expansion?.pattern !== undefined)
|
|
236
|
+
parts = joinRuns(parts, [{ text: p.expansion.pattern.written, pattern: true, judged: p.expansion.pattern.judged }]);
|
|
237
|
+
else if (p.expansion === undefined && p.literal !== undefined)
|
|
238
|
+
parts = joinRuns(parts, [{ text: p.literal, pattern: false }]);
|
|
239
|
+
else
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
return parts.some((r) => r.pattern) ? { spelled: f.text, glob: true, parts } : { spelled: f.text };
|
|
243
|
+
}
|
|
244
|
+
export function wordValues(word, table, quoted, overlay = new Map(), split = false) {
|
|
245
|
+
if (!word.pieces.some((p) => p.expansion?.kind === "parameter" || p.expansion?.kind === "tilde") && namesIn(word).length === 0)
|
|
246
|
+
return undefined;
|
|
247
|
+
return valueOf(word, table, quoted, overlay, split);
|
|
248
|
+
}
|
|
249
|
+
export function namesIn(word) {
|
|
250
|
+
return word.pieces.flatMap((p) => {
|
|
251
|
+
const e = p.expansion;
|
|
252
|
+
if (e?.kind === "parameter" && e.name !== undefined)
|
|
253
|
+
return e.body === undefined || e.body === e.name ? [e.name] : [];
|
|
254
|
+
return e?.kind === "glob" && e.pattern === undefined ? (bracketBodyOf(e.body) ?? []).flatMap((w) => namesIn(w)) : [];
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function bracketBodyOf(body) {
|
|
258
|
+
const lexed = body === undefined ? undefined : lexShell(`""${body}`);
|
|
259
|
+
return lexed === undefined || "parseFailure" in lexed ? undefined : lexed.segments.flatMap((s) => argvOf(s));
|
|
212
260
|
}
|
|
213
|
-
const runsOf = (v) => (v.glob === true ? v.parts : [{ text: v.spelled, pattern: false }]);
|
|
214
261
|
function joinRuns(runs, more) {
|
|
215
262
|
const out = [...runs];
|
|
216
263
|
for (const r of more) {
|
|
@@ -224,51 +271,96 @@ function joinRuns(runs, more) {
|
|
|
224
271
|
}
|
|
225
272
|
return out;
|
|
226
273
|
}
|
|
227
|
-
export function valueOf(word, table, quoted, overlay = new Map()) {
|
|
274
|
+
export function valueOf(word, table, quoted, overlay = new Map(), split = false) {
|
|
228
275
|
const stateOf = (name) => (table === undefined ? "unknown" : overlay.get(name) ?? table.stateOf(name));
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
const EMPTY = { text: "", runs: [] };
|
|
277
|
+
let combos = [{ field: EMPTY, closed: [], started: false, split: false }];
|
|
278
|
+
const activeRun = (text) => ({ text, mode: "active" });
|
|
279
|
+
const splitInto = (c, value) => {
|
|
280
|
+
const words = value.split(/[ \t\n]+/).filter((w) => w !== "");
|
|
281
|
+
if (words.length === 0)
|
|
282
|
+
return value === "" ? { ...c, split: true } : { ...c, field: EMPTY, started: false, closed: c.started ? [...c.closed, c.field] : c.closed, split: true };
|
|
283
|
+
const closed = [...c.closed];
|
|
284
|
+
if (/^[ \t\n]/.test(value) && c.started)
|
|
285
|
+
closed.push(c.field);
|
|
286
|
+
let field = closed.length > c.closed.length ? { text: words[0], runs: [activeRun(words[0])] } : { text: c.field.text + words[0], runs: [...c.field.runs, activeRun(words[0])] };
|
|
287
|
+
for (let i = 1; i < words.length; i++) {
|
|
288
|
+
closed.push(field);
|
|
289
|
+
field = { text: words[i], runs: [activeRun(words[i])] };
|
|
242
290
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
else if (e.kind === "parameter" && e.name !== undefined && (e.body === undefined || e.body === e.name)) {
|
|
252
|
-
const state = stateOf(e.name);
|
|
253
|
-
if (state === "unknown")
|
|
254
|
-
return "unknown";
|
|
255
|
-
if (p.quotedBy === undefined && !quoted && (table?.ifsTouched === true || state.some((v) => v.glob === true || !ONE_WORD.test(v.spelled))))
|
|
256
|
-
return "unknown";
|
|
257
|
-
options = state;
|
|
258
|
-
}
|
|
259
|
-
else
|
|
260
|
-
return "unknown";
|
|
261
|
-
const next = [];
|
|
262
|
-
for (const c of combos)
|
|
263
|
-
for (const o of options)
|
|
264
|
-
next.push({ text: c.text + o.spelled, glob: c.glob || o.glob === true, parts: joinRuns(c.parts, runsOf(o)) });
|
|
291
|
+
if (!/[ \t\n]$/.test(value))
|
|
292
|
+
return { ...c, field, started: true, closed, split: true };
|
|
293
|
+
closed.push(field);
|
|
294
|
+
return { ...c, field: EMPTY, started: false, closed, split: true };
|
|
295
|
+
};
|
|
296
|
+
const extend = (options) => {
|
|
297
|
+
const next = combos.flatMap((c) => options.map((o) => ({ ...c, field: { text: c.field.text + o.text, runs: [...c.field.runs, ...o.runs] }, started: true })));
|
|
265
298
|
if (next.length > WORD_VALUES_MAX)
|
|
266
|
-
return
|
|
299
|
+
return false;
|
|
267
300
|
combos = next;
|
|
268
|
-
|
|
301
|
+
return true;
|
|
302
|
+
};
|
|
303
|
+
const unquotedKind = (v) => v.glob === true || !READABLE_VALUE.test(v.spelled) ? undefined : !/[ \t\n]/.test(v.spelled) && v.spelled !== "" ? "run" : split ? "split" : undefined;
|
|
304
|
+
const walk = (pieces, unquoted) => {
|
|
305
|
+
for (const p of pieces) {
|
|
306
|
+
const e = p.expansion;
|
|
307
|
+
const bare = unquoted && p.quotedBy === undefined;
|
|
308
|
+
if (e === undefined) {
|
|
309
|
+
if (p.literal === undefined || !extend([{ text: p.literal, runs: [{ text: p.literal, mode: bare ? "active" : "inactive" }] }]))
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
else if (e.kind === "glob") {
|
|
313
|
+
const formed = e.pattern;
|
|
314
|
+
if (formed !== undefined) {
|
|
315
|
+
if (!extend([{ text: formed.folded, runs: [{ text: formed.written, mode: "raw", judged: formed.judged }] }]))
|
|
316
|
+
return false;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const words = bracketBodyOf(e.body);
|
|
320
|
+
if (words?.length !== 1 || !extend([{ text: "[", runs: [activeRun("[")] }]) || !walk(words[0].pieces, true) || !extend([{ text: "]", runs: [activeRun("]")] }]))
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
else if (e.kind === "tilde") {
|
|
324
|
+
const home = e.body === "~" ? stateOf("HOME") : "unknown";
|
|
325
|
+
if (home === "unknown" || !extend(home.map((v) => ({ text: v.spelled, runs: quotedRunsOf(v) }))))
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
else if (e.kind === "parameter" && e.name !== undefined && (e.body === undefined || e.body === e.name)) {
|
|
329
|
+
const state = stateOf(e.name);
|
|
330
|
+
if (state === "unknown")
|
|
331
|
+
return false;
|
|
332
|
+
if (!bare) {
|
|
333
|
+
if (!extend(state.map((v) => ({ text: v.spelled, runs: quotedRunsOf(v) }))))
|
|
334
|
+
return false;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const kinds = state.map(unquotedKind);
|
|
338
|
+
if (table?.ifsTouched === true || kinds.includes(undefined))
|
|
339
|
+
return false;
|
|
340
|
+
if (!kinds.includes("split")) {
|
|
341
|
+
if (!extend(state.map((v) => ({ text: v.spelled, runs: [activeRun(v.spelled)] }))))
|
|
342
|
+
return false;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const next = combos.flatMap((c) => state.map((v) => splitInto(c, v.spelled)));
|
|
346
|
+
if (next.length > WORD_VALUES_MAX)
|
|
347
|
+
return false;
|
|
348
|
+
combos = next;
|
|
349
|
+
}
|
|
350
|
+
else
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
};
|
|
355
|
+
if (!walk(word.pieces, !quoted))
|
|
356
|
+
return "unknown";
|
|
269
357
|
const out = [];
|
|
270
358
|
for (const c of combos) {
|
|
271
|
-
const
|
|
359
|
+
const fields = (c.split ? (c.started ? [...c.closed, c.field] : c.closed) : [c.field]).map(formedField);
|
|
360
|
+
const formed = fields.filter((f) => f !== undefined && !(c.split && f.glob === true));
|
|
361
|
+
if (formed.length !== fields.length)
|
|
362
|
+
return "unknown";
|
|
363
|
+
const v = !c.split ? formed[0] : formed.length !== 1 ? { spelled: formed.map((f) => f.spelled).join(" "), fields: formed.map((f) => f.spelled) } : { spelled: formed[0].spelled };
|
|
272
364
|
if (!out.some((x) => sameValue(x, v)))
|
|
273
365
|
out.push(v);
|
|
274
366
|
}
|
|
@@ -288,22 +380,29 @@ export function wordFloorValues(word, table) {
|
|
|
288
380
|
}
|
|
289
381
|
else if (e.kind === "parameter" && e.name !== undefined) {
|
|
290
382
|
const state = table.stateOf(e.name);
|
|
291
|
-
options = state !== "unknown" && (e.body === undefined || e.body === e.name) ? state : table.floorOf(e.name);
|
|
383
|
+
options = state !== "unknown" && (e.body === undefined || e.body === e.name) ? state.map(floorOf) : table.floorOf(e.name);
|
|
292
384
|
}
|
|
293
385
|
else
|
|
294
386
|
return [];
|
|
295
387
|
if (options.length === 0)
|
|
296
388
|
return [];
|
|
297
389
|
const next = [];
|
|
298
|
-
for (const c of combos)
|
|
299
|
-
for (const o of options)
|
|
300
|
-
|
|
390
|
+
for (const c of combos) {
|
|
391
|
+
for (const o of options) {
|
|
392
|
+
const pattern = c.pattern !== undefined || o.pattern !== undefined ? (c.pattern ?? c.spelled) + (o.pattern ?? o.spelled) : undefined;
|
|
393
|
+
next.push(c.glob === true || o.glob === true ? { spelled: c.spelled + o.spelled, glob: true, ...(pattern !== undefined ? { pattern } : {}) } : { spelled: c.spelled + o.spelled });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
301
396
|
if (next.length > WORD_VALUES_MAX)
|
|
302
397
|
return [];
|
|
303
398
|
combos = next;
|
|
304
399
|
}
|
|
305
400
|
return combos;
|
|
306
401
|
}
|
|
402
|
+
export function loopListHeadAt(argv) {
|
|
403
|
+
const at = isShellSyntaxWord(argv[0], LOOP_LIST_HEADS) ? 0 : readProgramPosition(argv).written;
|
|
404
|
+
return at !== undefined && isShellSyntaxWord(argv[at], LOOP_LIST_HEADS) ? at : undefined;
|
|
405
|
+
}
|
|
307
406
|
export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
308
407
|
const bindings = [];
|
|
309
408
|
let all = shape.definesFunction ? "reset" : undefined;
|
|
@@ -353,21 +452,21 @@ export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
|
353
452
|
kill(name);
|
|
354
453
|
return { bindings, all };
|
|
355
454
|
}
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
455
|
+
const loopAt = loopListHeadAt(argv);
|
|
456
|
+
if (loopAt !== undefined) {
|
|
457
|
+
const headWord = argv[loopAt].folded;
|
|
359
458
|
if (tokens.some((t) => t.kind === "group"))
|
|
360
459
|
unknownAll();
|
|
361
|
-
const name = argv[1] !== undefined ? nameOf(argv[1]) : undefined;
|
|
460
|
+
const name = argv[loopAt + 1] !== undefined ? nameOf(argv[loopAt + 1]) : undefined;
|
|
362
461
|
if (name === undefined || name === "computed") {
|
|
363
462
|
unknownAll();
|
|
364
463
|
return { bindings, all };
|
|
365
464
|
}
|
|
366
|
-
const inAt = argv.findIndex((w, i) => i >= 2 && w.raw === "in");
|
|
465
|
+
const inAt = argv.findIndex((w, i) => i >= loopAt + 2 && w.raw === "in");
|
|
367
466
|
let state = "unknown";
|
|
368
|
-
if (headWord === "for" && inAt === 2 && argv.length > 3) {
|
|
467
|
+
if (headWord === "for" && inAt === loopAt + 2 && argv.length > loopAt + 3) {
|
|
369
468
|
const values = [];
|
|
370
|
-
for (const w of argv.slice(3)) {
|
|
469
|
+
for (const w of argv.slice(loopAt + 3)) {
|
|
371
470
|
const v = readValue(w, false, overlay);
|
|
372
471
|
if (v === "unknown") {
|
|
373
472
|
values.length = 0;
|
|
@@ -381,7 +480,7 @@ export function bindingEffectOf(segment, argv, shape, readValue) {
|
|
|
381
480
|
bind({ name, state, loopVar: true });
|
|
382
481
|
return { bindings, all };
|
|
383
482
|
}
|
|
384
|
-
if (
|
|
483
|
+
if (argv[0]?.raw === "coproc" && argv[1] !== undefined)
|
|
385
484
|
killNamed(nameOf(argv[1]));
|
|
386
485
|
const pos = readProgramPosition(argv);
|
|
387
486
|
const program = pos.program;
|
|
@@ -490,7 +589,7 @@ function compoundFloor(raw, readValue, overlay) {
|
|
|
490
589
|
return compoundElementWords(raw).flatMap((w) => {
|
|
491
590
|
const v = w.tail === true ? "unknown" : readValue(w, true, overlay);
|
|
492
591
|
if (v !== "unknown")
|
|
493
|
-
return
|
|
592
|
+
return v.map(floorOf);
|
|
494
593
|
const text = w.pieces.some((q) => q.expansion?.kind === "tilde") ? w.folded : literalTildeSpelling(w.folded);
|
|
495
594
|
return [w.pieces.some((q) => q.expansion?.kind === "glob") ? { spelled: text, glob: true } : { spelled: text }];
|
|
496
595
|
});
|
|
@@ -550,6 +649,7 @@ export class BindingThread {
|
|
|
550
649
|
unreadable;
|
|
551
650
|
blocks;
|
|
552
651
|
detached;
|
|
652
|
+
startFloors;
|
|
553
653
|
constructor(segments, named, initial, boundAnywhere, loops, homeStartsTouched = false, inherited = NOTHING_INHERITED) {
|
|
554
654
|
this.segments = segments;
|
|
555
655
|
this.named = named;
|
|
@@ -557,6 +657,7 @@ export class BindingThread {
|
|
|
557
657
|
this.boundAnywhere = boundAnywhere;
|
|
558
658
|
this.loops = loops;
|
|
559
659
|
this.homeStartsTouched = homeStartsTouched;
|
|
660
|
+
this.startFloors = inherited.floors ?? new Map();
|
|
560
661
|
this.blocks = blockStructure(segments);
|
|
561
662
|
this.detached = segments.map((s, i) => s.connector === "|" || segments[i + 1]?.connector === "|" || segments[i + 1]?.connector === "&");
|
|
562
663
|
this.unreadable = [...(inherited.references ? [{ at: -1 }] : []), ...named.flatMap((effect, at) => this.unreadableBy(effect, at))];
|
|
@@ -564,9 +665,14 @@ export class BindingThread {
|
|
|
564
665
|
}
|
|
565
666
|
inheritedAt(j) {
|
|
566
667
|
const holds = (at) => j === undefined || this.mayRunBefore(at, j);
|
|
668
|
+
const floors = new Map(this.startFloors);
|
|
669
|
+
for (const e of this.entries)
|
|
670
|
+
if (e.name !== undefined && e.floor !== undefined && (j === undefined || e.at < j))
|
|
671
|
+
floors.set(e.name, [...(floors.get(e.name) ?? []), ...e.floor]);
|
|
567
672
|
return {
|
|
568
673
|
attributes: this.attributesAt.filter((x) => holds(x.at)).map(({ name, kind }) => (name !== undefined ? { name, kind } : { kind })),
|
|
569
674
|
references: this.unreadable.some((u) => u.name === undefined && u.body === undefined && holds(u.at)),
|
|
675
|
+
...(floors.size > 0 ? { floors } : {}),
|
|
570
676
|
};
|
|
571
677
|
}
|
|
572
678
|
unreadableBy(effect, at) {
|
|
@@ -688,7 +794,7 @@ export class BindingThread {
|
|
|
688
794
|
isInteger: (name) => SHELL_INTEGER_VARIABLES.has(name) || given(name, j, true),
|
|
689
795
|
ifsTouched: touched("IFS"),
|
|
690
796
|
homeTouched: this.homeStartsTouched || touched("HOME"),
|
|
691
|
-
floorOf: (name) => this.entries.filter((e) => e.at < j && e.name === name).flatMap((e) => e.floor ?? []),
|
|
797
|
+
floorOf: (name) => [...(this.startFloors.get(name) ?? []), ...this.entries.filter((e) => e.at < j && e.name === name).flatMap((e) => e.floor ?? [])],
|
|
692
798
|
inherited: this.inheritedAt(j),
|
|
693
799
|
};
|
|
694
800
|
}
|
|
@@ -57,11 +57,10 @@ function parseBracketExpression(pattern, start) {
|
|
|
57
57
|
return { source: anyChar ? "[^/]" : `[${negate ? "^" : ""}${body}]`, end: i };
|
|
58
58
|
}
|
|
59
59
|
if (ch === "[" && (pattern[i + 1] === ":" || pattern[i + 1] === "." || pattern[i + 1] === "=")) {
|
|
60
|
-
const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
|
|
61
|
-
if (close < 0)
|
|
62
|
-
return undefined;
|
|
63
60
|
anyChar = true;
|
|
64
|
-
|
|
61
|
+
const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
|
|
62
|
+
if (close >= 0 && (pattern[i + 1] === ":" ? /^[A-Za-z]+$/.test(pattern.slice(i + 2, close)) : close === i + 3))
|
|
63
|
+
i = close + 1;
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
66
|
body += ch === "-" ? "-" : ch.replace(/[\]\\^]/g, "\\$&");
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
* Every ambiguity resolves toward MORE candidates, never fewer.
|
|
31
31
|
*/
|
|
32
32
|
import { type LenientConnector, type LexedRedirect, type LexedWord } from "../../shell/lex.js";
|
|
33
|
-
import { type BindingTable, type NameReader } from "./shell-read-bindings.js";
|
|
34
33
|
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
35
34
|
import { type StdinText } from "./shell-reentry-text.js";
|
|
35
|
+
import { type PlanStart } from "./shell-read-start.js";
|
|
36
36
|
/** WHY a word is a read candidate — a closed set, registered in `docs/CLOSED-SETS.md`. */
|
|
37
37
|
export declare const READ_CANDIDATE_PROVENANCES: readonly ["operand", "option_value", "stdin_redirect", "write_redirect_target", "env_assignment", "unlisted_word", "traversal_root", "cd_target", "program_path", "script_slot_file", "mount_source", "object_path"];
|
|
38
38
|
export type ReadCandidateProvenance = (typeof READ_CANDIDATE_PROVENANCES)[number];
|
|
@@ -142,18 +142,5 @@ export interface ShellReadPlan {
|
|
|
142
142
|
readonly strayRedirection: boolean;
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
|
-
/** Where a nested text starts its names: a substitution body inherits the table of the segment it sits in (a subshell
|
|
146
|
-
* holds every variable); a text the shell runs later or elsewhere (a code body, `eval`'s text, a child shell's `-c`)
|
|
147
|
-
* starts from the text's own start — every name unknown, `HOME` the declared home unless the enclosing text binds it
|
|
148
|
-
* anywhere. `boundAnywhere` answers for the enclosing texts. */
|
|
149
|
-
interface PlanStart {
|
|
150
|
-
readonly initial: NameReader;
|
|
151
|
-
readonly boundAnywhere: (name: string) => boolean;
|
|
152
|
-
/** `HOME` may already hold something other than the declared home where the nested text starts. */
|
|
153
|
-
readonly homeTouched: boolean;
|
|
154
|
-
/** The attributes and name references in force where the nested text starts. */
|
|
155
|
-
readonly inherited: BindingTable["inherited"];
|
|
156
|
-
}
|
|
157
145
|
/** Plan every read candidate of one command (the contract is this module's header). */
|
|
158
146
|
export declare function planShellReads(command: string, decl: ShellReadDeclarations, depth?: number, start?: PlanStart): ShellReadPlan;
|
|
159
|
-
export {};
|