@sema-agent/core
Version:
Stateless, task-oriented AI agent core
96 lines (95 loc) • 5.03 kB
JavaScript
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;
}