yash_env/builtin.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 built-in utilities
18//!
19//! This module provides data types for defining built-in utilities.
20//!
21//! Note that concrete implementations of built-ins are not included in the
22//! `yash_env` crate. For implementations of specific built-ins like `cd` and
23//! `export`, see the `yash_builtin` crate.
24
25use crate::Env;
26#[cfg(doc)]
27use crate::semantics::Divert;
28use crate::semantics::ExitStatus;
29use crate::semantics::Field;
30use std::fmt::Debug;
31use std::pin::Pin;
32
33/// Types of built-in utilities
34#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
35pub enum Type {
36 /// Special built-in
37 ///
38 /// Special built-in utilities are built-ins that are defined in [POSIX XCU
39 /// section 2.15](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_15).
40 ///
41 /// They are treated differently from other built-ins.
42 /// Especially, special built-ins are found in the first stage of command
43 /// search without the `$PATH` search and cannot be overridden by functions
44 /// or external utilities.
45 /// Many errors in special built-ins force the shell to exit.
46 Special,
47
48 /// Standard utility that can be used without `$PATH` search
49 ///
50 /// Mandatory built-ins are utilities that are listed in [POSIX XCU section
51 /// 1.7](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap01.html#tag_18_07).
52 /// In POSIX, they are called "intrinsic utilities".
53 ///
54 /// Like special built-ins, mandatory built-ins are not subject to `$PATH`
55 /// in command search; They are always found regardless of whether there is
56 /// a corresponding external utility in `$PATH`. However, mandatory
57 /// built-ins can still be overridden by functions.
58 ///
59 /// We call them "mandatory" because POSIX effectively requires them to be
60 /// built into the shell.
61 Mandatory,
62
63 /// Non-portable built-in that can be used without `$PATH` search
64 ///
65 /// Elective built-ins are built-ins that are listed in step 1b of [Command
66 /// Search and Execution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_01_04)
67 /// in POSIX XCU section 2.9.1.4.
68 /// They are very similar to mandatory built-ins, but their behavior is not
69 /// specified by POSIX, so they are not portable. They cannot be used when
70 /// the (TODO TBD) option is set. <!-- An option that disables non-portable
71 /// behavior would make elective built-ins unusable even if found. An option
72 /// that disables non-conforming behavior would not affect elective
73 /// built-ins. -->
74 ///
75 /// We call them "elective" because it is up to the shell whether to
76 /// implement them.
77 Elective,
78
79 /// Non-conforming extension
80 ///
81 /// Extension built-ins are non-conformant extensions to the POSIX shell.
82 /// Like elective built-ins, they can be executed without `$PATH` search
83 /// finding a corresponding external utility. However, since this behavior
84 /// does not conform to [Command
85 /// Search and Execution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_01_04)
86 /// in POSIX XCU section 2.9.1.4, they cannot be used when the (TODO TBD)
87 /// option is set. <!-- An option that disables non-conforming behavior
88 /// would make extension built-ins regarded as non-existing utilities. An
89 /// option that disables non-portable behavior would make extension
90 /// built-ins unusable even if found. -->
91 Extension,
92
93 /// Built-in that works like a standalone utility
94 ///
95 /// A substitutive built-in is a built-in that is executed instead of an
96 /// external utility to minimize invocation overhead. Since a substitutive
97 /// built-in behaves just as if it were an external utility, it must be
98 /// found in `$PATH` in order to be executed.
99 Substitutive,
100}
101
102/// Result of built-in utility execution
103///
104/// The result type contains an exit status and optional flags that may affect
105/// the behavior of the shell following the built-in execution.
106#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
107#[must_use]
108pub struct Result {
109 exit_status: ExitStatus,
110 divert: crate::semantics::Result,
111 should_retain_redirs: bool,
112}
113
114impl Result {
115 /// Creates a new result.
116 pub const fn new(exit_status: ExitStatus) -> Self {
117 Self {
118 exit_status,
119 divert: crate::semantics::Result::Continue(()),
120 should_retain_redirs: false,
121 }
122 }
123
124 /// Creates a new result with a [`Divert`].
125 #[inline]
126 pub const fn with_exit_status_and_divert(
127 exit_status: ExitStatus,
128 divert: crate::semantics::Result,
129 ) -> Self {
130 Self {
131 exit_status,
132 divert,
133 should_retain_redirs: false,
134 }
135 }
136
137 /// Returns the exit status of this result.
138 ///
139 /// The return value is the argument to the previous invocation of
140 /// [`new`](Self::new) or [`set_exit_status`](Self::set_exit_status).
141 #[inline]
142 #[must_use]
143 pub const fn exit_status(&self) -> ExitStatus {
144 self.exit_status
145 }
146
147 /// Sets the exit status of this result.
148 ///
149 /// See [`exit_status`](Self::exit_status()).
150 #[inline]
151 pub fn set_exit_status(&mut self, exit_status: ExitStatus) {
152 self.exit_status = exit_status
153 }
154
155 /// Returns an optional [`Divert`] to be taken.
156 ///
157 /// The return value is the argument to the previous invocation of
158 /// [`set_divert`](Self::set_divert). The default is `Continue(())`.
159 #[inline]
160 pub const fn divert(&self) -> crate::semantics::Result {
161 self.divert
162 }
163
164 /// Sets a [`Divert`].
165 ///
166 /// See [`divert`](Self::divert()).
167 #[inline]
168 pub fn set_divert(&mut self, divert: crate::semantics::Result) {
169 self.divert = divert;
170 }
171
172 /// Tests whether the caller should retain redirections.
173 ///
174 /// Usually, the shell reverts redirections applied to a built-in after
175 /// executing it. However, redirections applied to a successful `exec`
176 /// built-in should persist. To make it happen, the `exec` built-in calls
177 /// [`retain_redirs`](Self::retain_redirs), and this function returns true.
178 /// In that case, the caller of the built-in should take appropriate actions
179 /// to preserve the effect of the redirections.
180 #[inline]
181 pub const fn should_retain_redirs(&self) -> bool {
182 self.should_retain_redirs
183 }
184
185 /// Flags that redirections applied to the built-in should persist.
186 ///
187 /// Calling this function makes
188 /// [`should_retain_redirs`](Self::should_retain_redirs) return true.
189 /// [`clear_redirs`](Self::clear_redirs) cancels the effect of this
190 /// function.
191 #[inline]
192 pub fn retain_redirs(&mut self) {
193 self.should_retain_redirs = true;
194 }
195
196 /// Cancels the effect of [`retain_redirs`](Self::retain_redirs).
197 #[inline]
198 pub fn clear_redirs(&mut self) {
199 self.should_retain_redirs = false;
200 }
201
202 /// Merges two results by taking the maximum of each field.
203 pub fn max(self, other: Self) -> Self {
204 use std::ops::ControlFlow::{Break, Continue};
205 let divert = match (self.divert, other.divert) {
206 (Continue(()), other) => other,
207 (other, Continue(())) => other,
208 (Break(left), Break(right)) => Break(left.max(right)),
209 };
210
211 Self {
212 exit_status: self.exit_status.max(other.exit_status),
213 divert,
214 should_retain_redirs: self.should_retain_redirs.max(other.should_retain_redirs),
215 }
216 }
217}
218
219impl Default for Result {
220 #[inline]
221 fn default() -> Self {
222 Self::new(ExitStatus::default())
223 }
224}
225
226impl From<ExitStatus> for Result {
227 #[inline]
228 fn from(exit_status: ExitStatus) -> Self {
229 Self::new(exit_status)
230 }
231}
232
233/// Type of functions that implement the behavior of a built-in
234///
235/// The function takes two arguments.
236/// The first is an environment in which the built-in is executed.
237/// The second is arguments to the built-in
238/// (not including the leading command name word).
239pub type Main = fn(&mut Env, Vec<Field>) -> Pin<Box<dyn Future<Output = Result> + '_>>;
240
241/// Built-in utility definition
242///
243/// # Notes on equality
244///
245/// Although this type implements `PartialEq`, comparison between instances of
246/// this type may not always yield predictable results due to the presence of
247/// function pointers. As a result, it is recommended to avoid relying on
248/// equality comparisons for values of this type. See
249/// <https://doc.rust-lang.org/std/ptr/fn.fn_addr_eq.html> for the
250/// characteristics of function pointer comparisons.
251#[allow(unpredictable_function_pointer_comparisons)]
252#[derive(Clone, Copy, Eq, Hash, PartialEq)]
253#[non_exhaustive]
254pub struct Builtin {
255 /// Type of the built-in
256 pub r#type: Type,
257
258 /// Function that implements the behavior of the built-in
259 pub execute: Main,
260
261 /// Whether the built-in is a declaration utility
262 ///
263 /// The [`decl_util::Glossary`](crate::decl_util::Glossary) implementation
264 /// for [`Env`] uses this field to determine whether a command name is a
265 /// declaration utility. See the [method description] for the value this
266 /// field should have.
267 ///
268 /// [method description]: crate::decl_util::Glossary::is_declaration_utility
269 pub is_declaration_utility: Option<bool>,
270}
271
272impl Debug for Builtin {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 f.debug_struct("Builtin")
275 .field("type", &self.r#type)
276 .field("is_declaration_utility", &self.is_declaration_utility)
277 .finish_non_exhaustive()
278 }
279}
280
281impl Builtin {
282 /// Creates a new built-in utility definition.
283 ///
284 /// The `type` and `execute` fields are set to the given arguments.
285 /// The `is_declaration_utility` field is set to `Some(false)`, indicating
286 /// that the built-in is not a declaration utility.
287 pub const fn new(r#type: Type, execute: Main) -> Self {
288 Self {
289 r#type,
290 execute,
291 is_declaration_utility: Some(false),
292 }
293 }
294}