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