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}