@sema-agent/core
Version:
Stateless, task-oriented AI agent core
61 lines (60 loc) • 4.34 kB
TypeScript
/**
* The character-level scanners under the tightening reader's shell lexer (`shell-lexer.ts`): a double-
* quoted body, an expansion (`$…`, a backtick, a process substitution), a balanced `(…)` / `{…}` region,
* and the here-document bodies. Every scanner answers an INDEX (past what it read) or `undefined` for
* "never closes", and reports through `word.substitution` / the heredoc result whether it passed a
* COMMAND SUBSTITUTION — `$(…)`, a backtick, `<(…)`/`>(…)`, anywhere, including inside `${…}` and
* `$((…))` — because a substitution runs a program the lexer does not read: the lexer marks the segment
* and the reader answers `unreadable` for every rule the segment does not otherwise reach. Reading the
* commands INSIDE a substitution was tried and retired: every bash quirk of substitution boundaries
* (a `)` in a comment, a multi-line body in a heredoc, a `$"…"` string) became a way to hide a run, and
* the fail-closed word costs an approval prompt, never a bypass. Split from the lexer so the lexer's own
* ceiling holds the segment/peel logic and this one holds the byte walking.
*/
/** A word under construction; `braceOpen`/`braceExpands` track an unquoted `{a,b}` / `{1..3}` pattern, `bracketOpen` a `[…]` class;
* `substitution` is set when the word carries a command/process substitution anywhere. */
export interface Word {
text: string;
raw: string;
expands: false | "one" | "many";
started: boolean;
braceOpen: boolean;
braceExpands: boolean;
bracketOpen: boolean;
substitution: boolean;
}
export interface Heredoc {
delimiter: string;
quoted: boolean;
dash: boolean;
}
/** Is `expr`, the interior of an arithmetic expansion or command, provably free of a hidden run? Only digits,
* operators and spaces are: a NAME or a `$…` evaluates recursively (`a='arr[$(…)]'; $((a))`), and anything
* else (`;`, a quote, a bracket) is not arithmetic at all — bash then re-reads `((…))` as nested subshells. */
export declare const arithmeticHidesRun: (expr: string) => boolean;
/** Does an expansion interior, an extended-glob interior or a heredoc line carry a run this reader does not
* read — a substitution opener, or a subscript that evaluates indirectly? Backslash-newline is removed
* first, as bash removes it before it reads the opener (`$\<newline>(` is `$(`). */
export declare const carriesSubstitution: (text: string) => boolean;
export declare const freshWord: () => Word;
/** Read a `"…"` body into `word` from `i` (just past the opening quote); returns the index past the closing quote. */
export declare function readDoubleQuoted(s: string, i: number, word: Word): number | undefined;
/** The index past the expansion starting at `i` (`$…`, a backtick, or a `<(`/`>(` process substitution);
* `i + 1` when the `$` is literal; `undefined` when it never closes. Sets `word.substitution` when the
* region is, or contains, a command/process substitution. A `$"…"` string is NOT read here (the lexer
* reads it as a double-quoted string, which is what it is); inside double quotes `$'` is a literal `$`
* (ANSI-C quoting exists only outside them). */
export declare function readExpansion(s: string, i: number, word: Word, inDoubleQuotes?: boolean): number | undefined;
/** Past the matching `close` for the `open` at `i`, quote-aware (a `)` inside `$(echo ")")` is text).
* LINEAR: one pass with a small state stack (nesting depth × quote state), no re-entry. */
export declare function skipBalanced(s: string, i: number, open: string, close: string): number | undefined;
/** Skip the here-document bodies that begin at `i` (the start of a line), one per delimiter in order,
* answering the index past them and whether an UNQUOTED body carries a substitution (bash expands such a
* body: `$(…)` in it runs). An unquoted body joins a line ending in an ODD run of backslashes with the
* next before the delimiter is compared; `<<-` strips leading tabs. */
export declare function skipHeredocBodies(s: string, i: number, docs: readonly Heredoc[]): {
end: number;
substitution: boolean;
} | undefined;
/** The LONGEST operator of `ops` at `i` (`>&` before `>`, `&&` before `&`). */
export declare function matchOp(s: string, i: number, ops: readonly string[]): string | undefined;