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