UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

96 lines (95 loc) • 5.03 kB
import { argvOf, lexShell, literalTildeSpelling, UNRESOLVED_SENTENCE } from "../../shell/lex.js"; import { expansionInWord, extglobOpenerIn, globInWord, homeExpansionOf } from "./bash-readonly-classifier.js"; export function readsOf(words, home) { return words.map((word) => { const spelled = home === undefined ? undefined : homeExpansionOf(word, home)?.spelled; if (spelled !== undefined) return { word, raw: spelled, folded: spelled, expands: false, homeSpelled: true }; return { word, raw: word.raw, folded: word.folded, expands: expandsOf(word) }; }); } export const rewritten = (r) => r.homeSpelled !== true && expansionInWord(r.word); export function readOfText(text) { const l = lexShell(text); const words = "parseFailure" in l || l.segments.length !== 1 ? [] : argvOf(l.segments[0]); const word = words[0]; if (word === undefined || words.length !== 1) { const stub = { kind: "word", raw: text, folded: text, pieces: [], quotedSpans: [], expansions: [], atCommandPosition: false }; return { word: stub, raw: text, folded: text, expands: "unresolvable" }; } return { word, raw: text, folded: text, expands: expandsOf(word) }; } export function spelledWithHome(spelled, raw, home) { const tilde = spelled.startsWith("./~") ? spelled.slice(2) : spelled; if (tilde.startsWith("~")) return /^~(?:\/|$)/.test(tilde) ? `${home}${tilde.slice(1)}` : undefined; if (!raw.includes("HOME")) return undefined; const w = readOfText(raw).word; return homeExpansionOf(w.assignment !== undefined ? { ...w, pieces: w.assignment.value.pieces } : w, home)?.spelled; } export function dropDefinitionHeads(tokens) { for (let k = 0; k + 1 < tokens.length; k++) { const [a, b, c, d] = [tokens[k], tokens[k + 1], tokens[k + 2], tokens[k + 3]]; if (a.kind === "word" && a.raw === "function" && a.atCommandPosition && b.kind === "word") tokens.splice(k--, c?.kind === "group" && c.op === "(" && d?.kind === "group" && d.op === ")" ? 4 : 2); } for (let k = 0; k + 2 < tokens.length; k++) { const [a, b, c] = [tokens[k], tokens[k + 1], tokens[k + 2]]; if (a.kind === "word" && b.kind === "group" && b.op === "(" && c.kind === "group" && c.op === ")" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(a.folded)) tokens.splice(k, 3); } } export function unreadableSentenceOf(lexed, text, globs = true) { for (const segment of lexed.segments) { for (const token of segment.tokens) { const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined; if (word?.unresolved !== undefined) return UNRESOLVED_SENTENCE[word.unresolved]; } } return globs && extglobOpenerIn(lexed, text) ? "an extended pattern (`?(…)` `*(…)` `+(…)` `@(…)` `!(…)`) is one this reader does not expand" : undefined; } export function compoundArrayElements(value) { const inner = /^\(([\s\S]*)\)$/.exec(value.raw)?.[1]; const l = inner === undefined ? undefined : lexShell(inner); if (l === undefined || "parseFailure" in l) return []; return l.segments.flatMap((sg) => argvOf(sg)).flatMap((element) => { const sub = /^\[[^\]]*\]\+?=/.exec(element.raw)?.[0]; const value = sub === undefined ? undefined : readOfText(`V=${element.raw.slice(sub.length)}`).word.assignment?.value; const word = value === undefined ? element : { ...element, raw: value.raw, folded: value.folded, pieces: value.pieces }; const tilde = word.pieces.some((q) => q.expansion?.kind === "tilde"); const read = { word, raw: word.raw, folded: tilde ? word.folded : literalTildeSpelling(word.folded), expands: expandsOf(word) }; const prefix = /^\[[^\]]*\]\+?=/.exec(element.folded)?.[0]; if (prefix === undefined) return [read]; const spelled = element.pieces.some((q) => q.expansion?.kind === "tilde") ? element.folded.slice(prefix.length) : literalTildeSpelling(element.folded.slice(prefix.length)); return [read, { word: element, raw: element.raw, folded: spelled, expands: expandsOf(element), denyOnly: true }]; }); } export function isArithmeticBody(body) { const l = body.startsWith("(") && body.endsWith(")") ? lexShell(body) : undefined; if (l === undefined || "parseFailure" in l) return false; const tokens = l.segments.flatMap((sg) => sg.tokens); if (tokens[0]?.kind !== "group" || tokens[0].op !== "(") return false; let depth = 0; for (const [i, t] of tokens.entries()) { if (t.kind !== "group") continue; if (t.op === "(") depth++; else if (--depth === 0) return i === tokens.length - 1; } return false; } export function expandsOf(w) { if (expansionInWord(w) || /[`()]/.test(w.raw)) return "unresolvable"; if (globInWord(w)) return "glob"; return false; }