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