@sema-agent/core
Version:
Stateless, task-oriented AI agent core
184 lines (183 loc) • 6.17 kB
JavaScript
const SPECIAL_PARAMS = "?$!#@*-0123456789";
const SUBSTITUTION_RE = /\$\(|`|<\(|>\(|\$\[/;
export const arithmeticHidesRun = (expr) => /[^\s\d+\-*/%&|^~!<>=?:,()]/.test(expr);
const SUBSCRIPT_RE = /\$\{[!#]?[A-Za-z_][A-Za-z0-9_]*(?:\[[^\]]*[A-Za-z_$]|(?:\[[^\]]*\])?:(?![-=+?])[^}]*[A-Za-z_$])/;
export const carriesSubstitution = (text) => { const t = text.replace(/\\\n/g, ""); return SUBSTITUTION_RE.test(t) || SUBSCRIPT_RE.test(t); };
const PER_ELEMENT_RE = /^\$@$|^\$\{!?@|\[@\]|^\$\{![^}]*@\}$/;
export const freshWord = () => ({ text: "", raw: "", expands: false, started: false, braceOpen: false, braceExpands: false, bracketOpen: false, substitution: false });
export function readDoubleQuoted(s, i, word) {
word.started = true;
while (i < s.length) {
const ch = s[i];
if (ch === '"')
return i + 1;
if (ch === "\\") {
const next = s[i + 1];
if (next === undefined)
return undefined;
if (next === "\n") {
i += 2;
continue;
}
if ('$`"\\'.includes(next)) {
word.text += next;
i += 2;
continue;
}
word.text += ch;
i++;
continue;
}
if (ch === "$" || ch === "`") {
const end = readExpansion(s, i, word, true);
if (end === undefined)
return undefined;
if (end === i + 1) {
word.text += ch;
i = end;
continue;
}
if (PER_ELEMENT_RE.test(s.slice(i, end).replace(/\\\n/g, "")))
word.expands = "many";
else if (word.expands === false)
word.expands = "one";
i = end;
continue;
}
word.text += ch;
i++;
}
return undefined;
}
export function readExpansion(s, i, word, inDoubleQuotes = false) {
if (s[i] === "`") {
word.substitution = true;
for (let j = i + 1; j < s.length; j += s[j] === "\\" ? 2 : 1)
if (s[j] === "`")
return j + 1;
return undefined;
}
let o = i + 1;
while (s[o] === "\\" && s[o + 1] === "\n")
o += 2;
const next = s[o];
if (next === undefined)
return i + 1;
if (next === "(") {
const end = skipBalanced(s, o, "(", ")");
if (end === undefined)
return undefined;
if (s[i] !== "$" || s[o + 1] !== "(" || arithmeticHidesRun(s.slice(o + 2, end - 2)))
word.substitution = true;
return end;
}
if (next === "[") {
const end = s.indexOf("]", o);
word.substitution = true;
return end < 0 ? undefined : end + 1;
}
if (next === "{") {
const end = skipBalanced(s, o, "{", "}");
if (end !== undefined && carriesSubstitution(s.slice(i, end)))
word.substitution = true;
return end;
}
if (next === "'" && !inDoubleQuotes) {
let j = o + 1;
while (j < s.length && s[j] !== "'")
j += s[j] === "\\" ? 2 : 1;
return j < s.length ? j + 1 : undefined;
}
if (/[A-Za-z_]/.test(next)) {
let j = o + 1;
while (j < s.length && /[A-Za-z0-9_]/.test(s[j]))
j++;
return j;
}
return SPECIAL_PARAMS.includes(next) ? o + 1 : i + 1;
}
export function skipBalanced(s, i, open, close) {
const stack = [];
for (let j = i; j < s.length; j++) {
const ch = s[j];
const top = stack[stack.length - 1];
if (ch === "\\") {
j++;
continue;
}
if (top?.quote === true) {
if (ch === '"')
top.quote = false;
else if (ch === "$" && (s[j + 1] === "(" || s[j + 1] === "{")) {
stack.push({ closer: s[j + 1] === "(" ? ")" : "}", quote: false });
j++;
}
else if (ch === "`") {
const k = s.indexOf("`", j + 1);
if (k < 0)
return undefined;
j = k;
}
continue;
}
if (ch === "'") {
const k = s.indexOf("'", j + 1);
if (k < 0)
return undefined;
j = k;
continue;
}
if (ch === '"') {
if (top !== undefined)
top.quote = true;
continue;
}
if (ch === "`") {
const k = s.indexOf("`", j + 1);
if (k < 0)
return undefined;
j = k;
continue;
}
if (ch === open || ch === "(" || ch === "{") {
stack.push({ closer: ch === "(" ? ")" : "}", quote: false });
continue;
}
if (ch === close || ch === ")" || ch === "}") {
if (stack.pop()?.closer !== ch)
return undefined;
if (stack.length === 0)
return j + 1;
}
}
return undefined;
}
export function skipHeredocBodies(s, i, docs) {
let substitution = false;
for (const { delimiter, quoted, dash } of docs) {
for (;;) {
if (i >= s.length)
return undefined;
let eol = s.indexOf("\n", i);
let line = s.slice(i, eol < 0 ? s.length : eol);
while (!quoted && eol >= 0 && (/\\+$/.exec(line)?.[0].length ?? 0) % 2 === 1) {
const next = s.indexOf("\n", eol + 1);
line = line.slice(0, -1) + s.slice(eol + 1, next < 0 ? s.length : next);
eol = next;
}
i = eol < 0 ? s.length : eol + 1;
if ((dash ? line.replace(/^\t+/, "") : line) === delimiter)
break;
if (!quoted && carriesSubstitution(line))
substitution = true;
}
}
return { end: i, substitution };
}
export function matchOp(s, i, ops) {
let best;
for (const op of ops)
if (s.startsWith(op, i) && (best === undefined || op.length > best.length))
best = op;
return best;
}