conch_runtime_pshaw/
eval.rs

1//! A module for evaluating arbitrary shell components such as words,
2//! parameter subsitutions, redirections, and others.
3
4use crate::env::StringWrapper;
5use crate::error::ExpansionError;
6use futures_core::future::BoxFuture;
7
8mod assignment;
9mod concat;
10mod double_quoted;
11mod fields;
12mod param_subst;
13mod redirect;
14mod redirect_or_cmd_word;
15mod redirect_or_var_assig;
16
17#[cfg(feature = "conch-parser")]
18pub mod ast_impl;
19
20pub use self::assignment::eval_as_assignment;
21pub use self::concat::concat;
22pub use self::double_quoted::double_quoted;
23pub use self::fields::Fields;
24pub use self::param_subst::{alternative, assign, default, error, len};
25pub use self::param_subst::{
26    remove_largest_prefix, remove_largest_suffix, remove_smallest_prefix, remove_smallest_suffix,
27};
28pub use self::redirect::{
29    redirect_append, redirect_clobber, redirect_dup_read, redirect_dup_write, redirect_heredoc,
30    redirect_read, redirect_readwrite, redirect_write, RedirectAction, RedirectEval,
31};
32pub use self::redirect_or_cmd_word::{
33    eval_redirects_or_cmd_words_with_restorer, EvalRedirectOrCmdWordError, RedirectOrCmdWord,
34};
35pub use self::redirect_or_var_assig::{
36    eval_redirects_or_var_assignments_with_restorer, EvalRedirectOrVarAssigError,
37    RedirectOrVarAssig,
38};
39
40/// A trait for evaluating parameters.
41pub trait ParamEval<E: ?Sized> {
42    /// The underlying representation of the evaulation type (e.g. `String`, `Rc<String>`).
43    type EvalResult: StringWrapper;
44
45    /// Evaluates a parameter in the context of some environment,
46    /// optionally splitting fields.
47    ///
48    /// A `None` value indicates that the parameter is unset.
49    fn eval(&self, split_fields_further: bool, env: &E) -> Option<Fields<Self::EvalResult>>;
50
51    /// Returns the (variable) name of the parameter to be used for assignments, if applicable.
52    fn assig_name(&self) -> Option<Self::EvalResult>;
53}
54
55impl<'a, E: ?Sized, P: ?Sized + ParamEval<E>> ParamEval<E> for &'a P {
56    type EvalResult = P::EvalResult;
57
58    fn eval(&self, split_fields_further: bool, env: &E) -> Option<Fields<Self::EvalResult>> {
59        (**self).eval(split_fields_further, env)
60    }
61
62    fn assig_name(&self) -> Option<Self::EvalResult> {
63        (**self).assig_name()
64    }
65}
66
67/// A trait for evaluating arithmetic expansions.
68pub trait ArithEval<E: ?Sized> {
69    /// Evaluates an arithmetic expression in the context of an environment.
70    ///
71    /// A mutable reference to the environment is needed since an arithmetic
72    /// expression could mutate environment variables.
73    fn eval(&self, env: &mut E) -> Result<isize, ExpansionError>;
74}
75
76impl<'a, T: ?Sized + ArithEval<E>, E: ?Sized> ArithEval<E> for &'a T {
77    fn eval(&self, env: &mut E) -> Result<isize, ExpansionError> {
78        (**self).eval(env)
79    }
80}
81
82/// An enum representing how tildes (`~`) are expanded.
83#[derive(PartialEq, Eq, Copy, Clone, Debug)]
84pub enum TildeExpansion {
85    /// Tildes retain a literal value, no expansion is done.
86    None,
87    /// Tildes are expanded if they are at the beginning of a word.
88    First,
89    /// All tildes (either at start of word or after `:`) are expanded.
90    All,
91}
92
93/// A config object for customizing `WordEval` evaluations.
94#[derive(PartialEq, Eq, Copy, Clone, Debug)]
95pub struct WordEvalConfig {
96    /// Configure tilde expansion.
97    pub tilde_expansion: TildeExpansion,
98    /// Perform field splitting where appropriate or not.
99    pub split_fields_further: bool,
100}
101
102/// A convenience trait representing the result of a word evaluation.
103pub type WordEvalResult<T, E> = Result<BoxFuture<'static, Fields<T>>, E>;
104
105/// A trait for evaluating shell words with various rules for expansion.
106pub trait WordEval<E: ?Sized> {
107    /// The underlying representation of the evaulation type (e.g. `String`, `Arc<String>`).
108    type EvalResult: StringWrapper;
109    /// An error that can arise during evaluation.
110    type Error;
111
112    /// Evaluates a word in a given environment and performs all expansions.
113    ///
114    /// Tilde, parameter, command substitution, and arithmetic expansions are
115    /// performed first. All resulting fields are then further split based on
116    /// the contents of the `IFS` variable (no splitting is performed if `IFS`
117    /// is set to be the empty or null string). Finally, quotes and escaping
118    /// backslashes are removed from the original word (unless they themselves
119    /// have been quoted).
120    fn eval<'life0, 'life1, 'async_trait>(
121        &'life0 self,
122        env: &'life1 mut E,
123    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
124    where
125        'life0: 'async_trait,
126        'life1: 'async_trait,
127        Self: 'async_trait,
128    {
129        self.eval_with_config(
130            env,
131            WordEvalConfig {
132                tilde_expansion: TildeExpansion::First,
133                split_fields_further: true,
134            },
135        )
136    }
137
138    /// Evaluate and take a provided config into account.
139    ///
140    /// Generally `$*` should always be joined by the first char of `$IFS` or have all
141    /// fields concatenated if `$IFS` is null or `$*` is in double quotes.
142    ///
143    /// If `cfg.split_fields_further` is false then all empty fields will be kept.
144    ///
145    /// The caller is responsible for doing path expansions.
146    fn eval_with_config<'life0, 'life1, 'async_trait>(
147        &'life0 self,
148        env: &'life1 mut E,
149        cfg: WordEvalConfig,
150    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
151    where
152        'life0: 'async_trait,
153        'life1: 'async_trait,
154        Self: 'async_trait;
155}
156
157impl<'a, T, E> WordEval<E> for &'a T
158where
159    T: ?Sized + WordEval<E>,
160    E: ?Sized,
161{
162    type EvalResult = T::EvalResult;
163    type Error = T::Error;
164
165    fn eval<'life0, 'life1, 'async_trait>(
166        &'life0 self,
167        env: &'life1 mut E,
168    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
169    where
170        'life0: 'async_trait,
171        'life1: 'async_trait,
172        Self: 'async_trait,
173    {
174        (**self).eval(env)
175    }
176
177    fn eval_with_config<'life0, 'life1, 'async_trait>(
178        &'life0 self,
179        env: &'life1 mut E,
180        cfg: WordEvalConfig,
181    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
182    where
183        'life0: 'async_trait,
184        'life1: 'async_trait,
185        Self: 'async_trait,
186    {
187        (**self).eval_with_config(env, cfg)
188    }
189}
190
191impl<T, E> WordEval<E> for Box<T>
192where
193    T: ?Sized + WordEval<E>,
194    E: ?Sized,
195{
196    type EvalResult = T::EvalResult;
197    type Error = T::Error;
198
199    fn eval<'life0, 'life1, 'async_trait>(
200        &'life0 self,
201        env: &'life1 mut E,
202    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
203    where
204        'life0: 'async_trait,
205        'life1: 'async_trait,
206        Self: 'async_trait,
207    {
208        (**self).eval(env)
209    }
210
211    fn eval_with_config<'life0, 'life1, 'async_trait>(
212        &'life0 self,
213        env: &'life1 mut E,
214        cfg: WordEvalConfig,
215    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
216    where
217        'life0: 'async_trait,
218        'life1: 'async_trait,
219        Self: 'async_trait,
220    {
221        (**self).eval_with_config(env, cfg)
222    }
223}
224
225impl<T, E> WordEval<E> for std::sync::Arc<T>
226where
227    T: ?Sized + WordEval<E>,
228    E: ?Sized,
229{
230    type EvalResult = T::EvalResult;
231    type Error = T::Error;
232
233    fn eval<'life0, 'life1, 'async_trait>(
234        &'life0 self,
235        env: &'life1 mut E,
236    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
237    where
238        'life0: 'async_trait,
239        'life1: 'async_trait,
240        Self: 'async_trait,
241    {
242        (**self).eval(env)
243    }
244
245    fn eval_with_config<'life0, 'life1, 'async_trait>(
246        &'life0 self,
247        env: &'life1 mut E,
248        cfg: WordEvalConfig,
249    ) -> BoxFuture<'async_trait, WordEvalResult<Self::EvalResult, Self::Error>>
250    where
251        'life0: 'async_trait,
252        'life1: 'async_trait,
253        Self: 'async_trait,
254    {
255        (**self).eval_with_config(env, cfg)
256    }
257}
258
259// Evaluate a word as a pattern. Note this is not a public API since there needs to be a
260// better abstraction for allowing consumers to override/define patterns (i.e. don't
261// tie ourselves to `glob`).
262pub(crate) async fn eval_as_pattern<W, E>(word: W, env: &mut E) -> Result<glob::Pattern, W::Error>
263where
264    W: WordEval<E>,
265    E: ?Sized,
266{
267    let future = word.eval_with_config(
268        env,
269        WordEvalConfig {
270            tilde_expansion: TildeExpansion::First,
271            split_fields_further: false,
272        },
273    );
274
275    // FIXME: "intelligently" compile the pattern here
276    // Other shells will treat certain glob "errors" (like unmatched char groups)
277    // as just literal values. Also it would be interesting to explore treating
278    // variables/interpolated values as literals unconditionally (i.e. glob
279    // special chars like *, !, ?, etc. would only have special meaning if they
280    // appear in the original source). Unfortunately, this future doesn't appear
281    // flexible enough to accomplish that (the actual word itself needs to
282    // determine what is special and what isn't at each step), so this may
283    // need to move into its own trait (right now WordEval *must* return a
284    // Pattern future).
285    let pat = future.await?.await.join();
286    let pat = glob::Pattern::new(pat.as_str())
287        .or_else(|_| glob::Pattern::new(&glob::Pattern::escape(pat.as_str())))
288        .expect("pattern compilation unexpectedly failed");
289    Ok(pat)
290}