This document provides a comprehensive overview of Den Shell's architecture, design decisions, and component interactions.
- Overview
- Core Architecture
- Component Overview
- Data Flow
- Execution Pipeline
- Concurrency Model
- Memory Management
- Extension Points
Den Shell is a modern, high-performance POSIX-compatible shell written in Zig. The architecture is designed for:
- Performance: Optimized for startup time, execution speed, and memory efficiency
- Modularity: Clean separation of concerns with well-defined interfaces
- Extensibility: Plugin system and hooks for customization
- Correctness: Strong typing and comprehensive testing
- Concurrency: Thread pool and parallel execution for I/O-bound operations
- Language: Zig 0.16-dev
- Build System: Zig build system
- Testing: Zig test framework with custom profiling
- Concurrency: Custom thread pool and lock-free data structures
Den Shell follows a layered architecture with clear separation between components:
┌─────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ (src/cli.zig, src/main.zig) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Shell Core │
│ (src/shell.zig) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Environment │ History │ Jobs │ Aliases │ Variables │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ REPL Layer │
│ (src/repl/) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Input │ Completion │ Highlighting │ Auto-suggest │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Parser Layer │
│ (src/parser/, src/expansion/, src/types/) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Tokenizer → Parser → AST → Expansion → Commands │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Executor Layer │
│ (src/executor/) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Builtins │ External │ Pipelines │ Redirections │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Cross-Cutting Concerns │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Plugins │ Hooks │ Scripting │ Profiling │ Logging │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Purpose: Entry point and command-line argument handling
Key Components:
main.zig: Program entry point with GPA allocator setupcli.zig: Command parsing and subcommand dispatch
Responsibilities:
- Parse command-line arguments
- Route to appropriate subcommands (shell, exec, complete, setup, etc.)
- Initialize allocator and shell instance
- Handle version and help commands
Purpose: Central state management and coordination
Key Components:
Shellstruct: Main shell state- Environment variables management
- History tracking
- Job control
- Alias management
- Positional parameters
- Thread pool for concurrency
Responsibilities:
- Initialize and manage shell state
- Coordinate between subsystems
- Execute hooks at lifecycle events
- Manage background jobs
- Track command history
Key Data Structures:
pub const Shell = struct {
allocator: std.mem.Allocator,
running: bool,
environment: std.StringHashMap([]const u8),
aliases: std.StringHashMap([]const u8),
history: [1000]?[]const u8,
job_manager: JobManager, // Centralized job control
script_manager: ScriptManager,
function_manager: FunctionManager,
plugin_registry: PluginRegistry,
plugin_manager: PluginManager,
thread_pool: concurrency.ThreadPool, // For parallel operations
// ... more fields
}Purpose: Centralized background job management
Key Components:
job_manager.zig: Job tracking, status monitoring, builtin implementationsmod.zig: Module exports
Responsibilities:
- Track background jobs (add, remove, status)
- Non-blocking job completion checks
- Implement job builtins (jobs, fg, bg, disown, wait)
- Graceful shutdown with SIGTERM/SIGKILL
Purpose: Centralized shell option management
Key Components:
SetOptions: POSIX set options (-e, -u, -x, etc.)ShoptOptions: Bash-style shopt optionsShellOptions: Combined options with accessor methods
Key Modules:
regex.zig: Simple regex matching for shell patternsconfig_watch.zig: File modification tracking for hot-reloadio.zig: Cross-platform I/O utilitiesterminal.zig: Terminal handling and line editingcompletion.zig: Tab completion engineexpansion.zig: Variable and glob expansionsignals.zig: Signal handlingplatform.zig: Platform abstraction layer for cross-platform support
Provides unified API for platform-specific operations:
- Process Management:
waitProcess,killProcess,continueProcess - Process Groups:
setProcessGroup,getProcessGroup,setForegroundProcessGroup - Terminal Detection:
isTty,getTerminalSize - Pipes:
createPipe,duplicateFd,closeFd - Environment:
getEnv,getHomeDir,getUsername,isRoot - Path Operations:
isAbsolutePath,path_separator - File Operations:
fileExists,isDirectory,isExecutable - Signal Constants: Platform-appropriate signal definitions
Purpose: Interactive command-line interface
Key Components:
editor.zig: Line editing with history navigation- Completion engine (src/completion/)
- Syntax highlighting (plugins)
- Auto-suggestions (plugins)
Responsibilities:
- Read user input with editing capabilities
- Provide completions for commands, files, variables
- Highlight syntax in real-time
- Show suggestions based on history
- Handle special keys (Ctrl+C, Ctrl+D, etc.)
Purpose: Transform input text into executable AST
Pipeline:
Input String → Tokenizer → Parser → AST → Expansion → Command Tree
Key Components:
- Breaks input into tokens
- Handles quotes, escapes, operators
- Tracks token positions for error reporting
Token Types:
- Words (commands, arguments)
- Operators (|, ||, &&, ;, &, <, >, >>)
- Special characters ($, `, )
- Keywords (if, while, for, function)
- Builds AST from tokens
- Handles operator precedence
- Validates syntax
- Supports complex constructs (pipelines, conditionals, loops)
AST Node Types:
- Command: Simple command with arguments
- Pipeline: Commands connected with pipes
- Conditional: if/elif/else statements
- Loop: for/while loops
- Function: Function definitions
- Compound: Grouped commands ({})
- Parameter expansion (
$VAR, $ {VAR}) - Command substitution ($(cmd),
cmd) - Arithmetic expansion ($((expr)))
- Glob expansion (.txt, [a-z])
- Brace expansion ({a,b,c}, {1..10})
- Tilde expansion (~, ~/dir)
Purpose: Execute parsed commands
Key Components:
- Routes commands to builtins or external processes
- Handles pipelines with proper file descriptor management
- Manages redirections (<, >, >>, 2>&1)
- Implements job control (fg, bg, jobs)
The builtins system is organized into logical modules:
- Registry (
mod.zig):BuiltinRegistryinterface for registering and executing builtins - Filesystem (
filesystem.zig):basename,dirname,realpath - Directory (
directory.zig):pushd,popd,dirs - I/O (
io.zig):printf,read - Process (
process.zig):exec,wait,kill,disown - Variables (
variables.zig):local,declare,readonly,typeset,let - Misc (
misc.zig):sleep,help,clear,uname,whoami,umask,time,caller
Core builtins include:
cd: Change directory with CDPATH supportecho: Print text with escape sequencesexport: Set environment variablesalias: Create command aliasessource: Execute script filesexit: Exit shell with status code
- Fork/exec for external commands
- Process group management
- Signal handling (SIGINT, SIGTERM, SIGCHLD)
- Background job tracking
- File descriptor manipulation
- Pipe creation and management
- Here-document implementation
- File mode handling (read, write, append)
/dev/tcp/host/portsupport for TCP connections/dev/udp/host/portsupport for UDP connections- Detailed error messages for malformed paths
- IPv4 and IPv6 address validation
CommandMemoryPool: Arena allocator for command executionPipelineMemoryPool: Arena allocator for pipeline managementExpansionMemoryPool: Arena allocator for variable expansion- Reduces allocation overhead during execution
Purpose: Extensibility and customization
Key Components:
pub const Plugin = struct {
name: []const u8,
version: []const u8,
init: *const fn (*PluginContext) anyerror!void,
deinit: *const fn (*PluginContext) void,
hooks: []const HookRegistration,
};
pub const HookType = enum {
shell_init,
shell_exit,
pre_command,
post_command,
prompt,
// ... more hooks
};- Load plugins from directories
- Manage plugin lifecycle
- Handle plugin dependencies
- Provide isolation between plugins
- AutoSuggest: History-based suggestions
- Highlight: Syntax highlighting
- ScriptSuggester: Script completion
Hook Points:
shell_init: After shell initializationshell_exit: Before shell cleanuppre_command: Before command executionpost_command: After command executionprompt: Generate custom promptscompletion: Custom completions
Purpose: Script execution and function management
Key Components:
- Load and execute script files
- Track script state and variables
- Handle script errors
- Define shell functions
- Store function bodies
- Execute functions with arguments
- Handle function-local variables
Purpose: Load and manage shell configuration
Key Components:
- Multi-source configuration loading
- JSONC parsing with comment support
- Configuration validation
Configuration Search Order:
- Custom path (via
--configflag) ./den.jsonc./package.jsonc(extracts "den" key)./config/den.jsonc./.config/den.jsonc~/.config/den.jsonc~/package.jsonc(extracts "den" key)
Key Types:
pub const ConfigSource = struct {
path: ?[]const u8,
source_type: SourceType,
pub const SourceType = enum {
default,
den_jsonc,
package_jsonc,
custom_path,
};
};
pub const ConfigLoadResult = struct {
config: DenConfig,
source: ConfigSource,
};Features:
- Hot Reload: Set
hot_reload: truein config for automatic reload - Validation: Comprehensive validation with warnings and errors
- package.jsonc Support: Embed Den config in package.jsonc under "den" key
pub const DenConfig = struct {
verbose: bool = false,
stream_output: ?bool = null,
hot_reload: bool = false, // Auto-reload on file change
prompt: PromptConfig = .{},
history: HistoryConfig = .{},
completion: CompletionConfig = .{},
theme: ThemeConfig = .{},
expansion: ExpansionConfig = .{},
aliases: AliasConfig = .{},
keybindings: KeybindingConfig = .{},
environment: EnvironmentConfig = .{},
};Purpose: Shared functionality and helpers
Key Modules:
io.zig: I/O utilities, terminal handlingpath.zig: Path manipulation and resolutioncompletion.zig: Completion engineglob.zig: Glob pattern matchingbrace.zig: Brace expansionexpansion.zig: Variable/command expansionconcurrency.zig: Thread pool, atomic structuresparallel_discovery.zig: Parallel file operations
User Input
│
▼
┌───────────────┐
│ REPL/Editor │ Read line with editing
└───────┬───────┘
│
▼
┌───────────────┐
│ Tokenizer │ Break into tokens
└───────┬───────┘
│
▼
┌───────────────┐
│ Parser │ Build AST
└───────┬───────┘
│
▼
┌───────────────┐
│ Expansion │ Expand variables, globs, etc.
└───────┬───────┘
│
▼
┌───────────────┐
│ Executor │ Execute commands
└───────┬───────┘
│
├─▶ Builtin Command
│ │
│ ▼
│ Execute internally
│
└─▶ External Command
│
▼
Fork & Exec
│
▼
Wait for completion
│
▼
Return exit code
Shell Event (e.g., pre_command)
│
▼
┌────────────────┐
│ Plugin Registry│ Find registered hooks
└────────┬───────┘
│
▼
┌────────────────┐
│ Hook Context │ Build context with event data
└────────┬───────┘
│
▼
┌─────────────┐
│ For each │
│ registered │
│ plugin │
└──────┬──────┘
│
▼
┌──────────────┐
│ Call plugin │
│ hook handler │
└──────┬───────┘
│
▼
┌──────────────┐
│ Collect │
│ results │
└──────┬───────┘
│
▼
Return to caller
ls -la /tmp- Tokenize:
[ls] [-la] [/tmp] - Parse:
Command{name: "ls", args: ["-la", "/tmp"]} - Expand: No expansion needed
- Execute:
- Check if builtin → No
- Fork process
- Exec
/usr/bin/lswith args - Wait for completion
cat file.txt | grep "pattern" | wc -l- Tokenize:
[cat] [file.txt] [|] [grep] ["pattern"] [|] [wc] [-l] - Parse:
Pipeline{[Command(cat), Command(grep), Command(wc)]} - Execute Pipeline:
- Create pipe1: cat → grep - Create pipe2: grep → wc - Fork cat: stdout → pipe1 - Fork grep: stdin ← pipe1, stdout → pipe2 - Fork wc: stdin ← pipe2 - Close all pipe ends in parent - Wait for all processes
echo "Hello $USER, your home is $HOME"-
Tokenize:
[echo] ["Hello $USER, your home is $HOME"] -
Parse:
Command{name: "echo", args: ["Hello $USER, your home is $HOME"]} -
Expand:
- Find
$USER→ Replace with environment value - Find
$HOME→ Replace with environment value - Result:
"Hello john, your home is /home/john"
- Find
- Execute: Builtin echo prints the expanded string
Den Shell implements a hybrid concurrency model:
Shell Initialization
│
▼
Create ThreadPool (auto CPU count)
│
├─▶ Worker Thread 1
├─▶ Worker Thread 2
├─▶ Worker Thread 3
└─▶ Worker Thread N
│
▼
Job Queue (mutex-protected)
│
▼
Wait on condition variable
│
▼
Execute jobs as submitted
Plugin Discovery:
- Scan multiple directories in parallel
- Each directory assigned to worker thread
- Results collected with mutex protection
File Globbing (potential):
- Parallel directory traversal
- Concurrent pattern matching
- Merge results
Completion (potential):
- Query multiple sources concurrently
- Merge completion results
- Return sorted/deduplicated list
- ThreadPool: Work queue with condition variables
- AtomicCounter: Lock-free metrics
- SPSCQueue: Lock-free producer-consumer
- RWLock: Reader-writer locks for read-heavy data
- ConcurrentHashMap: Sharded hash map (16 shards)
Den Shell uses a General Purpose Allocator (GPA) at the top level with careful lifetime management:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var shell = try Shell.init(allocator);
defer shell.deinit();
try shell.run();
}- Shell owns: Environment, aliases, history, jobs
- Parser owns: AST nodes (temporary, freed after execution)
- Executor owns: Process handles, file descriptors
- Plugins own: Internal state (cleaned up in deinit)
-
Shell Level: Long-lived (entire session)
- Environment variables
- History
- Aliases
- Background jobs
-
Command Level: Medium-lived (single command)
- AST nodes
- Expanded arguments
- Temporary files
-
Execution Level: Short-lived (during execution)
- Pipe buffers
- Process state
- I/O buffers
See MEMORY_OPTIMIZATION.md for details:
- Arena allocators for parser
- String interning for common values
- Fixed-size arrays for bounded collections
- Lazy initialization of optional features
Add new builtins by implementing in src/builtins/:
pub fn myBuiltin(
shell: *Shell,
args: []const []const u8,
stdout: anytype,
) !i32 {
// Implementation
return 0;
}Register in src/builtins/mod.zig.
Create plugins by implementing the Plugin interface:
pub const MyPlugin = struct {
pub fn init(ctx: *PluginContext) !void {
// Initialize plugin
}
pub fn deinit(ctx: *PluginContext) void {
// Cleanup
}
pub const hooks = [_]HookRegistration{
.{ .hook_type = .pre_command, .handler = preCommand },
};
fn preCommand(ctx: *HookContext) !void {
// Handle pre-command event
}
};Add completion sources in src/completion/:
pub fn completeMyThing(
prefix: []const u8,
allocator: std.mem.Allocator,
) ![][]const u8 {
// Return completions
}Add expansion rules in src/expansion/:
pub fn expandMyPattern(
input: []const u8,
allocator: std.mem.Allocator,
env: *std.StringHashMap([]const u8),
) ![][]const u8 {
// Expand pattern
}- Clear boundaries between components
- Single responsibility per module
- Minimal coupling
- Early validation
- Descriptive error messages
- Proper error propagation
- Lazy initialization
- Minimize allocations
- Cache where beneficial
- Parallel operations where possible
- POSIX compliance for core features
- Bash-like syntax for familiarity
- Cross-platform (Linux, macOS)
- Pure functions where possible
- Dependency injection
- Comprehensive test coverage
- Async I/O: Non-blocking file operations
- JIT Compilation: Compile frequently-used scripts
- Distributed Execution: Remote command execution
- Advanced Caching: Cache parsed scripts, completions
- Live Reload: Hot-reload plugins and configuration
- Memory: Fixed-size history/job arrays can be made dynamic
- Concurrency: Thread pool size auto-adjusts to CPU count
- Plugins: Isolated address spaces for safety
- Performance: Profiling infrastructure for continuous optimization
- Data Structures - Detailed structure documentation
- Algorithms - Algorithm implementations
- API Reference - Public API documentation
- Contributing - How to contribute