Skip to main content

zsh/
lib.rs

1//! Zsh interpreter and parser in Rust
2//!
3//! This crate provides:
4//! - A complete zsh lexer (`lexer` module)
5//! - A zsh parser (`parser` module)  
6//! - Shell execution engine (`exec` module)
7//! - Job control (`jobs` module)
8//! - History management (`history` module)
9//! - ZLE (Zsh Line Editor) support (`zle` module)
10//! - ZWC (compiled zsh) support (`zwc` module)
11//! - Fish-style features (`fish_features` module)
12//! - Mathematical expression evaluation (`math` module)
13
14// Many doc comments reference C-source pages, shell constructs, and
15// zsh-internal identifiers by name in `[...]` form; they don't resolve as
16// rustdoc intra-doc links. Silence so docs build clean on CI.
17#![allow(rustdoc::broken_intra_doc_links)]
18#![allow(rustdoc::private_intra_doc_links)]
19#![allow(rustdoc::invalid_html_tags)]
20#![allow(dead_code)]
21#![allow(unused_variables)]
22#![allow(unused_imports)]
23#![allow(unused_assignments)]
24#![allow(unused_mut)]
25#![allow(unused_parens)]
26#![allow(unused_doc_comments)]
27#![allow(unreachable_patterns)]
28#![allow(deprecated)]
29#![allow(unexpected_cfgs)]
30// Allow zsh-canonical identifier names (lowercase statics/constants/types
31// like `ca_parsed`, `convchar_t`, `P_ISBRANCH`) so the ports stay
32// faithful to the C source per PORT.md.
33#![allow(non_snake_case)]
34#![allow(non_camel_case_types)]
35#![allow(non_upper_case_globals)]
36// Function-pointer-to-integer casts appear in ported dispatch tables.
37#![allow(function_casts_as_integer)]
38// Clippy: the C → Rust ports preserve idioms from the zsh source
39// (raw pointer derefs, dead-loop `do { ... } while (0)` shapes, bitmasks
40// that look redundant but match the C, etc.). Silence the whole group so
41// port fidelity wins over Rust-idiom rewrites. New non-ported code
42// should still aim for clippy-clean, but at file/function scope, not
43// crate-wide.
44#![allow(clippy::all)]
45
46/// Runtime shell-mode flag set by the binary entrypoint (`bins/zshrs.rs`)
47/// at startup. The library can't directly read `bins/zshrs.rs::shell_mode()`
48/// (it lives in the binary crate), so the binary writes this atomic when
49/// parsing `--zsh` / `--bash` / `--posix` and the library reads it from
50/// bridge / dispatch sites that need to gate bash-compat-vs-zsh behavior.
51/// Defaults to `false` (zshrs-native mode) when not explicitly set.
52///
53/// Bugs #475 / #504 / #555 in docs/BUGS.md — bash-only builtins
54/// (`caller`/`help`/`mapfile`/`readarray`/`compgen`/etc.) should
55/// dispatch as "command not found" when this is true.
56pub static IS_ZSH_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
57
58/// `compsys` submodule.
59pub mod compsys;
60/// `exec_jobs` submodule.
61pub mod exec_jobs;
62/// `extensions` submodule.
63pub mod extensions;
64/// `ported` submodule.
65pub mod ported;
66/// `pattern_data_escape` submodule (Rust-only; see the module docs).
67pub mod pattern_data_escape;
68/// `subscript_escape` submodule (Rust-only; see the module docs).
69pub mod subscript_escape;
70/// `test_util` submodule.
71#[cfg(test)]
72pub mod test_util;
73/// `tiers` submodule.
74pub mod tiers;
75pub mod tolerant_sort;
76
77// Back-compat: re-export every ported submodule at the crate root so
78// historical call sites (`crate::exec::`, `crate::subst::`,
79// `crate::zle::`, `crate::modules::`, `crate::builtins::`, etc.)
80// continue to resolve unchanged after the physical move into
81// `src/ported/`. New code should prefer `crate::ported::<name>`.
82pub use ported::*;
83/// `alias_input_frames` submodule — per-thread record of popped alias
84/// input-stack frames, restoring the reachability C's manually-indexed
85/// `instack` gives `input_hasalias`.
86#[path = "extensions/alias_input_frames.rs"]
87pub mod alias_input_frames;
88/// `aot` submodule.
89#[path = "extensions/aot.rs"]
90pub mod aot;
91/// `arith_compiler` submodule.
92#[path = "extensions/arith_compiler.rs"]
93pub mod arith_compiler;
94/// `atomic_write` submodule — the shared temp-file-safe shard writer
95/// used by both rkyv caches.
96#[path = "extensions/atomic_write.rs"]
97pub mod atomic_write;
98/// `autoload_cache` submodule.
99#[path = "extensions/autoload_cache.rs"]
100pub mod autoload_cache;
101/// `autoload_prewarm` submodule.
102#[path = "extensions/autoload_prewarm.rs"]
103pub mod autoload_prewarm;
104/// `bash_complete` submodule.
105#[path = "extensions/bash_complete.rs"]
106pub mod bash_complete;
107/// `canonical_apply` submodule.
108#[path = "extensions/canonical_apply.rs"]
109pub mod canonical_apply;
110/// Shared-handle accessors for the completion match accumulators (Rust-original
111/// glue restoring C's `matches = mgroup->lmatches` pointer alias; see the module
112/// doc). Deliberately outside `src/ported/` — not a C-fn port.
113pub mod comp_match_handles;
114pub mod comp_word_tok;
115/// `compile_zsh` submodule.
116#[path = "extensions/compile_zsh.rs"]
117pub mod compile_zsh;
118/// `completion` submodule.
119#[path = "extensions/completion.rs"]
120pub mod completion;
121/// `config` submodule.
122#[path = "extensions/config.rs"]
123pub mod config;
124/// `cow_map` submodule — copy-on-write HashMap wrapper for cheap subshell
125/// snapshot/restore (Rust-only helper).
126#[path = "extensions/cow_map.rs"]
127pub mod cow_map;
128/// `daemon_presence` submodule.
129#[path = "extensions/daemon_presence.rs"]
130pub mod daemon_presence;
131/// `errflag_cell` submodule — per-thread storage for `errflag`, restoring
132/// the copy-on-fork semantics C zsh gets for free.
133#[path = "extensions/errflag_cell.rs"]
134pub mod errflag_cell;
135/// `fast_hash` submodule — dependency-free FxHash for internal name tables.
136#[path = "extensions/fast_hash.rs"]
137pub mod fast_hash;
138/// `opts_cache` submodule — fast-path `isset()` option-state cache.
139#[path = "extensions/opts_cache.rs"]
140pub mod opts_cache;
141/// `overlay_snapshot` submodule.
142#[path = "extensions/overlay_snapshot.rs"]
143pub mod overlay_snapshot;
144/// `pat_cache` submodule — global compiled-pattern cache (Rust-only opt).
145#[path = "extensions/pat_cache.rs"]
146pub mod pat_cache;
147/// `provenance` submodule — value-lineage ledger over bytecode
148/// execution (zshrs-original; ported from stryke's `provenance.rs`).
149#[path = "extensions/provenance.rs"]
150pub mod provenance;
151/// `script_cache` submodule.
152#[path = "extensions/script_cache.rs"]
153pub mod script_cache;
154/// `shout` submodule — buffered terminal-output stream for the ZLE display
155/// (the stdio buffering C gets from libc's `FILE *shout`).
156#[path = "extensions/shout.rs"]
157pub mod shout;
158/// `startup_signals` submodule.
159#[path = "extensions/startup_signals.rs"]
160pub mod startup_signals;
161/// `subexp_cleanup` submodule — RAII eviction of `__subexp_arr_*`
162/// paramtab scratch temps created during array sub-expression expansion.
163#[path = "extensions/subexp_cleanup.rs"]
164pub mod subexp_cleanup;
165/// `vm_pool` submodule — per-thread pool of recyclable fusevm VMs.
166#[path = "extensions/vm_pool.rs"]
167pub mod vm_pool;
168// Daemon lives in the `zshrs-daemon` workspace crate. Re-export it as `daemon`
169// so existing `crate::daemon::...` (in vm_helper) and `zsh::daemon::...` (in bins,
170// integration tests) paths keep resolving without churn.
171//
172// The `daemon` feature gates the actual zshrs-daemon dep. When disabled
173// (--no-default-features), a stub module covers the call sites in vm_helper.
174// This lets the library compile in isolation while the daemon crate is
175// being refactored in a concurrent session.
176#[cfg(feature = "daemon")]
177pub use zshrs_daemon as daemon;
178/// `daemon` submodule.
179#[cfg(not(feature = "daemon"))]
180pub mod daemon {
181    //! Stub module used when the `daemon` feature is disabled. Provides
182    //! the minimal surface that `src/vm_helper` calls — the real
183    //! implementation lives in the `zshrs-daemon` workspace crate.
184    pub mod builtins {
185        pub const ZSHRS_BUILTIN_NAMES: &[&str] = &[];
186        /// `is_zshrs_builtin` — see implementation.
187        pub fn is_zshrs_builtin(_name: &str) -> bool {
188            false
189        }
190        /// `try_dispatch` — see implementation.
191        pub fn try_dispatch(_name: &str, _argv: &[String]) -> Option<i32> {
192            None
193        }
194        /// `dispatch` — see implementation.
195        pub fn dispatch(_name: &str, _args: &[String]) -> Option<i32> {
196            None
197        }
198    }
199}
200/// `ast_sexp` submodule.
201#[path = "extensions/ast_sexp.rs"]
202pub mod ast_sexp;
203/// `bash_arrays` submodule — bash sparse-array holes tracker (Rust-only).
204#[path = "extensions/bash_arrays.rs"]
205pub mod bash_arrays;
206/// `dap` submodule.
207#[path = "extensions/dap.rs"]
208pub mod dap;
209/// `dash_mode` submodule — strict-dash emulation flag (Rust-only).
210#[path = "extensions/dash_mode.rs"]
211pub mod dash_mode;
212/// `dumpers` submodule.
213#[path = "extensions/dumpers.rs"]
214pub mod dumpers;
215/// `ext_builtins` submodule.
216#[path = "extensions/ext_builtins.rs"]
217pub mod ext_builtins;
218/// `fds` submodule.
219#[path = "extensions/fds.rs"]
220pub mod fds;
221/// `fish_features` submodule.
222#[path = "extensions/fish_features.rs"]
223pub mod fish_features;
224/// `fmt` submodule — zsh source formatter (CLI `--fmt` + LSP
225/// `textDocument/formatting`).
226#[path = "extensions/fmt.rs"]
227pub mod fmt;
228/// `ftime` submodule — TEMPORARY per-function timing scaffold (Rust-only).
229#[path = "extensions/ftime.rs"]
230pub mod ftime;
231/// `func_body_fmt` submodule.
232#[path = "extensions/func_body_fmt.rs"]
233pub mod func_body_fmt;
234/// `funcdef_capture` submodule — Rust-only verbatim capture of function
235/// body source text as `hgetc` consumes it, so `functions` / `typeset -f`
236/// work for functions defined interactively or on stdin (where zshrs's
237/// `LEX_INPUT` window does not exist). No C counterpart: C zsh
238/// reconstructs the text from wordcode via `getpermtext` (Src/text.c:189).
239#[path = "extensions/funcdef_capture.rs"]
240pub mod funcdef_capture;
241/// `global_rc` submodule — runtime sysconfdir resolution for the
242/// system-wide startup files (Rust-only; zsh bakes the path in at build
243/// time).
244#[path = "extensions/global_rc.rs"]
245pub mod global_rc;
246/// `lsp` submodule.
247#[path = "extensions/lsp.rs"]
248pub mod lsp;
249/// `lsp_symbols` submodule.
250#[path = "extensions/lsp_symbols.rs"]
251pub mod lsp_symbols;
252/// `native_cmds` submodule — builtins contributed by the linking binary
253/// (the fat `zshrs-native` build registers `git` / `arb` / `stryke` here).
254#[path = "extensions/native_cmds.rs"]
255pub mod native_cmds;
256// Lexer + parser live in `src/ported/lex.rs` and `src/ported/parse.rs`.
257// Re-export the modules so existing call sites (`zsh::lex::…`,
258// `zsh::parse::…`, `zsh::tokens::…`) keep resolving.
259// `tokens` aliases `lex` because tokens.rs's contents (lextok enum +
260// reserved-word table) now live inside lex.rs. Char tokens (Pound / Inpar /
261// Equals / …) and the REDIR_* / COND_* constants are not duplicated — they
262// live as flat `pub const` items in `ported::zsh_h` per `Src/zsh.h:144-679`.
263pub use ported::lex;
264pub use ported::lex as tokens;
265pub use ported::parse;
266/// `heredoc_ast` submodule.
267#[path = "extensions/heredoc_ast.rs"]
268pub mod heredoc_ast;
269/// `history` submodule.
270#[path = "extensions/history.rs"]
271pub mod history;
272/// `history_lazy` submodule — on-demand HISTFILE paging; the history
273/// is never slurped whole.
274#[path = "extensions/history_lazy.rs"]
275pub mod history_lazy;
276/// `log` submodule.
277#[path = "extensions/log.rs"]
278pub mod log;
279/// `lowfd` submodule — keeps the shell's own descriptors out of the user's fd space.
280#[path = "extensions/lowfd.rs"]
281pub mod lowfd;
282/// `zsh_ast` submodule.
283#[path = "extensions/zsh_ast.rs"]
284pub mod zsh_ast;
285// Backwards-compat flat re-exports — call sites that still write
286// `crate::datetime::…`, `crate::stat::…`, etc. resolve to the
287// `crate::modules::<modname>` ports without churn. New code should
288// reach for `crate::modules::<modname>` directly.
289pub use builtins::sched;
290pub use modules::attr;
291pub use modules::cap;
292pub use modules::clone;
293pub use modules::curses;
294pub use modules::datetime;
295pub use modules::db_gdbm;
296pub use modules::example;
297pub use modules::files;
298pub use modules::hlgroup;
299pub use modules::ksh93;
300pub use modules::langinfo;
301pub use modules::mapfile;
302pub use modules::mathfunc;
303pub use modules::nearcolor;
304pub use modules::newuser;
305pub use modules::param_private;
306pub use modules::parameter;
307pub use modules::pcre;
308pub use modules::random;
309pub use modules::random_real;
310pub use modules::regex as regex_module;
311pub use modules::socket;
312pub use modules::stat;
313pub use modules::system;
314pub use modules::tcp;
315pub use modules::termcap;
316pub use modules::terminfo;
317pub use modules::watch;
318pub use modules::zftp;
319pub use modules::zprof;
320pub use modules::zpty;
321pub use modules::zselect;
322pub use modules::zutil;
323/// `compinit_bg` submodule.
324#[path = "extensions/compinit_bg.rs"]
325pub mod compinit_bg;
326/// `fusevm_bridge` submodule.
327pub mod fusevm_bridge;
328/// `fusevm_disasm` submodule.
329pub mod fusevm_disasm;
330/// `intercepts` submodule.
331#[path = "extensions/intercepts.rs"]
332pub mod intercepts;
333/// `p10k` submodule — native powerlevel10k prompt engine.
334#[path = "extensions/p10k/mod.rs"]
335pub mod p10k;
336/// `pkg` — the `zpm` plugin package manager (global store).
337#[path = "extensions/pkg/mod.rs"]
338pub mod pkg;
339/// `plugin_cache` submodule.
340#[path = "extensions/plugin_cache.rs"]
341pub mod plugin_cache;
342/// `plugin_host` submodule — native (Rust) plugin loader (`zmodload -R`).
343#[path = "extensions/plugin_host.rs"]
344pub mod plugin_host;
345/// `recorder_ext` submodule.
346#[path = "extensions/recorder.rs"]
347pub mod recorder_ext;
348/// `rust_ffi` submodule — inline `rust { ... }` FFI desugaring.
349pub mod rust_ffi;
350// Plugin-Framework-Agnostic State-Modification Recorder. Entire module
351// is `#![cfg(feature = "recorder")]` so it disappears from the default
352// `zshrs` build at the rustc-expansion stage. See docs/RECORDER.md.
353/// `async_precmd` submodule — run precmd-style hooks on the worker pool so they
354/// don't block prompt rendering (writes into the shared param table).
355#[path = "extensions/async_precmd.rs"]
356pub mod async_precmd;
357/// `autopair` submodule — native bracket/quote auto-pairing
358/// (port of hlissner/zsh-autopair).
359#[path = "extensions/autopair.rs"]
360pub mod autopair;
361/// `autosuggest` submodule — native fish-style autosuggestions
362/// (port of the reader.rs autosuggestion state machine).
363#[path = "extensions/autosuggest.rs"]
364pub mod autosuggest;
365/// `gen_docs` submodule.
366#[path = "extensions/gen_docs.rs"]
367pub mod gen_docs;
368/// `history_search` submodule — native up-arrow prefix/substring/token history
369/// search (port of fish reader/history_search.rs).
370#[path = "extensions/history_search.rs"]
371pub mod history_search;
372/// `recorder` submodule.
373#[cfg(feature = "recorder")]
374pub mod recorder;
375/// `regex_mod` submodule.
376#[path = "extensions/regex_mod.rs"]
377pub mod regex_mod;
378/// `stringsort` submodule.
379#[path = "extensions/stringsort.rs"]
380pub mod stringsort;
381/// `terminfo_caps` submodule — the frozen terminfo capability-name tables
382/// that replace ncurses' exported `boolnames`/`numnames`/`strnames` arrays.
383#[path = "extensions/terminfo_caps.rs"]
384pub mod terminfo_caps;
385/// `terminfo_db` submodule — pure-Rust reader for the compiled terminfo
386/// database, replacing `setupterm`/`tigetstr`/`tgetent`/… from libtinfo.
387#[path = "extensions/terminfo_db.rs"]
388pub mod terminfo_db;
389/// `tparm` submodule — the terminfo parameterized-string evaluator plus
390/// `tgoto` and `tputs` padding, replacing the last libtinfo entry points.
391#[path = "extensions/tparm.rs"]
392pub mod tparm;
393/// `syntax_highlight` submodule — native command-line syntax highlighting
394/// (port of fish highlight/highlight.rs, driven by the zshrs lexer).
395#[path = "extensions/syntax_highlight.rs"]
396pub mod syntax_highlight;
397/// `worker` submodule.
398#[path = "extensions/worker.rs"]
399pub mod worker;
400/// `zle_file_tester` submodule — file-existence/permission tests for native ZLE
401/// syntax highlighting (port of fish highlight/file_tester.rs).
402#[path = "extensions/zle_file_tester.rs"]
403pub mod zle_file_tester;
404/// `zle_fx` submodule — wiring for the native ZLE effects (autosuggest,
405/// syntax highlight, history search, autopair) into zlecore + the renderer.
406#[path = "extensions/zle_fx.rs"]
407pub mod zle_fx;
408/// `zle_param_sync` submodule — ZLE special-param write-back sync
409/// (Rust-only adapter for C's live GSU setters).
410#[path = "extensions/zle_param_sync.rs"]
411pub mod zle_param_sync;
412/// `zsh_builtin_docs` submodule.
413#[path = "extensions/zsh_builtin_docs.rs"]
414pub mod zsh_builtin_docs;
415/// `zsh_ext_builtin_docs` submodule.
416#[path = "extensions/zsh_ext_builtin_docs.rs"]
417pub mod zsh_ext_builtin_docs;
418/// `zsh_keyword_docs` submodule.
419#[path = "extensions/zsh_keyword_docs.rs"]
420pub mod zsh_keyword_docs;
421/// `zsh_option_docs` submodule.
422#[path = "extensions/zsh_option_docs.rs"]
423pub mod zsh_option_docs;
424/// `zsh_special_var_docs` submodule.
425#[path = "extensions/zsh_special_var_docs.rs"]
426pub mod zsh_special_var_docs;
427/// `ztest` submodule — shell-level unit test framework
428/// (port of `../strykelang` test framework).
429#[path = "extensions/ztest.rs"]
430pub mod ztest;
431/// `zwc` submodule.
432#[path = "extensions/zwc.rs"]
433pub mod zwc;
434/// `zwc_decode` submodule.
435#[path = "extensions/zwc_decode.rs"]
436pub mod zwc_decode;
437// Backwards-compat re-export so `crate::rlimits::…` keeps resolving.
438pub use builtins::rlimits;
439
440// Top-level shell executor state + fusevm bridge glue. Not a port of
441// any single Src/*.c file — zsh's native wordcode VM lives in `Src/exec.c`;
442// zshrs runs fusevm instead (see src/fusevm_bridge.rs).
443/// `vm_helper` submodule.
444pub mod vm_helper;
445
446pub use fish_features::{
447    autosuggest_from_history,
448    colorize_line,
449    expand_abbreviation,
450    // Syntax highlighting
451    highlight_shell,
452    // Private mode
453    is_private_mode,
454    // Killring
455    kill_add,
456    kill_replace,
457    kill_yank,
458    kill_yank_rotate,
459    set_private_mode,
460    validate_autosuggestion,
461    // Validation
462    validate_command,
463    with_abbrs,
464    with_abbrs_mut,
465    AbbrPosition,
466    // Abbreviations
467    Abbreviation,
468    AbbreviationSet,
469    // Autosuggestions
470    Autosuggestion,
471    HighlightRole,
472    HighlightSpec,
473    KillRing,
474    ValidationStatus,
475};
476pub use tokens::lextok;
477pub use vm_helper::ShellExecutor;
478
479// ── Stryke integration hook ──
480// The fat binary registers a handler for @ prefix dispatch.
481// The thin binary leaves this as None — @ is treated as a normal character.
482
483use std::sync::OnceLock;
484
485type StrykeHandler = Box<dyn Fn(&str) -> i32 + Send + Sync>;
486static STRYKE_HANDLER: OnceLock<StrykeHandler> = OnceLock::new();
487
488/// Register a handler for @ prefix lines (fat binary sets this to stryke::run).
489pub fn set_stryke_handler<F>(f: F)
490where
491    F: Fn(&str) -> i32 + Send + Sync + 'static,
492{
493    let _ = STRYKE_HANDLER.set(Box::new(f));
494}
495
496/// Try to dispatch a line starting with @ to stryke.
497/// Returns Some(exit_code) if handled, None if no handler registered.
498pub fn try_stryke_dispatch(code: &str) -> Option<i32> {
499    STRYKE_HANDLER.get().map(|f| f(code))
500}
501
502/// Register a native command contributed by the linking binary.
503///
504/// Convenience re-spelling of [`native_cmds::register`] at the crate root, so
505/// a fat binary's `main` reads as one call per runtime:
506///
507/// ```ignore
508/// zsh::register_native_command("git", |argv| zvcs::run_argv(argv));
509/// ```
510///
511/// The name then dispatches in-process — `whence -w git` says `builtin`,
512/// `${+builtins[git]}` is 1, `builtin git` reaches it, a user `git()` function
513/// still shadows it, and `command git` still runs the one on `PATH`.
514pub fn register_native_command<F>(name: &str, f: F)
515where
516    F: Fn(&[String]) -> i32 + Send + Sync + 'static,
517{
518    native_cmds::register(name, f);
519}