Skip to main content

shuck_parser/parser/
profile.rs

1use super::ZshOptionState;
2
3/// Supported shell dialects for parsing.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
5pub enum ShellDialect {
6    /// POSIX-style parsing used for `sh`, `dash`, and generic portable shell input.
7    Posix,
8    /// mksh-specific parsing.
9    Mksh,
10    /// Bash parsing.
11    #[default]
12    Bash,
13    /// zsh parsing.
14    Zsh,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub(super) struct DialectFeatures {
19    pub(super) double_bracket: bool,
20    pub(super) arithmetic_command: bool,
21    pub(super) arithmetic_for: bool,
22    pub(super) function_keyword: bool,
23    pub(super) select_loop: bool,
24    pub(super) coproc_keyword: bool,
25    pub(super) zsh_repeat_loop: bool,
26    pub(super) zsh_foreach_loop: bool,
27    pub(super) zsh_parameter_modifiers: bool,
28    pub(super) zsh_brace_if: bool,
29    pub(super) zsh_always: bool,
30    pub(super) zsh_background_operators: bool,
31    pub(super) zsh_glob_qualifiers: bool,
32}
33
34impl ShellDialect {
35    /// Infer a shell dialect from a command or shebang name.
36    pub fn from_name(name: &str) -> Self {
37        match name.trim().to_ascii_lowercase().as_str() {
38            "sh" | "dash" | "ksh" | "posix" => Self::Posix,
39            "mksh" => Self::Mksh,
40            "zsh" => Self::Zsh,
41            _ => Self::Bash,
42        }
43    }
44
45    pub(super) const fn features(self) -> DialectFeatures {
46        match self {
47            Self::Posix => DialectFeatures {
48                double_bracket: false,
49                arithmetic_command: false,
50                arithmetic_for: false,
51                function_keyword: true,
52                select_loop: false,
53                coproc_keyword: false,
54                zsh_repeat_loop: false,
55                zsh_foreach_loop: false,
56                zsh_parameter_modifiers: false,
57                zsh_brace_if: false,
58                zsh_always: false,
59                zsh_background_operators: false,
60                zsh_glob_qualifiers: false,
61            },
62            Self::Mksh => DialectFeatures {
63                double_bracket: true,
64                arithmetic_command: true,
65                arithmetic_for: false,
66                function_keyword: true,
67                select_loop: true,
68                coproc_keyword: false,
69                zsh_repeat_loop: false,
70                zsh_foreach_loop: false,
71                zsh_parameter_modifiers: false,
72                zsh_brace_if: false,
73                zsh_always: false,
74                zsh_background_operators: false,
75                zsh_glob_qualifiers: false,
76            },
77            Self::Bash => DialectFeatures {
78                double_bracket: true,
79                arithmetic_command: true,
80                arithmetic_for: true,
81                function_keyword: true,
82                select_loop: true,
83                coproc_keyword: true,
84                zsh_repeat_loop: false,
85                zsh_foreach_loop: false,
86                zsh_parameter_modifiers: false,
87                zsh_brace_if: false,
88                zsh_always: false,
89                zsh_background_operators: false,
90                zsh_glob_qualifiers: false,
91            },
92            Self::Zsh => DialectFeatures {
93                double_bracket: true,
94                arithmetic_command: true,
95                arithmetic_for: true,
96                function_keyword: true,
97                select_loop: true,
98                coproc_keyword: true,
99                zsh_repeat_loop: true,
100                zsh_foreach_loop: true,
101                zsh_parameter_modifiers: true,
102                zsh_brace_if: true,
103                zsh_always: true,
104                zsh_background_operators: true,
105                zsh_glob_qualifiers: true,
106            },
107        }
108    }
109}
110
111/// Dialect plus optional zsh option state used to configure the lexer and parser.
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct ShellProfile {
114    /// Shell dialect to parse.
115    pub dialect: ShellDialect,
116    /// Optional zsh option state, used only for zsh parsing.
117    pub options: Option<ZshOptionState>,
118}
119
120impl ShellProfile {
121    /// Build a native profile for `dialect`.
122    pub fn native(dialect: ShellDialect) -> Self {
123        Self {
124            dialect,
125            options: (dialect == ShellDialect::Zsh).then(ZshOptionState::zsh_default),
126        }
127    }
128
129    /// Build a profile with explicit zsh option state.
130    pub fn with_zsh_options(dialect: ShellDialect, options: ZshOptionState) -> Self {
131        Self {
132            dialect,
133            options: (dialect == ShellDialect::Zsh).then_some(options),
134        }
135    }
136
137    /// Borrow the zsh option state, if this profile carries one.
138    pub fn zsh_options(&self) -> Option<&ZshOptionState> {
139        self.options.as_ref()
140    }
141}