UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

61 lines (60 loc) • 4.34 kB
/** * 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;