UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

184 lines (183 loc) • 6.17 kB
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; }