UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

161 lines • 6.12 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { REDIRECT_NODE_TYPES } from "./helpers.js"; import { NodeType as NT } from "./types.js"; /** * Statement kinds the executor dispatches on. * * `nodeKind` owns every node-type check, including the * lookahead that distinguishes `select` from `for`, `until` from `while` * and a `(( ))` command from a `{ }` group. */ export const NodeKind = Object.freeze({ COMMENT: 'comment', PROGRAM: 'program', TIMED: 'timed', COMMAND: 'command', PIPELINE: 'pipeline', LIST: 'list', REDIRECT: 'redirect', SUBSHELL: 'subshell', COMPOUND: 'compound', ARITH: 'arith', IF: 'if', FOR: 'for', CFOR: 'cfor', SELECT: 'select', WHILE: 'while', UNTIL: 'until', CASE: 'case', FUNCTION_DEF: 'function_def', DECLARATION: 'declaration', UNSET: 'unset', TEST: 'test', NEGATED: 'negated', VAR_ASSIGN: 'var_assign', VAR_ASSIGNS: 'var_assigns', UNSUPPORTED: 'unsupported', }); const SIMPLE_KINDS = Object.freeze({ [NT.COMMENT]: NodeKind.COMMENT, [NT.PROGRAM]: NodeKind.PROGRAM, [NT.TIMED_STATEMENT]: NodeKind.TIMED, [NT.COMMAND]: NodeKind.COMMAND, [NT.PIPELINE]: NodeKind.PIPELINE, [NT.LIST]: NodeKind.LIST, [NT.REDIRECTED_STATEMENT]: NodeKind.REDIRECT, [NT.SUBSHELL]: NodeKind.SUBSHELL, [NT.COMPOUND_STATEMENT]: NodeKind.COMPOUND, [NT.IF_STATEMENT]: NodeKind.IF, [NT.CASE_STATEMENT]: NodeKind.CASE, [NT.FUNCTION_DEFINITION]: NodeKind.FUNCTION_DEF, [NT.DECLARATION_COMMAND]: NodeKind.DECLARATION, [NT.UNSET_COMMAND]: NodeKind.UNSET, [NT.TEST_COMMAND]: NodeKind.TEST, [NT.NEGATED_COMMAND]: NodeKind.NEGATED, [NT.VARIABLE_ASSIGNMENT]: NodeKind.VAR_ASSIGN, [NT.VARIABLE_ASSIGNMENTS]: NodeKind.VAR_ASSIGNS, [NT.C_STYLE_FOR_STATEMENT]: NodeKind.CFOR, }); // Statement kinds that are not pipelines of their own: they run other // statements and report the last one's status, so `${PIPESTATUS[@]}` // after them is whatever the last pipeline inside them left (bash: // `{ false | true; }` keeps `1 0`, `! false | true` keeps `1 0`). // Everything else, a simple command, a function call, a subshell, an // assignment, a `(( ))`, is a pipeline of one segment and stamps its own // status. A redirected statement is not here because it is as // transparent as what it redirects (`pipelineTransparent` looks inside). // A list is not here either: its left side is closed by the list handler // and its right side by the list's own boundary, so `false | true && true` // reports the `true`; a list that short-circuits carries its left // pipeline to that boundary (`carryStatus`). const PIPELINE_TRANSPARENT_KINDS = new Set([ NodeKind.COMPOUND, NodeKind.IF, NodeKind.FOR, NodeKind.CFOR, NodeKind.SELECT, NodeKind.WHILE, NodeKind.UNTIL, NodeKind.CASE, NodeKind.NEGATED, NodeKind.TIMED, NodeKind.FUNCTION_DEF, ]); /** * Whether a statement leaves `PIPESTATUS` to the pipelines inside it * rather than stamping its own exit status. */ export function pipelineTransparent(node) { const kind = nodeKind(node); if (kind === NodeKind.REDIRECT) { // A redirected statement is as transparent as what it redirects: // `{ false | true; } >f` keeps the group's record, while `echo hi >f` // and `cat </missing` are a simple command's own one-segment status // whether or not the redirect opened. A bare redirect (`>f`) runs // the empty command, one segment too. const inner = node.namedChildren?.find((child) => !REDIRECT_NODE_TYPES.has(child.type)); return inner !== undefined && pipelineTransparent(inner); } return PIPELINE_TRANSPARENT_KINDS.has(kind); } const SIMPLE_COMMAND_KINDS = new Set([ NodeKind.COMMAND, NodeKind.DECLARATION, NodeKind.UNSET, NodeKind.VAR_ASSIGN, NodeKind.VAR_ASSIGNS, ]); /** * Whether a statement is a simple command: a command word, a function call * or a declaration, not a compound command. A child shell forked to run one * (a pipeline stage, a `&` job) is that command, so an error that ends it * reports the status the shell itself would exit with (`: ${U?} &` is 127); * a compound command forked the same way reports a subshell's * (`{ : ${U?}; } &` is 1). */ export function simpleCommand(node) { const kind = nodeKind(node); if (kind === NodeKind.REDIRECT) { const inner = node.namedChildren?.find((child) => !REDIRECT_NODE_TYPES.has(child.type)); return inner === undefined || simpleCommand(inner); } return SIMPLE_COMMAND_KINDS.has(kind); } /** * Classify a parsed node into the shared statement kind, or * UNSUPPORTED for node types neither walker implements (future grammar * additions). */ export function nodeKind(node) { const ntype = node.type; const simple = SIMPLE_KINDS[ntype]; if (simple === NodeKind.COMPOUND && node.children?.[0]?.type === NT.ARITH_OPEN) { return NodeKind.ARITH; } if (simple !== undefined) return simple; if (ntype === NT.FOR_STATEMENT) { if (node.children?.[0]?.type === NT.SELECT) return NodeKind.SELECT; return NodeKind.FOR; } if (ntype === NT.WHILE_STATEMENT) { if (node.children?.[0]?.type === NT.UNTIL) return NodeKind.UNTIL; return NodeKind.WHILE; } return NodeKind.UNSUPPORTED; } //# sourceMappingURL=node_kind.js.map