shrub_core/shell/funcs.rs
1//! Function support for shells.
2
3use crate::results::ExecutionWaitResult;
4use crate::{ExecutionParameters, ExecutionResult, commands, error, extensions, functions};
5
6impl<SE: extensions::ShellExtensions> crate::Shell<SE> {
7 /// Returns the function definition environment for this shell.
8 pub const fn funcs(&self) -> &functions::FunctionEnv {
9 &self.funcs
10 }
11
12 /// Returns a mutable reference to the function definition environment for
13 /// this shell.
14 pub const fn funcs_mut(&mut self) -> &mut functions::FunctionEnv {
15 &mut self.funcs
16 }
17
18 /// Tries to undefine a function in the shell's environment. Returns whether
19 /// or not a definition was removed.
20 ///
21 /// # Arguments
22 ///
23 /// * `name` - The name of the function to undefine.
24 pub fn undefine_func(&mut self, name: &str) -> bool {
25 self.funcs.remove(name).is_some()
26 }
27
28 /// Defines a function in the shell's environment. If a function already
29 /// exists with the given name, it is replaced with the new definition.
30 ///
31 /// # Arguments
32 ///
33 /// * `name` - The name of the function to define.
34 /// * `definition` - The function's definition.
35 /// * `source_info` - Source information for the function definition.
36 pub fn define_func(
37 &mut self,
38 name: impl Into<String>,
39 definition: shrub_parser::ast::FunctionDefinition,
40 source_info: &crate::SourceInfo,
41 ) {
42 let reg = functions::Registration::new(definition, source_info);
43 self.funcs.update(name.into(), reg);
44 }
45
46 /// Tries to return a mutable reference to the registration for a named
47 /// function. Returns `None` if no such function was found.
48 ///
49 /// # Arguments
50 ///
51 /// * `name` - The name of the function to lookup
52 pub fn func_mut(&mut self, name: &str) -> Option<&mut functions::Registration> {
53 self.funcs.get_mut(name)
54 }
55
56 /// Tries to define a function in the shell's environment using the given
57 /// string as its body.
58 ///
59 /// # Arguments
60 ///
61 /// * `name` - The name of the function
62 /// * `body_text` - The body of the function, expected to start with "()".
63 ///
64 /// # Errors
65 ///
66 /// Returns [`crate::error::ErrorKind::FunctionParseError`] if `body_text`
67 /// is not a valid function body (`()` followed by a compound command).
68 pub fn define_func_from_str(
69 &mut self,
70 name: impl Into<String>,
71 body_text: &str,
72 ) -> Result<(), error::Error> {
73 let name = name.into();
74
75 let mut parser =
76 super::parsing::create_parser(body_text.as_bytes(), &self.parser_options());
77 let func_body = parser.parse_function_parens_and_body().map_err(|e| {
78 error::Error::from(error::ErrorKind::FunctionParseError(name.clone(), e))
79 })?;
80
81 let def = shrub_parser::ast::FunctionDefinition {
82 fname: name.clone().into(),
83 body: func_body,
84 };
85
86 self.define_func(name, def, &crate::SourceInfo::default());
87
88 Ok(())
89 }
90
91 /// Invokes a function defined in this shell, returning its execution
92 /// result.
93 ///
94 /// # Arguments
95 ///
96 /// * `name` - The name of the function to invoke.
97 /// * `args` - The arguments to pass to the function.
98 /// * `params` - Execution parameters to use for the invocation.
99 ///
100 /// # Errors
101 ///
102 /// - [`crate::error::ErrorKind::FunctionNotFound`] if no function has the
103 /// given name.
104 /// - [`crate::error::ErrorKind::Unimplemented`] if the function's execution
105 /// is stopped (job control).
106 /// - Any error raised while running the function body, such as
107 /// [`crate::error::ErrorKind::MaxFunctionCallDepthExceeded`].
108 pub async fn invoke_function<N: AsRef<str>, I: IntoIterator<Item = A>, A: AsRef<str>>(
109 &mut self,
110 name: N,
111 args: I,
112 params: ExecutionParameters,
113 ) -> Result<ExecutionResult, error::Error> {
114 let name = name.as_ref();
115 let command_name = String::from(name);
116
117 let func_registration = self
118 .funcs
119 .get(name)
120 .ok_or_else(|| error::ErrorKind::FunctionNotFound(name.to_owned()))?
121 .to_owned();
122
123 let context = commands::ExecutionContext {
124 shell: self,
125 command_name,
126 params,
127 };
128
129 let command_args = args
130 .into_iter()
131 .map(|s| commands::CommandArg::String(String::from(s.as_ref())))
132 .collect::<Vec<_>>();
133
134 let result =
135 commands::invoke_shell_function(func_registration, context, &command_args).await?;
136
137 match result.wait().await? {
138 ExecutionWaitResult::Completed(result) => Ok(result),
139 ExecutionWaitResult::Stopped(..) => {
140 error::unimp("stopped child from function invocation")
141 }
142 }
143 }
144}