yash_env/
semantics.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//! Type definitions for command execution.
18
19use crate::Env;
20use crate::signal;
21use crate::source::Location;
22use crate::system::System;
23use std::cell::RefCell;
24use std::ffi::c_int;
25use std::ops::ControlFlow;
26use std::pin::Pin;
27use std::process::ExitCode;
28use std::process::Termination;
29
30/// Resultant string of word expansion.
31///
32/// A field is a string accompanied with the original word location.
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct Field {
35    /// String value of the field.
36    pub value: String,
37    /// Location of the word this field resulted from.
38    pub origin: Location,
39}
40
41impl Field {
42    /// Creates a new field with a dummy origin location.
43    ///
44    /// The value of the resulting field will be `value.into()`.
45    /// The origin of the field will be created by [`Location::dummy`] with a
46    /// clone of the value.
47    #[inline]
48    pub fn dummy<S: Into<String>>(value: S) -> Field {
49        fn with_value(value: String) -> Field {
50            let origin = Location::dummy(value.clone());
51            Field { value, origin }
52        }
53        with_value(value.into())
54    }
55
56    /// Creates an array of fields with dummy origin locations.
57    ///
58    /// This function calls [`dummy`](Self::dummy) to create the results.
59    pub fn dummies<I, S>(values: I) -> Vec<Field>
60    where
61        I: IntoIterator<Item = S>,
62        S: Into<String>,
63    {
64        values.into_iter().map(Self::dummy).collect()
65    }
66}
67
68impl std::fmt::Display for Field {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        self.value.fmt(f)
71    }
72}
73
74/// Number that summarizes the result of command execution.
75///
76/// An exit status is an integer returned from a utility (or command) when
77/// executed. It usually is a summarized result of the execution.  Many
78/// utilities return an exit status of zero when successful and non-zero
79/// otherwise.
80///
81/// In the shell language, the special parameter `$?` expands to the exit status
82/// of the last executed command. Exit statuses also affect the behavior of some
83/// compound commands.
84#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
85pub struct ExitStatus(pub c_int);
86
87impl std::fmt::Display for ExitStatus {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        self.0.fmt(f)
90    }
91}
92
93impl From<c_int> for ExitStatus {
94    fn from(value: c_int) -> ExitStatus {
95        ExitStatus(value)
96    }
97}
98
99impl From<ExitStatus> for c_int {
100    fn from(exit_status: ExitStatus) -> c_int {
101        exit_status.0
102    }
103}
104
105/// Converts a signal number to the corresponding exit status.
106///
107/// POSIX requires the exit status to be greater than 128. The current
108/// implementation returns `signal_number + 384`.
109///
110/// See [`ExitStatus::to_signal`] for the reverse conversion.
111impl From<signal::Number> for ExitStatus {
112    fn from(number: signal::Number) -> Self {
113        Self::from(number.as_raw() + 0x180)
114    }
115}
116
117impl ExitStatus {
118    /// Returns the signal name and number corresponding to the exit status.
119    ///
120    /// This function is the inverse of the `From<signal::Number>` implementation
121    /// for `ExitStatus`. It tries to find a signal name and number by offsetting
122    /// the exit status by 384. If the offsetting does not result in a valid signal
123    /// name and number, it additionally tries with 128 and 0 unless `exact` is
124    /// `true`.
125    ///
126    /// If `self` is not a valid signal exit status, this function returns `None`.
127    #[must_use]
128    pub fn to_signal<S: System + ?Sized>(
129        self,
130        system: &S,
131        exact: bool,
132    ) -> Option<(signal::Name, signal::Number)> {
133        fn convert<S: System + ?Sized>(
134            exit_status: ExitStatus,
135            offset: c_int,
136            system: &S,
137        ) -> Option<(signal::Name, signal::Number)> {
138            let number = exit_status.0.checked_sub(offset)?;
139            system.validate_signal(number)
140        }
141
142        if let Some(signal) = convert(self, 0x180, system) {
143            return Some(signal);
144        }
145        if exact {
146            return None;
147        }
148        if let Some(signal) = convert(self, 0x80, system) {
149            return Some(signal);
150        }
151        if let Some(signal) = convert(self, 0, system) {
152            return Some(signal);
153        }
154        None
155    }
156}
157
158/// Converts the exit status to `ExitCode`.
159///
160/// Note that `ExitCode` only supports exit statuses in the range of 0 to 255.
161/// Only the lowest 8 bits of the exit status are used in the conversion.
162impl Termination for ExitStatus {
163    fn report(self) -> ExitCode {
164        (self.0 as u8).into()
165    }
166}
167
168impl ExitStatus {
169    /// Exit status of 0: success
170    pub const SUCCESS: ExitStatus = ExitStatus(0);
171
172    /// Exit status of 1: failure
173    pub const FAILURE: ExitStatus = ExitStatus(1);
174
175    /// Exit status of 2: error severer than failure
176    pub const ERROR: ExitStatus = ExitStatus(2);
177
178    /// Exit Status of 126: command not executable
179    pub const NOEXEC: ExitStatus = ExitStatus(126);
180
181    /// Exit status of 127: command not found
182    pub const NOT_FOUND: ExitStatus = ExitStatus(127);
183
184    /// Exit status of 128: unrecoverable read error
185    pub const READ_ERROR: ExitStatus = ExitStatus(128);
186
187    /// Returns true if and only if `self` is zero.
188    pub const fn is_successful(&self) -> bool {
189        self.0 == 0
190    }
191}
192
193/// Result of interrupted command execution.
194///
195/// `Divert` implements `Ord`. Values are ordered by severity.
196#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
197pub enum Divert {
198    /// Continue the current loop.
199    Continue {
200        /// Number of loops to break before continuing.
201        ///
202        /// `0` for continuing the innermost loop, `1` for one-level outer, and so on.
203        count: usize,
204    },
205
206    /// Break the current loop.
207    Break {
208        /// Number of loops to break.
209        ///
210        /// `0` for breaking the innermost loop, `1` for one-level outer, and so on.
211        count: usize,
212    },
213
214    /// Return from the current function or script.
215    Return(Option<ExitStatus>),
216
217    /// Interrupt the current shell execution environment.
218    ///
219    /// This is the same as `Exit` in a non-interactive shell: it makes the
220    /// shell exit after executing the EXIT trap, if any. If this is used inside
221    /// the EXIT trap, the shell will exit immediately.
222    ///
223    /// In an interactive shell, this will abort the currently executed command
224    /// and resume prompting for a next command line.
225    Interrupt(Option<ExitStatus>),
226
227    /// Exit from the current shell execution environment.
228    ///
229    /// This makes the shell exit after executing the EXIT trap, if any.
230    /// If this is used inside the EXIT trap, the shell will exit immediately.
231    Exit(Option<ExitStatus>),
232
233    /// Exit from the current shell execution environment immediately.
234    ///
235    /// This makes the shell exit without executing the EXIT trap.
236    Abort(Option<ExitStatus>),
237}
238
239impl Divert {
240    /// Returns the exit status associated with the `Divert`.
241    ///
242    /// Returns the variant's value if `self` is `Exit` or `Interrupt`;
243    /// otherwise, `None`.
244    pub fn exit_status(&self) -> Option<ExitStatus> {
245        use Divert::*;
246        match self {
247            Continue { .. } | Break { .. } => None,
248            Return(exit_status)
249            | Interrupt(exit_status)
250            | Exit(exit_status)
251            | Abort(exit_status) => *exit_status,
252        }
253    }
254}
255
256/// Result of command execution.
257///
258/// If the command was interrupted in the middle of execution, the result value
259/// will be a `Break` having a [`Divert`] value which specifies what to execute
260/// next.
261pub type Result<T = ()> = ControlFlow<Divert, T>;
262
263type PinFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
264
265/// Wrapper for running a read-eval-loop
266///
267/// This struct declares a function type for running a read-eval-loop. An
268/// implementation of this function type should be provided and stored in the
269/// environment's [`any`](Env::any) storage so that it can be used by modules
270/// depending on read-eval-loop execution.
271///
272/// The function takes two arguments. The first argument is a mutable reference
273/// to an environment wrapped in a [`RefCell`]. The second argument is a
274/// [configuration](crate::parser::Config) for the parser, which contains the
275/// [input function](crate::input::Input) and source information. The
276/// configuration will be used by the function to create a lexer for reading
277/// commands.
278///
279/// Note that the `RefCell` is passed as a shared reference. It can be shared
280/// with the input function, allowing the input function to access and modify
281/// the environment while reading commands. The input function must release
282/// the borrow on the `RefCell` before returning control to the caller so that
283/// the caller can borrow the `RefCell` mutably to execute commands read from
284/// the parser.
285///
286/// The function returns a future which resolves to a [`Result`] when awaited.
287/// The function should execute commands read from the lexer until the end of
288/// input or a [`Divert`] is encountered.
289///
290/// The function should set [`Env::exit_status`] appropriately after the loop
291/// ends. If the input contains no commands, the exit status should be set to
292/// `ExitStatus(0)`.
293///
294/// The function should also
295/// [update subshell statuses](Env::update_all_subshell_statuses) and handle
296/// traps during the loop execution as specified in the shell semantics.
297///
298/// The most standard implementation of this function type is provided in the
299/// [`yash-semantics` crate](https://crates.io/crates/yash-semantics):
300///
301/// ```
302/// # use yash_env::semantics::RunReadEvalLoop;
303/// let mut env = yash_env::Env::new_virtual();
304/// env.any.insert(Box::new(RunReadEvalLoop(|env, config| {
305///     Box::pin(async move {
306///         yash_semantics::read_eval_loop(env, &mut config.into()).await
307///     })
308/// })));
309/// ```
310#[derive(Clone, Copy, Debug)]
311pub struct RunReadEvalLoop(
312    pub for<'a> fn(&'a RefCell<&mut Env>, crate::parser::Config<'a>) -> PinFuture<'a, Result>,
313);
314
315pub mod command;
316pub mod expansion;
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use crate::system::r#virtual::VirtualSystem;
322    use crate::system::r#virtual::{SIGINT, SIGTERM};
323
324    #[test]
325    fn exit_status_to_signal() {
326        let system = VirtualSystem::new();
327
328        assert_eq!(ExitStatus(0).to_signal(&system, false), None);
329        assert_eq!(ExitStatus(0).to_signal(&system, true), None);
330
331        assert_eq!(
332            ExitStatus(SIGINT.as_raw()).to_signal(&system, false),
333            Some((signal::Name::Int, SIGINT))
334        );
335        assert_eq!(ExitStatus(SIGINT.as_raw()).to_signal(&system, true), None);
336
337        assert_eq!(
338            ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, false),
339            Some((signal::Name::Int, SIGINT))
340        );
341        assert_eq!(
342            ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, true),
343            None
344        );
345
346        assert_eq!(
347            ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, false),
348            Some((signal::Name::Int, SIGINT))
349        );
350        assert_eq!(
351            ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, true),
352            Some((signal::Name::Int, SIGINT))
353        );
354
355        assert_eq!(
356            ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, false),
357            Some((signal::Name::Term, SIGTERM))
358        );
359        assert_eq!(
360            ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, true),
361            Some((signal::Name::Term, SIGTERM))
362        );
363    }
364}