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_number`] 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 number corresponding to the exit status.
116    ///
117    /// Basically, this function is the inverse of the `From<signal::Number>`
118    /// implementation for `ExitStatus`. It tries to find a signal number by
119    /// offsetting the exit status by 384. If the offsetting does not result in a
120    /// valid signal number, it additionally tries with 128 and 0.
121    #[must_use]
122    pub fn to_signal_number<S: System>(self, system: &S) -> Option<signal::Number> {
123        [0x180, 0x80, 0]
124            .into_iter()
125            .filter_map(|offset| self.0.checked_sub(offset))
126            .filter_map(|raw_number| system.validate_signal(raw_number))
127            .next()
128            .map(|(_name, number)| number)
129    }
130}
131
132/// Converts the exit status to `ExitCode`.
133///
134/// Note that `ExitCode` only supports exit statuses in the range of 0 to 255.
135/// Only the lowest 8 bits of the exit status are used in the conversion.
136impl Termination for ExitStatus {
137    fn report(self) -> ExitCode {
138        (self.0 as u8).into()
139    }
140}
141
142impl ExitStatus {
143    /// Exit status of 0: success.
144    pub const SUCCESS: ExitStatus = ExitStatus(0);
145
146    /// Exit status of 1: failure.
147    pub const FAILURE: ExitStatus = ExitStatus(1);
148
149    /// Exit status of 2: error severer than failure.
150    pub const ERROR: ExitStatus = ExitStatus(2);
151
152    /// Exit Status of 126: command not executable.
153    pub const NOEXEC: ExitStatus = ExitStatus(126);
154
155    /// Exit status of 127: command not found.
156    pub const NOT_FOUND: ExitStatus = ExitStatus(127);
157
158    /// Returns true if and only if `self` is zero.
159    pub const fn is_successful(&self) -> bool {
160        self.0 == 0
161    }
162}
163
164/// Result of interrupted command execution.
165///
166/// `Divert` implements `Ord`. Values are ordered by severity.
167#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
168pub enum Divert {
169    /// Continue the current loop.
170    Continue {
171        /// Number of loops to break before continuing.
172        ///
173        /// `0` for continuing the innermost loop, `1` for one-level outer, and so on.
174        count: usize,
175    },
176
177    /// Break the current loop.
178    Break {
179        /// Number of loops to break.
180        ///
181        /// `0` for breaking the innermost loop, `1` for one-level outer, and so on.
182        count: usize,
183    },
184
185    /// Return from the current function or script.
186    Return(Option<ExitStatus>),
187
188    /// Interrupt the current shell execution environment.
189    ///
190    /// This is the same as `Exit` in a non-interactive shell: it makes the
191    /// shell exit after executing the EXIT trap, if any. If this is used inside
192    /// the EXIT trap, the shell will exit immediately.
193    ///
194    /// In an interactive shell, this will abort the currently executed command
195    /// and resume prompting for a next command line.
196    Interrupt(Option<ExitStatus>),
197
198    /// Exit from the current shell execution environment.
199    ///
200    /// This makes the shell exit after executing the EXIT trap, if any.
201    /// If this is used inside the EXIT trap, the shell will exit immediately.
202    Exit(Option<ExitStatus>),
203
204    /// Exit from the current shell execution environment immediately.
205    ///
206    /// This makes the shell exit without executing the EXIT trap.
207    Abort(Option<ExitStatus>),
208}
209
210impl Divert {
211    /// Returns the exit status associated with the `Divert`.
212    ///
213    /// Returns the variant's value if `self` is `Exit` or `Interrupt`;
214    /// otherwise, `None`.
215    pub fn exit_status(&self) -> Option<ExitStatus> {
216        use Divert::*;
217        match self {
218            Continue { .. } | Break { .. } => None,
219            Return(exit_status)
220            | Interrupt(exit_status)
221            | Exit(exit_status)
222            | Abort(exit_status) => *exit_status,
223        }
224    }
225}
226
227/// Result of command execution.
228///
229/// If the command was interrupted in the middle of execution, the result value
230/// will be a `Break` having a [`Divert`] value which specifies what to execute
231/// next.
232pub type Result<T = ()> = ControlFlow<Divert, T>;
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use crate::system::r#virtual::VirtualSystem;
238    use crate::system::r#virtual::{SIGINT, SIGTERM};
239
240    #[test]
241    fn exit_status_to_signal_number() {
242        let system = VirtualSystem::new();
243
244        assert_eq!(ExitStatus(0).to_signal_number(&system), None);
245
246        assert_eq!(
247            ExitStatus(SIGINT.as_raw()).to_signal_number(&system),
248            Some(SIGINT)
249        );
250        assert_eq!(
251            ExitStatus::from(SIGINT).to_signal_number(&system),
252            Some(SIGINT)
253        );
254
255        let mut exit_status = ExitStatus::from(SIGTERM);
256        assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
257        exit_status.0 &= 0xFF;
258        assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
259        exit_status.0 &= 0x7F;
260        assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
261    }
262}