Skip to main content

Module interpreter

Module interpreter 

Source
Expand description

Interpreter module for kaish.

This module provides expression evaluation, variable scope management, and the structured result type ($?) that every command returns.

§Architecture

The interpreter is built in layers:

  • ExecResult: The structured result of every command, accessible as $?
  • Scope: Variable bindings with nested frames and path resolution
  • Evaluator: Reduces expressions to values

§Command Substitution

$(pipeline) expressions are executed by the async evaluator in the kernel (kernel.rs), which resolves them to literal values before any synchronous expression evaluation runs. The sync eval_expr evaluator therefore never sees a CommandSubst node; encountering one is a loud error, not a silent empty string.

§Example

use kaish_kernel::interpreter::{Scope, eval_expr};
use kaish_kernel::ast::{Expr, Value};

let mut scope = Scope::new();
scope.set("X", Value::Int(42));

let expr = Expr::VarRef(kaish_kernel::ast::VarPath::simple("X"));
let result = eval_expr(&expr, &mut scope).unwrap();
assert_eq!(result, Value::Int(42));

Structs§

Evaluator
Expression evaluator.
ExecResult
The result of executing a command or pipeline.
HeredocAssembler
Assembles a heredoc body part-by-part, applying POSIX <<- leading-tab stripping to the source rather than to the materialized result.
OutputData
Structured output data from a command.
OutputNode
A node in the output tree.
Scope
Variable scope with nested frames and last-result tracking.

Enums§

ControlFlow
Control flow signal from statement execution.
EntryType
Entry type for rendering hints (colors, icons).
EvalError
Errors that can occur during expression evaluation.
OutputFormat
Output serialization format, requested via global flags.
OutputPayload
A command’s stdout payload: text, or raw bytes.
PathError
Why a variable path failed to resolve.

Functions§

apply_output_format
Transform an ExecResult into the requested output format.
eval_expr
Convenience function to evaluate an expression with a scope.
expand_tilde
Expand tilde (~) to home directory.
hex_dump
Render bytes as an xxd-style hex dump for human display.
is_collection
True if value is a first-class collection (list/record) rather than a scalar. Value::Json also carries JSON scalars (numbers/strings/bool/null unwrapped at the value boundary — see json_to_value_no_envelope), so this only matches the two container variants.
json_to_value
Convert serde_json::Value to our AST Value.
json_to_value_no_envelope
Convert serde_json::Value to our AST Value without bytes-envelope sniffing.
numeric_compare
Numeric ordering for [[ -eq ]]/-gt/-lt/-ge/-le/-ne. Coerces string operands via value_to_num. Shared verbatim with the test builtin so test’s numeric ops match [[ exactly (JSON-number semantics, floats included — not POSIX integer-only).
resolve_default
${path:-default} decision over the shared path resolver (decision A): Ok(Some(v)) use the value, Ok(None) use the default (absence or emptiness), Err(msg) a loud shape error the default does NOT suppress. An unset root, a missing key, and an out-of-bounds index all fold into the default; only a wrong-shaped access shouts.
resolve_length
${#path} length semantics over the shared path resolver. An unset BARE root is length 0 (bash parity for ${#unset}); an unset root under a SUBSCRIPTED path is loud, consistent with bare ${nope[k]} — a typo’d name in while [[ ${#queue[items]} -gt 0 ]] must not silently count 0 forever (2026-07-03 review finding). Missing key, out-of-bounds index, and shape errors stay loud. The resolver borrows the tree — no whole-root clone.
scalar_test_operand_error
Decision E: scalar-only test operators (-z/-n, =~/!~, ordering </>/<=/>=, and numeric -eq/-ne/-gt/-lt/-ge/-le) refuse a collection operand loudly rather than falling through a stringify/silent path. ==/!= already error via values_equal; in/not in are the one operator family that legitimately takes a collection operand and must never call this. None for scalars. Shared by the sync eval_test (interpreter/eval.rs) and the async eval_test_async (kernel.rs) so the two [[ ]] evaluators can’t drift apart on this guard.
strip_leading_tabs
Strip leading tabs from each line, per POSIX <<-EOF heredoc semantics.
structured_boundary_error
Decision D: reject a bare collection value crossing a process-boundary sink — an external command’s argv element, or a redirect target. String interpolation ("$c") already reduces to a Value::String (rendering compact JSON) before reaching either sink, so only a live, un-interpolated Value::Json(Array|Object) — a bare $c — trips this. sink names the boundary in the message (e.g. “a command argument”, “a redirect target”). None for scalars. Callers: kernel.rs (build_args_flat, try_execute_external), dispatch.rs (try_external, test-only twin), scheduler/pipeline.rs (eval_redirect_target).
structured_export_error
Reject exporting a structured value into an OS env var. A list/record can’t cross the process boundary, and kaish will not silently JSON-serialize it into the child’s environment. Returns a loud “serialize first” message naming the offending variable, or None if every exported value is a scalar. Both external-spawn sites (kernel.rs, dispatch.rs) call this before cmd.env.
value_defaults_on_emptiness
Decision A: ${path:-default} yields the default on absence or emptiness, never on falsy values. Fires for a JSON null and an empty string; NOT for false, 0, an empty list [], or an empty record {} — those are present values, and Python-style truthiness leaking into a shell would be a silent-wrong factory. (Unset roots and missing keys are absence too, but they surface as PathError before a value exists — see resolve_default.)
value_length
Length of a value for ${#…}: element count for a list, key count for a record, and the byte length of the string form for any scalar (unchanged for non-collections). The single source of truth for the three ${#…} evaluation sites (sync + the two async ones).
value_to_bool
Convert a Value to its boolean representation.
value_to_exit_code
Convert a Value to its string representation for interpolation. Coerce a Value into an exit code (i64) for return/exit.
value_to_json
Convert our AST Value to serde_json::Value for serialization.
value_to_string
value_to_string_with_tilde
Convert a Value to its string representation, with tilde expansion for paths.
value_to_text_sink
Materialize a Value for a TEXT SINK — string interpolation ("x=$b") or an external-command argv element (prog $b) — going LOUD on binary rather than emitting the [binary: N bytes] placeholder that value_to_string uses.
value_to_text_sink_named
Same as value_to_text_sink, but sink names the specific boundary in the error message (e.g. “a path”, “an exported environment variable value”, “a redirect target”) instead of the generic “text” — mirroring the sink parameter structured_boundary_error already uses for the collection-vs-process-boundary guard. Every remaining text sink that used to fall back to value_to_string’s [binary: N bytes] placeholder (path-coercing builtins, env export, redirect targets, …) routes through this so the error names what the binary data was actually being used as.
values_equal
Check if two values are equal under == (string equality in [[ ]]).
values_to_text_sink_named
value_to_text_sink_named over a whole positional list — a builtin’s path operands (ls/find/grep/sed -i file lists), going loud on the first binary element rather than collecting placeholders.

Type Aliases§

EvalResult
Result type for evaluation.