yash_env/
function.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//! Type definitions for functions.
18//!
19//! This module provides data types for defining shell functions.
20
21use std::borrow::Borrow;
22use std::collections::HashSet;
23use std::hash::Hash;
24use std::hash::Hasher;
25use std::iter::FusedIterator;
26use std::rc::Rc;
27use thiserror::Error;
28use yash_syntax::source::Location;
29use yash_syntax::syntax::FullCompoundCommand;
30
31/// Definition of a function.
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct Function {
34    /// String that identifies the function.
35    pub name: String,
36
37    /// Command that is executed when the function is called.
38    ///
39    /// This is wrapped in `Rc` so that we don't have to clone the entire
40    /// compound command when we define a function. The function definition
41    /// command only clones the `Rc` object from the abstract syntax tree.
42    pub body: Rc<FullCompoundCommand>,
43
44    /// Location of the function definition command that defined this function.
45    pub origin: Location,
46
47    /// Optional location where this function was made read-only.
48    ///
49    /// If this function is not read-only, `read_only_location` is `None`.
50    /// Otherwise, `read_only_location` is the location of the simple command
51    /// that executed the `readonly` built-in that made this function read-only.
52    pub read_only_location: Option<Location>,
53}
54
55impl Function {
56    /// Creates a new function.
57    ///
58    /// This is a convenience function for constructing a `Function` object.
59    /// The `read_only_location` is set to `None`.
60    #[inline]
61    #[must_use]
62    pub fn new<N: Into<String>, C: Into<Rc<FullCompoundCommand>>>(
63        name: N,
64        body: C,
65        origin: Location,
66    ) -> Self {
67        Function {
68            name: name.into(),
69            body: body.into(),
70            origin,
71            read_only_location: None,
72        }
73    }
74
75    /// Makes the function read-only.
76    ///
77    /// This is a convenience function for doing
78    /// `self.read_only_location = Some(location)` in a method chain.
79    #[inline]
80    #[must_use]
81    pub fn make_read_only(mut self, location: Location) -> Self {
82        self.read_only_location = Some(location);
83        self
84    }
85
86    /// Whether this function is read-only or not.
87    #[must_use]
88    pub const fn is_read_only(&self) -> bool {
89        self.read_only_location.is_some()
90    }
91}
92
93/// Wrapper of [`Function`] for inserting into a hash set.
94///
95/// A `HashEntry` wraps a `Function` in `Rc` so that the `Function` object can
96/// outlive the execution of the function which may redefine or unset the
97/// function itself. A simple command that executes the function clones the
98/// `Rc` object from the function set and retains it until the command
99/// terminates.
100///
101/// The `Hash` and `PartialEq` implementation for `HashEntry` only compares
102/// the names of the functions.
103#[derive(Clone, Debug, Eq)]
104struct HashEntry(Rc<Function>);
105
106impl PartialEq for HashEntry {
107    /// Compares the names of two hash entries.
108    ///
109    /// Members of [`Function`] other than `name` are not considered in this
110    /// function.
111    fn eq(&self, other: &HashEntry) -> bool {
112        self.0.name == other.0.name
113    }
114}
115
116impl Hash for HashEntry {
117    /// Hashes the name of the function.
118    ///
119    /// Members of [`Function`] other than `name` are not considered in this
120    /// function.
121    fn hash<H: Hasher>(&self, state: &mut H) {
122        self.0.name.hash(state)
123    }
124}
125
126impl Borrow<str> for HashEntry {
127    fn borrow(&self) -> &str {
128        &self.0.name
129    }
130}
131
132/// Collection of functions.
133#[derive(Clone, Debug, Default)]
134pub struct FunctionSet {
135    entries: HashSet<HashEntry>,
136}
137
138/// Error redefining a read-only function.
139#[derive(Clone, Debug, Eq, Error, PartialEq)]
140#[error("cannot redefine read-only function `{}`", .existing.name)]
141#[non_exhaustive]
142pub struct DefineError {
143    /// Existing read-only function
144    pub existing: Rc<Function>,
145    /// New function that tried to redefine the existing function
146    pub new: Rc<Function>,
147}
148
149/// Error unsetting a read-only function.
150#[derive(Clone, Debug, Eq, Error, PartialEq)]
151#[error("cannot unset read-only function `{}`", .existing.name)]
152#[non_exhaustive]
153pub struct UnsetError {
154    /// Existing read-only function
155    pub existing: Rc<Function>,
156}
157
158/// Unordered iterator over functions in a function set.
159///
160/// This iterator is created by [`FunctionSet::iter`].
161#[derive(Clone, Debug)]
162pub struct Iter<'a> {
163    inner: std::collections::hash_set::Iter<'a, HashEntry>,
164}
165
166impl FunctionSet {
167    /// Creates a new empty function set.
168    #[must_use]
169    pub fn new() -> Self {
170        FunctionSet::default()
171    }
172
173    /// Returns the function with the given name.
174    #[must_use]
175    pub fn get(&self, name: &str) -> Option<&Rc<Function>> {
176        self.entries.get(name).map(|entry| &entry.0)
177    }
178
179    /// Returns the number of functions in the set.
180    #[inline]
181    #[must_use]
182    pub fn len(&self) -> usize {
183        self.entries.len()
184    }
185
186    /// Returns `true` if the set contains no functions.
187    #[inline]
188    #[must_use]
189    pub fn is_empty(&self) -> bool {
190        self.entries.is_empty()
191    }
192
193    /// Inserts a function into the set.
194    ///
195    /// If a function with the same name already exists, it is replaced and
196    /// returned unless it is read-only, in which case `DefineError` is
197    /// returned.
198    pub fn define<F: Into<Rc<Function>>>(
199        &mut self,
200        function: F,
201    ) -> Result<Option<Rc<Function>>, DefineError> {
202        #[allow(clippy::mutable_key_type)]
203        fn inner(
204            entries: &mut HashSet<HashEntry>,
205            new: Rc<Function>,
206        ) -> Result<Option<Rc<Function>>, DefineError> {
207            match entries.get(new.name.as_str()) {
208                Some(existing) if existing.0.is_read_only() => Err(DefineError {
209                    existing: Rc::clone(&existing.0),
210                    new,
211                }),
212
213                _ => Ok(entries.replace(HashEntry(new)).map(|entry| entry.0)),
214            }
215        }
216        inner(&mut self.entries, function.into())
217    }
218
219    /// Removes a function from the set.
220    ///
221    /// This function returns the previously defined function if it exists.
222    /// However, if the function is read-only, `UnsetError` is returned.
223    pub fn unset(&mut self, name: &str) -> Result<Option<Rc<Function>>, UnsetError> {
224        match self.entries.get(name) {
225            Some(entry) if entry.0.is_read_only() => Err(UnsetError {
226                existing: Rc::clone(&entry.0),
227            }),
228
229            _ => Ok(self.entries.take(name).map(|entry| entry.0)),
230        }
231    }
232
233    /// Returns an iterator over functions in the set.
234    ///
235    /// The order of iteration is not specified.
236    pub fn iter(&self) -> Iter<'_> {
237        let inner = self.entries.iter();
238        Iter { inner }
239    }
240}
241
242impl<'a> Iterator for Iter<'a> {
243    type Item = &'a Rc<Function>;
244
245    fn next(&mut self) -> Option<Self::Item> {
246        self.inner.next().map(|entry| &entry.0)
247    }
248}
249
250impl ExactSizeIterator for Iter<'_> {
251    #[inline]
252    fn len(&self) -> usize {
253        self.inner.len()
254    }
255}
256
257impl FusedIterator for Iter<'_> {}
258
259impl<'a> IntoIterator for &'a FunctionSet {
260    type Item = &'a Rc<Function>;
261    type IntoIter = Iter<'a>;
262
263    fn into_iter(self) -> Self::IntoIter {
264        self.iter()
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn defining_new_function() {
274        let mut set = FunctionSet::new();
275        let function = Rc::new(Function::new(
276            "foo",
277            "{ :; }".parse::<FullCompoundCommand>().unwrap(),
278            Location::dummy("foo"),
279        ));
280
281        let result = set.define(function.clone());
282        assert_eq!(result, Ok(None));
283        assert_eq!(set.get("foo"), Some(&function));
284    }
285
286    #[test]
287    fn redefining_existing_function() {
288        let mut set = FunctionSet::new();
289        let function1 = Rc::new(Function::new(
290            "foo",
291            "{ echo 1; }".parse::<FullCompoundCommand>().unwrap(),
292            Location::dummy("foo 1"),
293        ));
294        let function2 = Rc::new(Function::new(
295            "foo",
296            "{ echo 2; }".parse::<FullCompoundCommand>().unwrap(),
297            Location::dummy("foo 2"),
298        ));
299        set.define(function1.clone()).unwrap();
300
301        let result = set.define(function2.clone());
302        assert_eq!(result, Ok(Some(function1)));
303        assert_eq!(set.get("foo"), Some(&function2));
304    }
305
306    #[test]
307    fn redefining_readonly_function() {
308        let mut set = FunctionSet::new();
309        let function1 = Rc::new(
310            Function::new(
311                "foo",
312                "{ echo 1; }".parse::<FullCompoundCommand>().unwrap(),
313                Location::dummy("foo 1"),
314            )
315            .make_read_only(Location::dummy("readonly")),
316        );
317        let function2 = Rc::new(Function::new(
318            "foo",
319            "{ echo 2; }".parse::<FullCompoundCommand>().unwrap(),
320            Location::dummy("foo 2"),
321        ));
322        set.define(function1.clone()).unwrap();
323
324        let error = set.define(function2.clone()).unwrap_err();
325        assert_eq!(error.existing, function1);
326        assert_eq!(error.new, function2);
327        assert_eq!(set.get("foo"), Some(&function1));
328    }
329
330    #[test]
331    fn unsetting_existing_function() {
332        let mut set = FunctionSet::new();
333        let function = Rc::new(Function::new(
334            "foo",
335            "{ :; }".parse::<FullCompoundCommand>().unwrap(),
336            Location::dummy("foo"),
337        ));
338        set.define(function.clone()).unwrap();
339
340        let result = set.unset("foo").unwrap();
341        assert_eq!(result, Some(function));
342        assert_eq!(set.get("foo"), None);
343    }
344
345    #[test]
346    fn unsetting_nonexisting_function() {
347        let mut set = FunctionSet::new();
348
349        let result = set.unset("foo").unwrap();
350        assert_eq!(result, None);
351        assert_eq!(set.get("foo"), None);
352    }
353
354    #[test]
355    fn unsetting_readonly_function() {
356        let mut set = FunctionSet::new();
357        let function = Rc::new(
358            Function::new(
359                "foo",
360                "{ :; }".parse::<FullCompoundCommand>().unwrap(),
361                Location::dummy("foo"),
362            )
363            .make_read_only(Location::dummy("readonly")),
364        );
365        set.define(function.clone()).unwrap();
366
367        let error = set.unset("foo").unwrap_err();
368        assert_eq!(error.existing, function);
369    }
370
371    #[test]
372    fn iteration() {
373        let mut set = FunctionSet::new();
374        let function1 = Rc::new(Function::new(
375            "foo",
376            "{ echo 1; }".parse::<FullCompoundCommand>().unwrap(),
377            Location::dummy("foo"),
378        ));
379        let function2 = Rc::new(Function::new(
380            "bar",
381            "{ echo 2; }".parse::<FullCompoundCommand>().unwrap(),
382            Location::dummy("bar"),
383        ));
384        set.define(function1.clone()).unwrap();
385        set.define(function2.clone()).unwrap();
386
387        let functions = set.iter().collect::<Vec<_>>();
388        assert!(
389            functions[..] == [&function1, &function2] || functions[..] == [&function2, &function1],
390            "{functions:?}"
391        );
392    }
393}