yash_env/
alias.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 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//! Defining aliases
18//!
19//! This module provides data structures for defining aliases in the shell
20//! execution environment.
21
22use crate::Env;
23use crate::source::Location;
24use std::borrow::Borrow;
25use std::cell::RefCell;
26use std::collections::HashSet;
27use std::fmt::Debug;
28use std::hash::Hash;
29use std::hash::Hasher;
30use std::rc::Rc;
31
32/// Name-value pair that defines an alias
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct Alias {
35    /// Name of the alias that is matched against a command word by the syntax parser
36    pub name: String,
37    /// String that substitutes part of the source code when it is found to match the alias name
38    pub replacement: String,
39    /// Whether this alias is a global alias or not
40    pub global: bool,
41    /// Location of the word in the simple command that invoked the alias built-in to define this
42    /// alias
43    pub origin: Location,
44}
45
46/// Wrapper of [`Alias`] for inserting into a hash set
47///
48/// A `HashEntry` wraps an `Alias` in `Rc` so that the alias definition can be referred to even
49/// after the definition is removed. The `Hash` and `PartialEq` implementation for `HashEntry`
50/// compares only names.
51///
52/// ```
53/// let mut entries = std::collections::HashSet::new();
54/// let name = "foo";
55/// let origin = yash_env::source::Location::dummy("");
56/// let old = yash_env::alias::HashEntry::new(
57///     name.to_string(), "old".to_string(), false, origin.clone());
58/// let new = yash_env::alias::HashEntry::new(
59///     name.to_string(), "new".to_string(), false, origin);
60/// entries.insert(old);
61/// let old = entries.replace(new).unwrap();
62/// assert_eq!(old.0.replacement, "old");
63/// assert_eq!(entries.get(name).unwrap().0.replacement, "new");
64/// ```
65#[derive(Clone, Debug, Eq)]
66pub struct HashEntry(pub Rc<Alias>);
67
68impl HashEntry {
69    /// Convenience method for creating a new alias definition as `HashEntry`
70    pub fn new(name: String, replacement: String, global: bool, origin: Location) -> HashEntry {
71        HashEntry(Rc::new(Alias {
72            name,
73            replacement,
74            global,
75            origin,
76        }))
77    }
78}
79
80impl PartialEq for HashEntry {
81    fn eq(&self, other: &HashEntry) -> bool {
82        self.0.name == other.0.name
83    }
84}
85
86impl Hash for HashEntry {
87    fn hash<H: Hasher>(&self, state: &mut H) {
88        self.0.name.hash(state)
89    }
90}
91
92impl Borrow<str> for HashEntry {
93    fn borrow(&self) -> &str {
94        &self.0.name
95    }
96}
97
98/// Collection of aliases
99pub type AliasSet = HashSet<HashEntry>;
100
101/// Interface used by the parser to look up aliases
102///
103/// This trait is an abstract interface that represents an immutable collection
104/// of aliases. The parser uses this trait to look up aliases when it encounters
105/// a command word in a simple command.
106pub trait Glossary: Debug {
107    /// Looks up an alias by name.
108    ///
109    /// If an alias with the given name is found, it is returned. Otherwise, the
110    /// return value is `None`.
111    #[must_use]
112    // This method returns an `Rc<Alias>` rather than `&Alias` so that the
113    // implementation for `RefCell` below can return a value after releasing
114    // the borrow of the inner glossary.
115    fn look_up(&self, name: &str) -> Option<Rc<Alias>>;
116
117    /// Returns whether the glossary is empty.
118    ///
119    /// If the glossary is empty, the parser can skip alias expansion. This
120    /// method is used as a hint to optimize alias expansion.
121    ///
122    /// If the glossary has any aliases, it must return `false`.
123    ///
124    /// The default implementation returns `false`.
125    #[must_use]
126    fn is_empty(&self) -> bool {
127        false
128    }
129}
130
131impl<T: Glossary> Glossary for &T {
132    fn look_up(&self, name: &str) -> Option<Rc<Alias>> {
133        (**self).look_up(name)
134    }
135    fn is_empty(&self) -> bool {
136        (**self).is_empty()
137    }
138}
139
140impl<T: Glossary> Glossary for &mut T {
141    fn look_up(&self, name: &str) -> Option<Rc<Alias>> {
142        (**self).look_up(name)
143    }
144    fn is_empty(&self) -> bool {
145        (**self).is_empty()
146    }
147}
148
149impl Glossary for AliasSet {
150    fn look_up(&self, name: &str) -> Option<Rc<Alias>> {
151        self.get(name).map(|entry| entry.0.clone())
152    }
153    #[inline(always)]
154    fn is_empty(&self) -> bool {
155        self.is_empty()
156    }
157}
158
159/// Empty glossary that does not contain any aliases
160#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
161pub struct EmptyGlossary;
162
163impl Glossary for EmptyGlossary {
164    #[inline(always)]
165    fn look_up(&self, _name: &str) -> Option<Rc<Alias>> {
166        None
167    }
168    #[inline(always)]
169    fn is_empty(&self) -> bool {
170        true
171    }
172}
173
174/// Allows a glossary to be wrapped in a `RefCell`.
175///
176/// This implementation's methods immutably borrow the inner glossary.
177/// If the inner glossary is mutably borrowed at the same time, it panics.
178impl<T: Glossary> Glossary for RefCell<T> {
179    fn look_up(&self, name: &str) -> Option<Rc<Alias>> {
180        self.borrow().look_up(name)
181    }
182    fn is_empty(&self) -> bool {
183        self.borrow().is_empty()
184    }
185}
186
187/// Allows to look up aliases in the environment.
188///
189/// This implementation delegates to `self.aliases`.
190impl Glossary for Env {
191    #[inline(always)]
192    fn look_up(&self, name: &str) -> Option<Rc<Alias>> {
193        self.aliases.look_up(name)
194    }
195    #[inline(always)]
196    fn is_empty(&self) -> bool {
197        self.aliases.is_empty()
198    }
199}