Skip to main content

brush_core/shell/
callstack.rs

1//! Call stack management for the shell.
2
3use crate::{ExecutionParameters, callstack, env, error, functions, trace_categories};
4
5impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
6    /// Returns whether or not the shell is actively executing in a sourced script.
7    pub fn in_sourced_script(&self) -> bool {
8        self.call_stack.in_sourced_script()
9    }
10
11    /// Returns whether or not the shell is actively executing in a shell function.
12    pub fn in_function(&self) -> bool {
13        self.call_stack.in_function()
14    }
15
16    /// Updates the shell's internal tracking state to reflect that a new interactive
17    /// session is being started.
18    pub fn start_interactive_session(&mut self) -> Result<(), error::Error> {
19        self.call_stack.push_interactive_session();
20        Ok(())
21    }
22
23    /// Updates the shell's internal tracking state to reflect that the current
24    /// interactive session is ending.
25    pub fn end_interactive_session(&mut self) -> Result<(), error::Error> {
26        if self
27            .call_stack
28            .current_frame()
29            .is_none_or(|frame| !frame.frame_type.is_interactive_session())
30        {
31            return Err(error::ErrorKind::NotInInteractiveSession.into());
32        }
33
34        self.call_stack.pop();
35
36        Ok(())
37    }
38
39    /// Updates the shell's internal tracking state to reflect that command
40    /// string mode is being started.
41    pub fn start_command_string_mode(&mut self) {
42        self.call_stack.push_command_string();
43    }
44
45    /// Updates the shell's internal tracking state to reflect that command
46    /// string mode is ending.
47    pub fn end_command_string_mode(&mut self) -> Result<(), error::Error> {
48        if self
49            .call_stack
50            .current_frame()
51            .is_none_or(|frame| !frame.frame_type.is_command_string())
52        {
53            return Err(error::ErrorKind::NotExecutingCommandString.into());
54        }
55
56        self.call_stack.pop();
57
58        Ok(())
59    }
60
61    pub(crate) fn enter_trap_handler(
62        &mut self,
63        signal: crate::traps::TrapSignal,
64        handler: Option<&crate::traps::TrapHandler>,
65    ) {
66        self.call_stack.push_trap_handler(signal, handler);
67    }
68
69    pub(crate) fn leave_trap_handler(&mut self) {
70        self.call_stack.pop();
71    }
72
73    /// Acquires a block on trap delivery, preventing traps from being delivered until
74    /// the block is released. Multiple blocks may be acquired, and trap delivery will
75    /// remain suppressed until all blocks have been released.
76    pub(crate) const fn acquire_trap_delivery_block(&mut self) {
77        self.call_stack.acquire_trap_delivery_block();
78    }
79
80    /// Releases a block on trap delivery; note that trap delivery will remain
81    /// suppressed until all blocks have been released.
82    pub(crate) const fn release_trap_delivery_block(&mut self) {
83        self.call_stack.release_trap_delivery_block();
84    }
85
86    /// Updates the shell's internal tracking state to reflect that a new shell
87    /// function is being entered.
88    ///
89    /// # Arguments
90    ///
91    /// * `name` - The name of the function being entered.
92    /// * `function` - The function being entered.
93    /// * `args` - The arguments being passed to the function.
94    /// * `_params` - Current execution parameters.
95    pub(crate) fn enter_function(
96        &mut self,
97        name: &str,
98        function: &functions::Registration,
99        args: impl IntoIterator<Item = String>,
100        _params: &ExecutionParameters,
101    ) -> Result<(), error::Error> {
102        if let Some(max_call_depth) = self.options.max_function_call_depth
103            && self.call_stack.function_call_depth() >= max_call_depth
104        {
105            return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
106        }
107
108        if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
109            let depth = self.call_stack.function_call_depth();
110            let prefix = repeated_char_str(' ', depth);
111            tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
112        }
113
114        self.call_stack.push_function(name, function, args);
115        self.env.push_scope(env::EnvironmentScope::Local);
116
117        Ok(())
118    }
119
120    /// Updates the shell's internal tracking state to reflect that the shell
121    /// has exited the top-most function on its call stack.
122    pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
123        self.env.pop_scope(env::EnvironmentScope::Local)?;
124
125        if let Some(exited_call) = self.call_stack.pop() {
126            if let callstack::FrameType::Function(func_call) = exited_call.frame_type {
127                if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
128                    let depth = self.call_stack.function_call_depth();
129                    let prefix = repeated_char_str(' ', depth);
130                    tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func  [depth={depth}]: {prefix}{}", func_call.function_name);
131                }
132            } else {
133                let err: error::Error =
134                    error::ErrorKind::InternalError("mismatched call stack state".to_owned())
135                        .into();
136                return Err(err.into_fatal());
137            }
138        }
139
140        Ok(())
141    }
142
143    /// Returns the *current* positional arguments for the shell ($1 and beyond).
144    /// Influenced by the current call stack.
145    pub fn current_shell_args(&self) -> &[String] {
146        for frame in self.call_stack.iter() {
147            match frame.frame_type {
148                // Function calls always shadow positional parameters.
149                crate::callstack::FrameType::Function(..) => return &frame.args,
150                // Executed scripts always shadow positional parameters.
151                _ if frame.frame_type.is_run_script() => return &frame.args,
152                // Sourced scripts shadow positional parameters if they have arguments.
153                _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
154                    return &frame.args;
155                }
156                _ => (),
157            }
158        }
159
160        self.args.as_slice()
161    }
162
163    /// Returns a mutable reference to *current* positional parameters for the shell
164    /// ($1 and beyond).
165    pub fn current_shell_args_mut(&mut self) -> &mut Vec<String> {
166        for frame in self.call_stack.iter_mut() {
167            match frame.frame_type {
168                // Function calls always shadow positional parameters.
169                crate::callstack::FrameType::Function(..) => return &mut frame.args,
170                // Executed scripts always shadow positional parameters.
171                _ if frame.frame_type.is_run_script() => return &mut frame.args,
172                // Sourced scripts shadow positional parameters if they have arguments.
173                _ if frame.frame_type.is_sourced_script() && !frame.args.is_empty() => {
174                    return &mut frame.args;
175                }
176                _ => (),
177            }
178        }
179
180        &mut self.args
181    }
182}
183
184fn repeated_char_str(c: char, count: usize) -> String {
185    (0..count).map(|_| c).collect()
186}