yash_semantics/command/
function_definition.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementations of function definition semantics.
18
19use crate::Handle;
20use crate::command::Command;
21use crate::expansion::Field;
22use crate::expansion::expand_word;
23use std::ops::ControlFlow::Continue;
24use std::rc::Rc;
25use yash_env::Env;
26use yash_env::function::DefineError;
27use yash_env::function::Function;
28use yash_env::semantics::ExitStatus;
29use yash_env::semantics::Result;
30use yash_syntax::source::pretty::Annotation;
31use yash_syntax::source::pretty::AnnotationType;
32use yash_syntax::source::pretty::Message;
33use yash_syntax::syntax;
34
35/// Executes the function definition command.
36///
37/// First, the function name is [expanded](expand_word). If the expansion fails,
38/// the execution ends with a non-zero exit status. Next, the environment is
39/// examined for an existing function having the same name.  If there is such a
40/// function that is read-only, the execution ends with a non-zero exit status.
41/// Finally, the function definition is inserted into the environment, and the
42/// execution ends with an exit status of zero.
43///
44/// The `ErrExit` shell option is [applied](Env::apply_errexit) on error.
45impl Command for syntax::FunctionDefinition {
46    async fn execute(&self, env: &mut Env) -> Result {
47        define_function(env, self).await?;
48        env.apply_errexit()
49    }
50}
51
52async fn define_function(env: &mut Env, def: &syntax::FunctionDefinition) -> Result {
53    // Expand the function name
54    let Field {
55        value: name,
56        origin,
57    } = match expand_word(env, &def.name).await {
58        Ok((field, _exit_status)) => field,
59        Err(error) => return error.handle(env).await,
60    };
61
62    // Define the function
63    let function = Function::new(name, Rc::clone(&def.body), origin);
64    match env.functions.define(function) {
65        Ok(_) => {
66            env.exit_status = ExitStatus::SUCCESS;
67        }
68        Err(error) => {
69            report_define_error(env, &error).await;
70            env.exit_status = ExitStatus::ERROR;
71        }
72    }
73    Continue(())
74}
75
76/// Reports a function definition error.
77///
78/// This function assumes `error.existing.read_only_location.is_some()`.
79async fn report_define_error(env: &mut Env, error: &DefineError) {
80    let message = Message {
81        r#type: AnnotationType::Error,
82        title: error.to_string().into(),
83        annotations: vec![
84            Annotation::new(
85                AnnotationType::Error,
86                "failed function redefinition".into(),
87                &error.new.origin,
88            ),
89            Annotation::new(
90                AnnotationType::Info,
91                "existing function was defined here".into(),
92                &error.existing.origin,
93            ),
94            Annotation::new(
95                AnnotationType::Info,
96                "existing function was made read-only here".into(),
97                error.existing.read_only_location.as_ref().unwrap(),
98            ),
99        ],
100        footers: vec![],
101    };
102
103    yash_env::io::print_message(env, message).await;
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use futures_util::FutureExt;
110    use std::ops::ControlFlow::Break;
111    use yash_env::VirtualSystem;
112    use yash_env::option::On;
113    use yash_env::option::Option::ErrExit;
114    use yash_env::semantics::Divert;
115    use yash_env_test_helper::assert_stderr;
116    use yash_syntax::source::Location;
117
118    #[test]
119    fn function_definition_new() {
120        let mut env = Env::new_virtual();
121        env.exit_status = ExitStatus::ERROR;
122        let definition = syntax::FunctionDefinition {
123            has_keyword: false,
124            name: "foo".parse().unwrap(),
125            body: Rc::new("{ :; }".parse().unwrap()),
126        };
127
128        let result = definition.execute(&mut env).now_or_never().unwrap();
129        assert_eq!(result, Continue(()));
130        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
131        assert_eq!(env.functions.len(), 1);
132        let function = env.functions.get("foo").unwrap();
133        assert_eq!(function.name, "foo");
134        assert_eq!(function.origin, definition.name.location);
135        assert_eq!(function.body, definition.body);
136        assert_eq!(function.read_only_location, None);
137    }
138
139    #[test]
140    fn function_definition_overwrite() {
141        let mut env = Env::new_virtual();
142        env.exit_status = ExitStatus::ERROR;
143        let function = Function {
144            name: "foo".to_string(),
145            body: Rc::new("{ :; }".parse().unwrap()),
146            origin: Location::dummy("dummy"),
147            read_only_location: None,
148        };
149        env.functions.define(function).unwrap();
150        let definition = syntax::FunctionDefinition {
151            has_keyword: false,
152            name: "foo".parse().unwrap(),
153            body: Rc::new("( :; )".parse().unwrap()),
154        };
155
156        let result = definition.execute(&mut env).now_or_never().unwrap();
157        assert_eq!(result, Continue(()));
158        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
159        assert_eq!(env.functions.len(), 1);
160        let function = env.functions.get("foo").unwrap();
161        assert_eq!(function.name, "foo");
162        assert_eq!(function.origin, definition.name.location);
163        assert_eq!(function.body, definition.body);
164        assert_eq!(function.read_only_location, None);
165    }
166
167    #[test]
168    fn function_definition_read_only() {
169        let system = VirtualSystem::new();
170        let state = Rc::clone(&system.state);
171        let mut env = Env::with_system(Box::new(system));
172        let function = Rc::new(Function {
173            name: "foo".to_string(),
174            body: Rc::new("{ :; }".parse().unwrap()),
175            origin: Location::dummy("dummy"),
176            read_only_location: Some(Location::dummy("readonly")),
177        });
178        env.functions.define(Rc::clone(&function)).unwrap();
179        let definition = syntax::FunctionDefinition {
180            has_keyword: false,
181            name: "foo".parse().unwrap(),
182            body: Rc::new("( :; )".parse().unwrap()),
183        };
184
185        let result = definition.execute(&mut env).now_or_never().unwrap();
186        assert_eq!(result, Continue(()));
187        assert_eq!(env.exit_status, ExitStatus::ERROR);
188        assert_eq!(env.functions.len(), 1);
189        assert_eq!(env.functions.get("foo").unwrap(), &function);
190        assert_stderr(&state, |stderr| {
191            assert!(
192                stderr.contains("foo"),
193                "error message should contain function name: {stderr:?}"
194            )
195        });
196    }
197
198    #[test]
199    fn function_definition_name_expansion() {
200        let mut env = Env::new_virtual();
201        let definition = syntax::FunctionDefinition {
202            has_keyword: false,
203            name: r"\a".parse().unwrap(),
204            body: Rc::new("{ :; }".parse().unwrap()),
205        };
206
207        let result = definition.execute(&mut env).now_or_never().unwrap();
208        assert_eq!(result, Continue(()));
209        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
210        let names: Vec<&str> = env.functions.iter().map(|f| f.name.as_str()).collect();
211        assert_eq!(names, ["a"]);
212    }
213
214    #[test]
215    fn errexit_in_function_definition() {
216        let mut env = Env::new_virtual();
217        let function = Function {
218            name: "foo".to_string(),
219            body: Rc::new("{ :; }".parse().unwrap()),
220            origin: Location::dummy("dummy"),
221            read_only_location: Some(Location::dummy("readonly")),
222        };
223        env.functions.define(function).unwrap();
224        let definition = syntax::FunctionDefinition {
225            has_keyword: false,
226            name: "foo".parse().unwrap(),
227            body: Rc::new("( :; )".parse().unwrap()),
228        };
229        env.options.set(ErrExit, On);
230
231        let result = definition.execute(&mut env).now_or_never().unwrap();
232        assert_eq!(result, Break(Divert::Exit(None)));
233        assert_eq!(env.exit_status, ExitStatus::ERROR);
234    }
235}