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}