yash_env/
stack.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2022 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//! Runtime execution context stack
18//!
19//! The ["stack"](Stack) traces the state of execution context at runtime.
20//! For example, when entering a subshell, the runner pushes `Frame::Subshell`
21//! to the stack. By examining the stack, commands executed in the subshell can
22//! detect that they are inside the subshell.
23//!
24//! This module provides guards to ensure stack frames are pushed and popped
25//! correctly. The push function returns a guard that will pop the frame when
26//! dropped. Implementing `Deref` and `DerefMut`, the guard allows access to the
27//! borrowed stack or environment.
28//!
29//! [`Stack::push`] returns a [`StackFrameGuard`] that allows re-borrowing the
30//! `Stack`. [`Env::push_frame`] returns a [`EnvFrameGuard`] that implements
31//! `DerefMut<Target = Env>`.
32
33use crate::Env;
34use crate::semantics::Field;
35use std::ops::Deref;
36use std::ops::DerefMut;
37
38/// Information about the currently executing built-in
39///
40/// An instance of `Builtin` wrapped in a [`Frame::Builtin`] is pushed to the
41/// stack when executing a built-in.
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct Builtin {
44    /// Name of the built-in
45    pub name: Field,
46
47    /// Whether the utility acts as a special built-in
48    ///
49    /// This value determines whether an error in the built-in interrupts the
50    /// shell. This will be false if a special built-in is executed through the
51    /// `command` built-in.
52    pub is_special: bool,
53}
54
55/// Element of runtime execution context stack
56#[derive(Clone, Debug, Eq, PartialEq)]
57#[non_exhaustive]
58pub enum Frame {
59    /// For, while, or until loop
60    Loop,
61
62    /// Subshell
63    Subshell,
64
65    /// Context where the `ErrExit` [option](crate::option::Option) is ignored
66    ///
67    /// This frame is pushed when executing negated commands, the condition part
68    /// of and-or lists and the `if`, `while`, and `until` commands.
69    Condition,
70
71    /// Built-in utility
72    Builtin(Builtin),
73
74    /// Shell script file executed by the `.` built-in
75    DotScript,
76
77    /// Trap
78    Trap(crate::trap::Condition),
79
80    // TODO function
81    /// File executed during shell startup
82    InitFile,
83}
84
85impl From<Builtin> for Frame {
86    fn from(builtin: Builtin) -> Self {
87        Frame::Builtin(builtin)
88    }
89}
90
91impl From<crate::trap::Condition> for Frame {
92    fn from(condition: crate::trap::Condition) -> Self {
93        Frame::Trap(condition)
94    }
95}
96
97/// Runtime execution context stack
98///
99/// You can access the inner vector of the stack via the `Deref` implementation.
100#[derive(Clone, Debug, Default, Eq, PartialEq)]
101pub struct Stack {
102    inner: Vec<Frame>,
103}
104
105impl Deref for Stack {
106    type Target = Vec<Frame>;
107    fn deref(&self) -> &Vec<Frame> {
108        &self.inner
109    }
110}
111
112impl From<Vec<Frame>> for Stack {
113    fn from(vec: Vec<Frame>) -> Self {
114        Stack { inner: vec }
115    }
116}
117
118impl From<Stack> for Vec<Frame> {
119    fn from(vec: Stack) -> Self {
120        vec.inner
121    }
122}
123
124/// RAII-style guard that makes sure a stack frame is popped properly
125///
126/// The guard object is created by [`Stack::push`].
127#[derive(Debug)]
128#[must_use = "The frame is popped when the guard is dropped"]
129pub struct StackFrameGuard<'a> {
130    stack: &'a mut Stack,
131}
132
133impl Stack {
134    /// Pushes a new frame to the stack.
135    ///
136    /// This function returns a frame guard that will pop the frame when dropped.
137    #[inline]
138    pub fn push(&mut self, frame: Frame) -> StackFrameGuard<'_> {
139        self.inner.push(frame);
140        StackFrameGuard { stack: self }
141    }
142
143    /// Pops the topmost frame from the stack
144    #[inline]
145    pub fn pop(guard: StackFrameGuard<'_>) -> Frame {
146        let frame = guard.stack.inner.pop().unwrap();
147        std::mem::forget(guard);
148        frame
149    }
150
151    /// Returns the number of enclosing loops.
152    ///
153    /// This function returns the number of lexically enclosing `for`, `while`,
154    /// and `until` loops in the current execution environment. That is, the
155    /// result is the count of `Frame::Loop`s pushed after the last
156    /// `Frame::Subshell`, `Frame::DotScript`, or `Frame::Trap(_)`.
157    ///
158    /// The function stops counting when `max_count` is reached. The parameter
159    /// is useful if you don't have to count more than a specific number.
160    /// Pass `usize::MAX` to count all loops.
161    #[must_use]
162    pub fn loop_count(&self, max_count: usize) -> usize {
163        fn retains_context(frame: &Frame) -> bool {
164            match frame {
165                Frame::Loop | Frame::Condition | Frame::Builtin(_) => true,
166                Frame::Subshell | Frame::DotScript | Frame::Trap(_) | Frame::InitFile => false,
167            }
168        }
169
170        self.inner
171            .iter()
172            .rev()
173            .take_while(|&frame| retains_context(frame))
174            .filter(|&frame| frame == &Frame::Loop)
175            .take(max_count)
176            .count()
177    }
178
179    /// Returns the innermost built-in in the stack, if any.
180    #[must_use]
181    pub fn current_builtin(&self) -> Option<&Builtin> {
182        self.inner.iter().rev().find_map(|frame| match frame {
183            Frame::Builtin(builtin) => Some(builtin),
184            _ => None,
185        })
186    }
187}
188
189/// When the guard is dropped, the stack frame that was pushed when creating the
190/// guard is popped.
191impl Drop for StackFrameGuard<'_> {
192    fn drop(&mut self) {
193        self.stack.inner.pop().unwrap();
194    }
195}
196
197impl Deref for StackFrameGuard<'_> {
198    type Target = Stack;
199    fn deref(&self) -> &Stack {
200        self.stack
201    }
202}
203
204impl DerefMut for StackFrameGuard<'_> {
205    fn deref_mut(&mut self) -> &mut Stack {
206        self.stack
207    }
208}
209
210/// RAII-style guard that makes sure a stack frame is popped properly
211///
212/// The guard object is created by [`Env::push_frame`].
213#[derive(Debug)]
214#[must_use = "The frame is popped when the guard is dropped"]
215pub struct EnvFrameGuard<'a> {
216    env: &'a mut Env,
217}
218
219impl Env {
220    /// Pushes a new frame to the runtime execution context stack.
221    ///
222    /// This function is equivalent to `self.stack.push(frame)`, but returns an
223    /// `EnvFrameGuard` that allows re-borrowing the `Env`.
224    #[inline]
225    pub fn push_frame(&mut self, frame: Frame) -> EnvFrameGuard<'_> {
226        self.stack.inner.push(frame);
227        EnvFrameGuard { env: self }
228    }
229
230    /// Pops the topmost frame from the runtime execution context stack.
231    #[inline]
232    pub fn pop_frame(guard: EnvFrameGuard<'_>) -> Frame {
233        let frame = guard.env.stack.inner.pop().unwrap();
234        std::mem::forget(guard);
235        frame
236    }
237}
238
239/// When the guard is dropped, the stack frame that was pushed when creating the
240/// guard is popped.
241impl Drop for EnvFrameGuard<'_> {
242    fn drop(&mut self) {
243        self.env.stack.inner.pop().unwrap();
244    }
245}
246
247impl Deref for EnvFrameGuard<'_> {
248    type Target = Env;
249    fn deref(&self) -> &Env {
250        self.env
251    }
252}
253
254impl DerefMut for EnvFrameGuard<'_> {
255    fn deref_mut(&mut self) -> &mut Env {
256        self.env
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn loop_count_empty() {
266        let stack = Stack::default();
267        assert_eq!(stack.loop_count(usize::MAX), 0);
268    }
269
270    #[test]
271    fn loop_count_with_non_loop_frames() {
272        let mut stack = Stack::default();
273        let mut stack = stack.push(Frame::Builtin(Builtin {
274            name: Field::dummy(""),
275            is_special: false,
276        }));
277        assert_eq!(stack.loop_count(usize::MAX), 0);
278        let stack = stack.push(Frame::Condition);
279        assert_eq!(stack.loop_count(usize::MAX), 0);
280    }
281
282    #[test]
283    fn loop_count_with_one_loop() {
284        let mut stack = Stack::default();
285        let mut stack = stack.push(Frame::Loop);
286        assert_eq!(stack.loop_count(usize::MAX), 1);
287        let stack = stack.push(Frame::Condition);
288        assert_eq!(stack.loop_count(usize::MAX), 1);
289    }
290
291    #[test]
292    fn loop_count_with_two_loops() {
293        let mut stack = Stack::default();
294        let mut stack = stack.push(Frame::Loop);
295        let mut stack = stack.push(Frame::Condition);
296        let mut stack = stack.push(Frame::Loop);
297        assert_eq!(stack.loop_count(usize::MAX), 2);
298        let stack = stack.push(Frame::Condition);
299        assert_eq!(stack.loop_count(usize::MAX), 2);
300    }
301
302    #[test]
303    fn loop_count_with_subshells() {
304        let mut stack = Stack::default();
305        let mut stack = stack.push(Frame::Loop);
306        let mut stack = stack.push(Frame::Subshell);
307        assert_eq!(stack.loop_count(usize::MAX), 0);
308        let mut stack = stack.push(Frame::Loop);
309        assert_eq!(stack.loop_count(usize::MAX), 1);
310        let mut stack = stack.push(Frame::Loop);
311        assert_eq!(stack.loop_count(usize::MAX), 2);
312        let mut stack = stack.push(Frame::Subshell);
313        assert_eq!(stack.loop_count(usize::MAX), 0);
314        let stack = stack.push(Frame::Loop);
315        assert_eq!(stack.loop_count(usize::MAX), 1);
316    }
317
318    #[test]
319    fn loop_count_with_conditions() {
320        let mut stack = Stack::default();
321        let mut stack = stack.push(Frame::Loop);
322        let mut stack = stack.push(Frame::Condition);
323        assert_eq!(stack.loop_count(usize::MAX), 1);
324        let stack = stack.push(Frame::Loop);
325        assert_eq!(stack.loop_count(usize::MAX), 2);
326    }
327
328    #[test]
329    fn loop_count_with_builtins() {
330        let mut stack = Stack::default();
331        let mut stack = stack.push(Frame::Loop);
332        let mut stack = stack.push(Frame::Builtin(Builtin {
333            name: Field::dummy(""),
334            is_special: false,
335        }));
336        assert_eq!(stack.loop_count(usize::MAX), 1);
337        let stack = stack.push(Frame::Loop);
338        assert_eq!(stack.loop_count(usize::MAX), 2);
339    }
340
341    #[test]
342    fn loop_count_with_dot_scripts() {
343        let mut stack = Stack::default();
344        let mut stack = stack.push(Frame::Loop);
345        let mut stack = stack.push(Frame::DotScript);
346        assert_eq!(stack.loop_count(usize::MAX), 0);
347        let mut stack = stack.push(Frame::Loop);
348        assert_eq!(stack.loop_count(usize::MAX), 1);
349        let mut stack = stack.push(Frame::Loop);
350        assert_eq!(stack.loop_count(usize::MAX), 2);
351        let mut stack = stack.push(Frame::DotScript);
352        assert_eq!(stack.loop_count(usize::MAX), 0);
353        let stack = stack.push(Frame::Loop);
354        assert_eq!(stack.loop_count(usize::MAX), 1);
355    }
356
357    #[test]
358    fn loop_count_with_traps() {
359        let mut stack = Stack::default();
360        let mut stack = stack.push(Frame::Loop);
361        let mut stack = stack.push(Frame::Trap(crate::trap::Condition::Exit));
362        assert_eq!(stack.loop_count(usize::MAX), 0);
363        let mut stack = stack.push(Frame::Loop);
364        assert_eq!(stack.loop_count(usize::MAX), 1);
365        let mut stack = stack.push(Frame::Loop);
366        assert_eq!(stack.loop_count(usize::MAX), 2);
367        let mut stack = stack.push(Frame::Trap(crate::trap::Condition::Signal(
368            crate::signal::Number::from_raw_unchecked(1.try_into().unwrap()),
369        )));
370        assert_eq!(stack.loop_count(usize::MAX), 0);
371        let stack = stack.push(Frame::Loop);
372        assert_eq!(stack.loop_count(usize::MAX), 1);
373    }
374
375    #[test]
376    fn loop_count_with_small_max_count() {
377        let mut stack = Stack::default();
378        let mut stack = stack.push(Frame::Loop);
379        let mut stack = stack.push(Frame::Condition);
380        let mut stack = stack.push(Frame::Loop);
381        assert_eq!(stack.loop_count(usize::MAX), 2);
382        assert_eq!(stack.loop_count(3), 2);
383        assert_eq!(stack.loop_count(2), 2);
384        assert_eq!(stack.loop_count(1), 1);
385        assert_eq!(stack.loop_count(0), 0);
386
387        let stack = stack.push(Frame::Loop);
388        assert_eq!(stack.loop_count(4), 3);
389        assert_eq!(stack.loop_count(3), 3);
390        assert_eq!(stack.loop_count(2), 2);
391    }
392
393    #[test]
394    fn current_builtin() {
395        let mut stack = Stack::default();
396        assert_eq!(stack.current_builtin(), None);
397
398        let mut stack = stack.push(Frame::Loop);
399        assert_eq!(stack.current_builtin(), None);
400
401        let builtin = Builtin {
402            name: Field::dummy(""),
403            is_special: false,
404        };
405        let mut stack = stack.push(Frame::Builtin(builtin.clone()));
406        assert_eq!(stack.current_builtin(), Some(&builtin));
407
408        let builtin = Builtin {
409            name: Field::dummy("foo"),
410            is_special: true,
411        };
412        let stack = stack.push(Frame::Builtin(builtin.clone()));
413        assert_eq!(stack.current_builtin(), Some(&builtin));
414    }
415}