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/// `test_util` submodule.
67#[cfg(test)]
68pub mod test_util;
69pub mod tolerant_sort;
70
71// Back-compat: re-export every ported submodule at the crate root so
72// historical call sites (`crate::exec::`, `crate::subst::`,
73// `crate::zle::`, `crate::modules::`, `crate::builtins::`, etc.)
74// continue to resolve unchanged after the physical move into
75// `src/ported/`. New code should prefer `crate::ported::<name>`.
76pub use ported::*;
77/// `aot` submodule.
78#[path = "extensions/aot.rs"]
79pub mod aot;
80/// `arith_compiler` submodule.
81#[path = "extensions/arith_compiler.rs"]
82pub mod arith_compiler;
83/// `autoload_cache` submodule.
84#[path = "extensions/autoload_cache.rs"]
85pub mod autoload_cache;
86/// `bash_complete` submodule.
87#[path = "extensions/bash_complete.rs"]
88pub mod bash_complete;
89/// `canonical_apply` submodule.
90#[path = "extensions/canonical_apply.rs"]
91pub mod canonical_apply;
92/// `compile_zsh` submodule.
93#[path = "extensions/compile_zsh.rs"]
94pub mod compile_zsh;
95/// `completion` submodule.
96#[path = "extensions/completion.rs"]
97pub mod completion;
98/// `config` submodule.
99#[path = "extensions/config.rs"]
100pub mod config;
101/// `daemon_presence` submodule.
102#[path = "extensions/daemon_presence.rs"]
103pub mod daemon_presence;
104/// `overlay_snapshot` submodule.
105#[path = "extensions/overlay_snapshot.rs"]
106pub mod overlay_snapshot;
107/// `fast_hash` submodule — dependency-free FxHash for internal name tables.
108#[path = "extensions/fast_hash.rs"]
109pub mod fast_hash;
110/// `opts_cache` submodule — fast-path `isset()` option-state cache.
111#[path = "extensions/opts_cache.rs"]
112pub mod opts_cache;
113/// `pat_cache` submodule — global compiled-pattern cache (Rust-only opt).
114#[path = "extensions/pat_cache.rs"]
115pub mod pat_cache;
116/// `vm_pool` submodule — per-thread pool of recyclable fusevm VMs.
117#[path = "extensions/vm_pool.rs"]
118pub mod vm_pool;
119/// `subexp_cleanup` submodule — RAII eviction of `__subexp_arr_*`
120/// paramtab scratch temps created during array sub-expression expansion.
121#[path = "extensions/subexp_cleanup.rs"]
122pub mod subexp_cleanup;
123/// `script_cache` submodule.
124#[path = "extensions/script_cache.rs"]
125pub mod script_cache;
126// Daemon lives in the `zshrs-daemon` workspace crate. Re-export it as `daemon`
127// so existing `crate::daemon::...` (in vm_helper) and `zsh::daemon::...` (in bins,
128// integration tests) paths keep resolving without churn.
129//
130// The `daemon` feature gates the actual zshrs-daemon dep. When disabled
131// (--no-default-features), a stub module covers the call sites in vm_helper.
132// This lets the library compile in isolation while the daemon crate is
133// being refactored in a concurrent session.
134#[cfg(feature = "daemon")]
135pub use zshrs_daemon as daemon;
136/// `daemon` submodule.
137#[cfg(not(feature = "daemon"))]
138pub mod daemon {
139    //! Stub module used when the `daemon` feature is disabled. Provides
140    //! the minimal surface that `src/vm_helper` calls — the real
141    //! implementation lives in the `zshrs-daemon` workspace crate.
142    pub mod builtins {
143        pub const ZSHRS_BUILTIN_NAMES: &[&str] = &[];
144        /// `is_zshrs_builtin` — see implementation.
145        pub fn is_zshrs_builtin(_name: &str) -> bool {
146            false
147        }
148        /// `try_dispatch` — see implementation.
149        pub fn try_dispatch(_name: &str, _argv: &[String]) -> Option<i32> {
150            None
151        }
152        /// `dispatch` — see implementation.
153        pub fn dispatch(_name: &str, _args: &[String]) -> Option<i32> {
154            None
155        }
156    }
157}
158/// `ast_sexp` submodule.
159#[path = "extensions/ast_sexp.rs"]
160pub mod ast_sexp;
161/// `dap` submodule.
162#[path = "extensions/dap.rs"]
163pub mod dap;
164/// `dumpers` submodule.
165#[path = "extensions/dumpers.rs"]
166pub mod dumpers;
167/// `ext_builtins` submodule.
168#[path = "extensions/ext_builtins.rs"]
169pub mod ext_builtins;
170/// `fds` submodule.
171#[path = "extensions/fds.rs"]
172pub mod fds;
173/// `fish_features` submodule.
174#[path = "extensions/fish_features.rs"]
175pub mod fish_features;
176/// `fmt` submodule — zsh source formatter (CLI `--fmt` + LSP
177/// `textDocument/formatting`).
178#[path = "extensions/fmt.rs"]
179pub mod fmt;
180/// `func_body_fmt` submodule.
181#[path = "extensions/func_body_fmt.rs"]
182pub mod func_body_fmt;
183/// `lsp` submodule.
184#[path = "extensions/lsp.rs"]
185pub mod lsp;
186/// `lsp_symbols` submodule.
187#[path = "extensions/lsp_symbols.rs"]
188pub mod lsp_symbols;
189// Lexer + parser live in `src/ported/lex.rs` and `src/ported/parse.rs`.
190// Re-export the modules so existing call sites (`zsh::lex::…`,
191// `zsh::parse::…`, `zsh::tokens::…`) keep resolving.
192// `tokens` aliases `lex` because tokens.rs's contents (lextok enum +
193// reserved-word table) now live inside lex.rs. Char tokens (Pound / Inpar /
194// Equals / …) and the REDIR_* / COND_* constants are not duplicated — they
195// live as flat `pub const` items in `ported::zsh_h` per `Src/zsh.h:144-679`.
196pub use ported::lex;
197pub use ported::lex as tokens;
198pub use ported::parse;
199/// `heredoc_ast` submodule.
200#[path = "extensions/heredoc_ast.rs"]
201pub mod heredoc_ast;
202/// `history` submodule.
203#[path = "extensions/history.rs"]
204pub mod history;
205/// `history_lazy` submodule — on-demand HISTFILE paging; the history
206/// is never slurped whole.
207#[path = "extensions/history_lazy.rs"]
208pub mod history_lazy;
209/// `log` submodule.
210#[path = "extensions/log.rs"]
211pub mod log;
212/// `lowfd` submodule — keeps the shell's own descriptors out of the user's fd space.
213#[path = "extensions/lowfd.rs"]
214pub mod lowfd;
215/// `zsh_ast` submodule.
216#[path = "extensions/zsh_ast.rs"]
217pub mod zsh_ast;
218// Backwards-compat flat re-exports — call sites that still write
219// `crate::datetime::…`, `crate::stat::…`, etc. resolve to the
220// `crate::modules::<modname>` ports without churn. New code should
221// reach for `crate::modules::<modname>` directly.
222pub use builtins::sched;
223pub use modules::attr;
224pub use modules::cap;
225pub use modules::clone;
226pub use modules::curses;
227pub use modules::datetime;
228pub use modules::db_gdbm;
229pub use modules::example;
230pub use modules::files;
231pub use modules::hlgroup;
232pub use modules::ksh93;
233pub use modules::langinfo;
234pub use modules::mapfile;
235pub use modules::mathfunc;
236pub use modules::nearcolor;
237pub use modules::newuser;
238pub use modules::param_private;
239pub use modules::parameter;
240pub use modules::pcre;
241pub use modules::random;
242pub use modules::random_real;
243pub use modules::regex as regex_module;
244pub use modules::socket;
245pub use modules::stat;
246pub use modules::system;
247pub use modules::tcp;
248pub use modules::termcap;
249pub use modules::terminfo;
250pub use modules::watch;
251pub use modules::zftp;
252pub use modules::zprof;
253pub use modules::zpty;
254pub use modules::zselect;
255pub use modules::zutil;
256/// `compinit_bg` submodule.
257#[path = "extensions/compinit_bg.rs"]
258pub mod compinit_bg;
259/// `fusevm_bridge` submodule.
260pub mod fusevm_bridge;
261/// `fusevm_disasm` submodule.
262pub mod fusevm_disasm;
263/// `intercepts` submodule.
264#[path = "extensions/intercepts.rs"]
265pub mod intercepts;
266/// `p10k` submodule — native powerlevel10k prompt engine.
267#[path = "extensions/p10k/mod.rs"]
268pub mod p10k;
269/// `plugin_cache` submodule.
270#[path = "extensions/plugin_cache.rs"]
271pub mod plugin_cache;
272/// `plugin_host` submodule — native (Rust) plugin loader (`zmodload -R`).
273#[path = "extensions/plugin_host.rs"]
274pub mod plugin_host;
275/// `pkg` — the `zpm` plugin package manager (global store).
276#[path = "extensions/pkg/mod.rs"]
277pub mod pkg;
278/// `recorder_ext` submodule.
279#[path = "extensions/recorder.rs"]
280pub mod recorder_ext;
281// Plugin-Framework-Agnostic State-Modification Recorder. Entire module
282// is `#![cfg(feature = "recorder")]` so it disappears from the default
283// `zshrs` build at the rustc-expansion stage. See docs/RECORDER.md.
284/// `gen_docs` submodule.
285#[path = "extensions/gen_docs.rs"]
286pub mod gen_docs;
287/// `recorder` submodule.
288#[cfg(feature = "recorder")]
289pub mod recorder;
290/// `regex_mod` submodule.
291#[path = "extensions/regex_mod.rs"]
292pub mod regex_mod;
293/// `stringsort` submodule.
294#[path = "extensions/stringsort.rs"]
295pub mod stringsort;
296/// `worker` submodule.
297#[path = "extensions/worker.rs"]
298pub mod worker;
299/// `zsh_builtin_docs` submodule.
300#[path = "extensions/zsh_builtin_docs.rs"]
301pub mod zsh_builtin_docs;
302/// `zsh_ext_builtin_docs` submodule.
303#[path = "extensions/zsh_ext_builtin_docs.rs"]
304pub mod zsh_ext_builtin_docs;
305/// `zsh_keyword_docs` submodule.
306#[path = "extensions/zsh_keyword_docs.rs"]
307pub mod zsh_keyword_docs;
308/// `zsh_option_docs` submodule.
309#[path = "extensions/zsh_option_docs.rs"]
310pub mod zsh_option_docs;
311/// `zsh_special_var_docs` submodule.
312#[path = "extensions/zsh_special_var_docs.rs"]
313pub mod zsh_special_var_docs;
314/// `ztest` submodule — shell-level unit test framework
315/// (port of `../strykelang` test framework).
316#[path = "extensions/ztest.rs"]
317pub mod ztest;
318/// `zle_param_sync` submodule — ZLE special-param write-back sync
319/// (Rust-only adapter for C's live GSU setters).
320#[path = "extensions/zle_param_sync.rs"]
321pub mod zle_param_sync;
322/// `zle_file_tester` submodule — file-existence/permission tests for native ZLE
323/// syntax highlighting (port of fish highlight/file_tester.rs).
324#[path = "extensions/zle_file_tester.rs"]
325pub mod zle_file_tester;
326/// `syntax_highlight` submodule — native command-line syntax highlighting
327/// (port of fish highlight/highlight.rs, driven by the zshrs lexer).
328#[path = "extensions/syntax_highlight.rs"]
329pub mod syntax_highlight;
330/// `autosuggest` submodule — native fish-style autosuggestions
331/// (port of the reader.rs autosuggestion state machine).
332#[path = "extensions/autosuggest.rs"]
333pub mod autosuggest;
334/// `history_search` submodule — native up-arrow prefix/substring/token history
335/// search (port of fish reader/history_search.rs).
336#[path = "extensions/history_search.rs"]
337pub mod history_search;
338/// `zle_fx` submodule — wiring for the native ZLE effects (autosuggest,
339/// syntax highlight, history search, autopair) into zlecore + the renderer.
340#[path = "extensions/zle_fx.rs"]
341pub mod zle_fx;
342/// `autopair` submodule — native bracket/quote auto-pairing
343/// (port of hlissner/zsh-autopair).
344#[path = "extensions/autopair.rs"]
345pub mod autopair;
346/// `zwc` submodule.
347#[path = "extensions/zwc.rs"]
348pub mod zwc;
349/// `zwc_decode` submodule.
350#[path = "extensions/zwc_decode.rs"]
351pub mod zwc_decode;
352// Backwards-compat re-export so `crate::rlimits::…` keeps resolving.
353pub use builtins::rlimits;
354
355// Top-level shell executor state + fusevm bridge glue. Not a port of
356// any single Src/*.c file — zsh's native wordcode VM lives in `Src/exec.c`;
357// zshrs runs fusevm instead (see src/fusevm_bridge.rs).
358/// `vm_helper` submodule.
359pub mod vm_helper;
360
361pub use fish_features::{
362    autosuggest_from_history,
363    colorize_line,
364    expand_abbreviation,
365    // Syntax highlighting
366    highlight_shell,
367    // Private mode
368    is_private_mode,
369    // Killring
370    kill_add,
371    kill_replace,
372    kill_yank,
373    kill_yank_rotate,
374    set_private_mode,
375    validate_autosuggestion,
376    // Validation
377    validate_command,
378    with_abbrs,
379    with_abbrs_mut,
380    AbbrPosition,
381    // Abbreviations
382    Abbreviation,
383    AbbreviationSet,
384    // Autosuggestions
385    Autosuggestion,
386    HighlightRole,
387    HighlightSpec,
388    KillRing,
389    ValidationStatus,
390};
391pub use tokens::lextok;
392pub use vm_helper::ShellExecutor;
393
394// ── Stryke integration hook ──
395// The fat binary registers a handler for @ prefix dispatch.
396// The thin binary leaves this as None — @ is treated as a normal character.
397
398use std::sync::OnceLock;
399
400type StrykeHandler = Box<dyn Fn(&str) -> i32 + Send + Sync>;
401static STRYKE_HANDLER: OnceLock<StrykeHandler> = OnceLock::new();
402
403/// Register a handler for @ prefix lines (fat binary sets this to stryke::run).
404pub fn set_stryke_handler<F>(f: F)
405where
406    F: Fn(&str) -> i32 + Send + Sync + 'static,
407{
408    let _ = STRYKE_HANDLER.set(Box::new(f));
409}
410
411/// Try to dispatch a line starting with @ to stryke.
412/// Returns Some(exit_code) if handled, None if no handler registered.
413pub fn try_stryke_dispatch(code: &str) -> Option<i32> {
414    STRYKE_HANDLER.get().map(|f| f(code))
415}