zsh/extensions/heredoc_ast.rs
1//! Heredoc AST-glue types — Rust-only, NOT in zsh C.
2//!
3//! zsh tracks pending heredocs via the `struct heredocs` linked-list
4//! node defined at `Src/zsh.h:1152-1157`:
5//!
6//! ```c
7//! struct heredocs {
8//! struct heredocs *next;
9//! int type;
10//! int pc;
11//! char *str;
12//! };
13//! ```
14//!
15//! The C model defers body collection — the parser records `pc`
16//! (wordcode offset) + `str` (terminator) at the `<<EOF` site, walks
17//! past the redirection emitting normal wordcode for the rest of the
18//! line, then `gethere()` (lex.c:1810) walks the linked list at
19//! newline and reads each body from the input stream into the
20//! wordcode buffer at the saved pc.
21//!
22//! zshrs's pre-wordcode parser collects each heredoc body inline
23//! during lex (no pc, no later resolution), so the live shape of
24//! per-heredoc state is different: `terminator`, `strip_tabs`,
25//! `content`, `quoted`, `processed`. The Vec position carries
26//! ordering (no `next` linked list).
27//!
28//! Both `HereDoc` (pending lexer state) and `HereDocInfo` (parsed
29//! redir attachment) are scheduled to disappear when Phase 9 of
30//! `docs/PORT_PLAN.md` lands — the wordcode port reinstates the
31//! C `struct heredocs` shape + `gethere()` flow.
32
33use serde::{Deserialize, Serialize};
34
35/// Per-heredoc state collected by the lexer during `<<EOF` parsing.
36/// Held in the lexer-side `HEREDOCS: Vec<HereDoc>` thread_local for
37/// later attachment to `ZshRedir` entries (via `heredoc_idx`).
38///
39/// Rust-only — Phase 9 wordcode port replaces this with the canonical
40/// `struct heredocs` linked list (zsh.h:1152) + `gethere()` deferred
41/// body collection.
42#[derive(Debug, Clone)]
43pub struct HereDoc {
44 pub terminator: String,
45 pub strip_tabs: bool,
46 pub content: String,
47 /// True if the terminator was originally quoted (`<<'EOF'`,
48 /// `<<"EOF"`, or `<<\EOF`). Disables variable expansion / command
49 /// substitution / arithmetic in the body.
50 pub quoted: bool,
51 /// True once `process_heredocs` has read the body. Distinct from
52 /// "content is empty" because an empty heredoc legitimately has
53 /// empty content.
54 pub processed: bool,
55}
56
57/// Heredoc body+metadata attached to a parsed `ZshRedir`. Carried
58/// through the AST and consumed by the compiler when emitting
59/// `Op::HereDoc(idx)` for the fusevm VM.
60///
61/// Rust-only — Phase 9 wordcode port deletes this together with the
62/// rest of the AST tree; bodies will land directly in the wordcode
63/// stream at the redirection's `pc` slot.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct HereDocInfo {
66 pub content: String,
67 pub terminator: String,
68 /// Originally-quoted terminator (`<<'EOF'`, `<<"EOF"`). When true
69 /// the body is passed verbatim — no `$var` / `$(cmd)` / `$((expr))`
70 /// expansion. Plain `<<EOF` runs all expansions.
71 #[serde(default)]
72 pub quoted: bool,
73}