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}