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}