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::{Report, ReportType, Snippet, Span, SpanRole, add_span};
31use yash_syntax::syntax;
32
33/// Executes the function definition command.
34///
35/// First, the function name is [expanded](expand_word). If the expansion fails,
36/// the execution ends with a non-zero exit status. Next, the environment is
37/// examined for an existing function having the same name.  If there is such a
38/// function that is read-only, the execution ends with a non-zero exit status.
39/// Finally, the function definition is inserted into the environment, and the
40/// execution ends with an exit status of zero.
41///
42/// The `ErrExit` shell option is [applied](Env::apply_errexit) on error.
43impl Command for syntax::FunctionDefinition {
44    async fn execute(&self, env: &mut Env) -> Result {
45        define_function(env, self).await?;
46        env.apply_errexit()
47    }
48}
49
50async fn define_function(env: &mut Env, def: &syntax::FunctionDefinition) -> Result {
51    // Expand the function name
52    let Field {
53        value: name,
54        origin,
55    } = match expand_word(env, &def.name).await {
56        Ok((field, _exit_status)) => field,
57        Err(error) => return error.handle(env).await,
58    };
59
60    // Define the function
61    let function = Function::new(name, Rc::clone(&def.body), origin);
62    match env.functions.define(function) {
63        Ok(_) => {
64            env.exit_status = ExitStatus::SUCCESS;
65        }
66        Err(error) => {
67            report_define_error(env, &error).await;
68            env.exit_status = ExitStatus::ERROR;
69        }
70    }
71    Continue(())
72}
73
74/// Reports a function definition error.
75///
76/// This function assumes `error.existing.read_only_location.is_some()`.
77async fn report_define_error(env: &mut Env, error: &DefineError) {
78    let mut report = Report::new();
79    report.r#type = ReportType::Error;
80    report.title = error.to_string().into();
81    report.snippets =
82        Snippet::with_primary_span(&error.new.origin, "failed function redefinition".into());
83
84    add_span(
85        &error.existing.origin.code,
86        Span {
87            range: error.existing.origin.byte_range(),
88            role: SpanRole::Supplementary {
89                label: "existing function was defined here".into(),
90            },
91        },
92        &mut report.snippets,
93    );
94
95    let read_only_location = error.existing.read_only_location.as_ref().unwrap();
96    add_span(
97        &read_only_location.code,
98        Span {
99            range: read_only_location.byte_range(),
100            role: SpanRole::Supplementary {
101                label: "existing function was made read-only here".into(),
102            },
103        },
104        &mut report.snippets,
105    );
106
107    yash_env::io::print_report(env, &report).await;
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use futures_util::FutureExt;
114    use std::ops::ControlFlow::Break;
115    use yash_env::VirtualSystem;
116    use yash_env::option::On;
117    use yash_env::option::Option::ErrExit;
118    use yash_env::semantics::Divert;
119    use yash_env_test_helper::assert_stderr;
120    use yash_syntax::source::Location;
121
122    #[test]
123    fn function_definition_new() {
124        let mut env = Env::new_virtual();
125        env.exit_status = ExitStatus::ERROR;
126        let definition = syntax::FunctionDefinition {
127            has_keyword: false,
128            name: "foo".parse().unwrap(),
129            body: Rc::new("{ :; }".parse().unwrap()),
130        };
131
132        let result = definition.execute(&mut env).now_or_never().unwrap();
133        assert_eq!(result, Continue(()));
134        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
135        assert_eq!(env.functions.len(), 1);
136        let function = env.functions.get("foo").unwrap();
137        assert_eq!(function.name, "foo");
138        assert_eq!(function.origin, definition.name.location);
139        assert_eq!(function.body, definition.body);
140        assert_eq!(function.read_only_location, None);
141    }
142
143    #[test]
144    fn function_definition_overwrite() {
145        let mut env = Env::new_virtual();
146        env.exit_status = ExitStatus::ERROR;
147        let function = Function {
148            name: "foo".to_string(),
149            body: Rc::new("{ :; }".parse().unwrap()),
150            origin: Location::dummy("dummy"),
151            read_only_location: None,
152        };
153        env.functions.define(function).unwrap();
154        let definition = syntax::FunctionDefinition {
155            has_keyword: false,
156            name: "foo".parse().unwrap(),
157            body: Rc::new("( :; )".parse().unwrap()),
158        };
159
160        let result = definition.execute(&mut env).now_or_never().unwrap();
161        assert_eq!(result, Continue(()));
162        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
163        assert_eq!(env.functions.len(), 1);
164        let function = env.functions.get("foo").unwrap();
165        assert_eq!(function.name, "foo");
166        assert_eq!(function.origin, definition.name.location);
167        assert_eq!(function.body, definition.body);
168        assert_eq!(function.read_only_location, None);
169    }
170
171    #[test]
172    fn function_definition_read_only() {
173        let system = VirtualSystem::new();
174        let state = Rc::clone(&system.state);
175        let mut env = Env::with_system(Box::new(system));
176        let function = Rc::new(Function {
177            name: "foo".to_string(),
178            body: Rc::new("{ :; }".parse().unwrap()),
179            origin: Location::dummy("dummy"),
180            read_only_location: Some(Location::dummy("readonly")),
181        });
182        env.functions.define(Rc::clone(&function)).unwrap();
183        let definition = syntax::FunctionDefinition {
184            has_keyword: false,
185            name: "foo".parse().unwrap(),
186            body: Rc::new("( :; )".parse().unwrap()),
187        };
188
189        let result = definition.execute(&mut env).now_or_never().unwrap();
190        assert_eq!(result, Continue(()));
191        assert_eq!(env.exit_status, ExitStatus::ERROR);
192        assert_eq!(env.functions.len(), 1);
193        assert_eq!(env.functions.get("foo").unwrap(), &function);
194        assert_stderr(&state, |stderr| {
195            assert!(
196                stderr.contains("foo"),
197                "error message should contain function name: {stderr:?}"
198            )
199        });
200    }
201
202    #[test]
203    fn function_definition_name_expansion() {
204        let mut env = Env::new_virtual();
205        let definition = syntax::FunctionDefinition {
206            has_keyword: false,
207            name: r"\a".parse().unwrap(),
208            body: Rc::new("{ :; }".parse().unwrap()),
209        };
210
211        let result = definition.execute(&mut env).now_or_never().unwrap();
212        assert_eq!(result, Continue(()));
213        assert_eq!(env.exit_status, ExitStatus::SUCCESS);
214        let names: Vec<&str> = env.functions.iter().map(|f| f.name.as_str()).collect();
215        assert_eq!(names, ["a"]);
216    }
217
218    #[test]
219    fn errexit_in_function_definition() {
220        let mut env = Env::new_virtual();
221        let function = Function {
222            name: "foo".to_string(),
223            body: Rc::new("{ :; }".parse().unwrap()),
224            origin: Location::dummy("dummy"),
225            read_only_location: Some(Location::dummy("readonly")),
226        };
227        env.functions.define(function).unwrap();
228        let definition = syntax::FunctionDefinition {
229            has_keyword: false,
230            name: "foo".parse().unwrap(),
231            body: Rc::new("( :; )".parse().unwrap()),
232        };
233        env.options.set(ErrExit, On);
234
235        let result = definition.execute(&mut env).now_or_never().unwrap();
236        assert_eq!(result, Break(Divert::Exit(None)));
237        assert_eq!(env.exit_status, ExitStatus::ERROR);
238    }
239}