Skip to main content

clash_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(&mut self, handler: Option<&crate::traps::TrapHandler>) {
62        self.call_stack.push_trap_handler(handler);
63    }
64
65    pub(crate) fn leave_trap_handler(&mut self) {
66        self.call_stack.pop();
67    }
68
69    /// Updates the shell's internal tracking state to reflect that a new shell
70    /// function is being entered.
71    ///
72    /// # Arguments
73    ///
74    /// * `name` - The name of the function being entered.
75    /// * `function` - The function being entered.
76    /// * `args` - The arguments being passed to the function.
77    /// * `_params` - Current execution parameters.
78    pub(crate) fn enter_function(
79        &mut self,
80        name: &str,
81        function: &functions::Registration,
82        args: impl IntoIterator<Item = String>,
83        _params: &ExecutionParameters,
84    ) -> Result<(), error::Error> {
85        if let Some(max_call_depth) = self.options.max_function_call_depth
86            && self.call_stack.function_call_depth() >= max_call_depth
87        {
88            return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
89        }
90
91        if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
92            let depth = self.call_stack.function_call_depth();
93            let prefix = repeated_char_str(' ', depth);
94            tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
95        }
96
97        self.call_stack.push_function(name, function, args);
98        self.env.push_scope(env::EnvironmentScope::Local);
99
100        Ok(())
101    }
102
103    /// Updates the shell's internal tracking state to reflect that the shell
104    /// has exited the top-most function on its call stack.
105    pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
106        self.env.pop_scope(env::EnvironmentScope::Local)?;
107
108        if let Some(exited_call) = self.call_stack.pop() {
109            if let callstack::FrameType::Function(func_call) = exited_call.frame_type {
110                if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
111                    let depth = self.call_stack.function_call_depth();
112                    let prefix = repeated_char_str(' ', depth);
113                    tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func  [depth={depth}]: {prefix}{}", func_call.function_name);
114                }
115            } else {
116                let err: error::Error =
117                    error::ErrorKind::InternalError("mismatched call stack state".to_owned())
118                        .into();
119                return Err(err.into_fatal());
120            }
121        }
122
123        Ok(())
124    }
125
126    /// Returns the *current* positional arguments for the shell ($1 and beyond).
127    /// Influenced by the current call stack.
128    pub fn current_shell_args(&self) -> &[String] {
129        for frame in self.call_stack.iter() {
130            match frame.frame_type {
131                // Function calls always shadow positional parameters.
132                crate::callstack::FrameType::Function(..) => return &frame.args,
133                // Executed scripts always shadow positional parameters.
134                _ if frame.frame_type.is_run_script() => return &frame.args,
135                // Sourced scripts shadow positional parameters if they have arguments.
136                _ if frame.frame_type.is_sourced_script() => {
137                    if !frame.args.is_empty() {
138                        return &frame.args;
139                    }
140                }
141                _ => (),
142            }
143        }
144
145        self.args.as_slice()
146    }
147
148    /// Returns a mutable reference to *current* positional parameters for the shell
149    /// ($1 and beyond).
150    pub fn current_shell_args_mut(&mut self) -> &mut Vec<String> {
151        for frame in self.call_stack.iter_mut() {
152            match frame.frame_type {
153                // Function calls always shadow positional parameters.
154                crate::callstack::FrameType::Function(..) => return &mut frame.args,
155                // Executed scripts always shadow positional parameters.
156                _ if frame.frame_type.is_run_script() => return &mut frame.args,
157                // Sourced scripts shadow positional parameters if they have arguments.
158                _ if frame.frame_type.is_sourced_script() => {
159                    if !frame.args.is_empty() {
160                        return &mut frame.args;
161                    }
162                }
163                _ => (),
164            }
165        }
166
167        &mut self.args
168    }
169}
170
171fn repeated_char_str(c: char, count: usize) -> String {
172    (0..count).map(|_| c).collect()
173}