Skip to content

Architecture

lash is a high-performance shell written in D, comprising approximately 194 source files and ~42,000 lines of code. It uses a client-server daemon model where a persistent backend daemon and one or more frontend terminal instances communicate over a Unix domain socket using a binary protocol.

The backend daemon (lash -d) is a persistent, single-per-user process that owns all evaluation logic: lexing, parsing, semantic analysis, execution, plugin hosting, and hook dispatch. Frontend instances (lash) are thin view layers responsible only for terminal I/O, line editing, prompt rendering, and display.

Frontends never interpret or evaluate lash code. All intelligence lives in the backend.

┌──────────┐ Unix Socket ┌──────────────────────┐
│ Frontend │ <--- binary proto --> │ Backend Daemon │
│ (view) │ ~/.lash/lashd.sock │ lexer/parser/eval/... │
└──────────┘ └──────────────────────┘
┌──────────┐ │
│ Frontend │ <──────────┘
└──────────┘

Strict downward-only dependencies. No layer may import from a layer above it.

runtime/ -> (nothing, only std library)
lang/ -> runtime/
protocol/ -> runtime/
backend/ -> lang/, protocol/, runtime/
frontend/ -> protocol/, runtime/ (NEVER imports backend/)
plugins/ -> backend/plugin/base/, protocol/, runtime/
LayerContents
runtime/Pure types (LashValue, Scope, LashError), OS abstractions
lang/Lexer, parser, evaluator, expansion, optimizer (turbo analysis/fusion/operations/heap)
protocol/Wire format (codec/encoders/decoders), transport, IPC (shm/notify), shared paths
backend/Execution (pipeline/pipe/redirects/streaming/PTY/stdin/turbo), daemon, session, config, hooks, plugins, script
frontend/Terminal input, raw mode, key reader, completion, history, line parsing, connection, rendering

Communication uses a binary protocol over the Unix domain socket at ~/.lash/lashd.sock:

[4 bytes: message length][2 bytes: message type][payload: struct bytes]

Frontend and backend share D struct definitions for zero-cost serialization.

MessageDescription
ConnectTerminal size, supported display modes
InputUser input text
CompleteCompletion request (text + cursor position)
SignalSIGINT, SIGTSTP, etc.
ResizeTerminal size change
DisconnectSession end
PasswordResponseResponse to password prompt
MessageDescription
PromptPrompt string to display
Outputstdout/stderr data with stream tag
ErrorProblem/Suggestion in requested format
CompletionsCompletion candidates
HighlightToken spans for syntax highlighting
JobStatusBackground job state changes
ExitCodeCommand exit code
EnvNotifyEnvironment variable updates
PasswordRequestPrompt frontend for password (echo disabled)
KeyHookConfigList of hooked key names (type 0x91)
KeyEventResultResult of key hook dispatch
CommandDescription
lashStart the frontend (interactive)
lash -dStart the backend daemon only

Frontend startup: Check for existing backend at ~/.lash/lashd.sock. If responsive, connect. Otherwise, auto-start the backend, wait, then connect. Send a Connect message with terminal dimensions and supported display modes. Begin the interactive session.

Backend startup: Create socket at ~/.lash/lashd.sock. Load configuration from ~/.lash/config. Load and initialize plugins from ~/.lash/plugins.conf. Accept frontend connections. Daemonize (write PID to ~/.lash/lashd.pid).

Backend shutdown: The daemon stays alive when the last frontend disconnects. Explicit shutdown via lash -d stop. On SIGTERM, clean shutdown of plugins and session data.

ModuleResponsibility
LexerStreaming tokenizer with command/expression mode switching
ParserProduces AST from token stream. Strict — fails with Problem/Suggestion errors
Semantic AnalyzerPre-execution validation (undeclared variables, type checks)
EvaluatorAST walker, scope management, control flow, loop protection
Process ManagerProcess spawning, job table, stream capture, exit codes. Sets LASH_DISPLAY and LASH_TUI_VERSION on spawned processes
Range EngineLazy composable ranges with bounded buffer (default 1024). Built-in methods are range adapters
Plugin HostExternal process plugin lifecycle

The frontend is a thin view layer:

  • Line editing (keystrokes, cursor, multi-line, undo)
  • Prompt rendering (displays prompt string from backend)
  • Tab completion UI (displays candidates from backend)
  • Syntax highlighting (colorizes using token info from backend)
  • Output display (stdout, stderr, errors)
  • Job status display
  • Signal forwarding (Ctrl+C, Ctrl+Z sent as signal messages to backend)
  • Display mode handling

Input: `cat log.txt | head -100`.json.filter(e => e["level"] == "error").take(5)

  1. Frontend sends Input message.
  2. Lexer produces tokens. Parser builds AST.
  3. Semantic analyzer validates.
  4. Process Manager spawns cat log.txt | head -100.
  5. Range Engine composes: stdout -> [buffer:1024] -> json -> filter -> take(5).
  6. Backend sends Output messages. Then Prompt and ExitCode.