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.
- Exec
Result - The result of executing a command or pipeline.
- Heredoc
Assembler - Assembles a heredoc body part-by-part, applying POSIX
<<-leading-tab stripping to the source rather than to the materialized result. - Output
Data - Structured output data from a command.
- Output
Node - A node in the output tree.
- Scope
- Variable scope with nested frames and last-result tracking.
Enums§
- Control
Flow - Control flow signal from statement execution.
- Entry
Type - Entry type for rendering hints (colors, icons).
- Eval
Error - Errors that can occur during expression evaluation.
- Output
Format - Output serialization format, requested via global flags.
- Output
Payload - A command’s stdout payload: text, or raw bytes.
- Path
Error - 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
valueis a first-class collection (list/record) rather than a scalar.Value::Jsonalso carries JSON scalars (numbers/strings/bool/null unwrapped at the value boundary — seejson_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 viavalue_to_num. Shared verbatim with thetestbuiltin sotest’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 inwhile [[ ${#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 viavalues_equal;in/not inare the one operator family that legitimately takes a collection operand and must never call this.Nonefor scalars. Shared by the synceval_test(interpreter/eval.rs) and the asynceval_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
<<-EOFheredoc 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 aValue::String(rendering compact JSON) before reaching either sink, so only a live, un-interpolatedValue::Json(Array|Object)— a bare$c— trips this.sinknames the boundary in the message (e.g. “a command argument”, “a redirect target”).Nonefor 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
Noneif every exported value is a scalar. Both external-spawn sites (kernel.rs,dispatch.rs) call this beforecmd.env. - value_
defaults_ on_ emptiness - Decision A:
${path:-default}yields the default on absence or emptiness, never on falsy values. Fires for a JSONnulland an empty string; NOT forfalse,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 asPathErrorbefore a value exists — seeresolve_default.) - value_
length - Length of a value for
${#…}: element count for a list, key count for a record, and the CHARACTER count (Unicode scalar values) of the string form for any scalar (unchanged for non-collections). The single source of truth for every${#…}evaluation site — the two sync ones, the two async ones inkernel.rs, and the two reduced-sync ones inscheduler/pipeline.rs— all of which reach it throughresolve_length. - 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
Valuefor 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 thatvalue_to_stringuses. - value_
to_ text_ sink_ named - Same as
value_to_text_sink, butsinknames 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 thesinkparameterstructured_boundary_erroralready uses for the collection-vs-process-boundary guard. Every remaining text sink that used to fall back tovalue_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_namedover a whole positional list — a builtin’s path operands (ls/find/grep/sed -ifile lists), going loud on the first binary element rather than collecting placeholders.
Type Aliases§
- Eval
Result - Result type for evaluation.