Skip to main content

safe_chains/pathctx/
item_shape.rs

1//! What the values behind a loop variable or a piped stream can look like, as far as flags go.
2//!
3//! The locus bindings beside this one say WHERE such a value points. They cannot say whether the
4//! value is a flag: `for f in -delete` and `echo -delete | xargs` both bind something that reads
5//! as a worktree path while the command receives `-delete`.
6
7use std::cell::RefCell;
8use std::marker::PhantomData;
9use std::ops::BitOr;
10
11/// A small set of yes/no facts, one bit each. `T` names what they are facts about, so a bit that
12/// means one thing in one set cannot be tested in another.
13#[derive(Debug, PartialEq, Eq)]
14pub struct Facts<T>(u8, PhantomData<T>);
15
16impl<T> Clone for Facts<T> {
17    fn clone(&self) -> Self {
18        *self
19    }
20}
21
22impl<T> Copy for Facts<T> {}
23
24impl<T> Default for Facts<T> {
25    fn default() -> Self {
26        Self::NONE
27    }
28}
29
30impl<T> Facts<T> {
31    pub const NONE: Self = Self(0, PhantomData);
32
33    #[must_use]
34    pub const fn bit(n: u8) -> Self {
35        Self(1 << n, PhantomData)
36    }
37
38    #[must_use]
39    pub const fn union(self, other: Self) -> Self {
40        Self(self.0 | other.0, PhantomData)
41    }
42
43    pub fn has(self, fact: Self) -> bool {
44        self.0 & fact.0 != 0
45    }
46
47    pub fn set(&mut self, fact: Self, on: bool) {
48        if on {
49            self.0 |= fact.0;
50        }
51    }
52
53    pub fn clear(&mut self, fact: Self) {
54        self.0 &= !fact.0;
55    }
56
57    #[must_use]
58    pub fn when(fact: Self, on: bool) -> Self {
59        if on { fact } else { Self::NONE }
60    }
61}
62
63impl<T> BitOr for Facts<T> {
64    type Output = Self;
65    fn bitor(self, other: Self) -> Self {
66        self.union(other)
67    }
68}
69
70/// Marks the facts about a set of values a variable or stream may take.
71#[derive(Debug, PartialEq, Eq)]
72pub enum Items {}
73
74pub type ItemShape = Facts<Items>;
75
76/// Some value may begin with `-`.
77pub const LEAD: ItemShape = Facts::bit(0);
78/// Some value may hold a blank, so an unquoted use becomes several words.
79pub const SPLIT: ItemShape = Facts::bit(1);
80/// Some value may be empty, so an unquoted use becomes none.
81pub const EMPTY: ItemShape = Facts::bit(2);
82/// Nothing is known about the values.
83pub const UNKNOWN: ItemShape = LEAD.union(SPLIT).union(EMPTY);
84
85thread_local! {
86    static LOOP_SHAPES: RefCell<Vec<(String, ItemShape)>> = const { RefCell::new(Vec::new()) };
87    static STDIN_SHAPES: RefCell<Vec<ItemShape>> = const { RefCell::new(Vec::new()) };
88}
89
90pub struct ItemShapeGuard(bool);
91
92impl Drop for ItemShapeGuard {
93    fn drop(&mut self) {
94        if self.0 {
95            LOOP_SHAPES.with(|v| v.borrow_mut().pop());
96        } else {
97            STDIN_SHAPES.with(|v| v.borrow_mut().pop());
98        }
99    }
100}
101
102#[must_use]
103pub fn enter_loop_shape(name: String, shape: ItemShape) -> ItemShapeGuard {
104    LOOP_SHAPES.with(|v| v.borrow_mut().push((name, shape)));
105    ItemShapeGuard(true)
106}
107
108/// The innermost shape bound to `name`; [`UNKNOWN`] when none is.
109pub fn loop_shape(name: &str) -> ItemShape {
110    LOOP_SHAPES.with(|v| v.borrow().iter().rev().find(|(n, _)| n == name).map_or(UNKNOWN, |(_, s)| *s))
111}
112
113#[must_use]
114pub fn enter_stdin_shape(shape: ItemShape) -> ItemShapeGuard {
115    STDIN_SHAPES.with(|v| v.borrow_mut().push(shape));
116    ItemShapeGuard(false)
117}
118
119/// The shape of the items on stdin; [`UNKNOWN`] when no stage set one.
120pub fn stdin_shape() -> ItemShape {
121    STDIN_SHAPES.with(|v| v.borrow().last().copied().unwrap_or(UNKNOWN))
122}
123
124/// What `$name` is bound to, by [`super::expand_vars`]' precedence: a loop (unknown items) or a
125/// certain value.
126pub enum Binding {
127    Loop,
128    Value(String),
129    Unbound,
130}
131
132pub fn binding(name: &str) -> Binding {
133    if super::LOOP_VARS.with(|v| v.borrow().iter().any(|l| l.name == name)) {
134        return Binding::Loop;
135    }
136    super::VARS.with(|v| {
137        v.borrow()
138            .iter()
139            .rev()
140            .find(|b| b.name == name)
141            .map_or(Binding::Unbound, |b| Binding::Value(b.value.clone()))
142    })
143}
144
145/// Every name a loop or an assignment has bound so far.
146pub fn bound_names() -> Vec<String> {
147    let loops: Vec<String> = super::LOOP_VARS.with(|v| v.borrow().iter().map(|l| l.name.clone()).collect());
148    super::VARS.with(|v| loops.into_iter().chain(v.borrow().iter().map(|b| b.name.clone())).collect())
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn unbound_shapes_are_unknown_and_guards_unwind() {
157        let safe = ItemShape::NONE;
158        assert_eq!(loop_shape("f"), UNKNOWN);
159        assert_eq!(stdin_shape(), UNKNOWN);
160        {
161            let _outer = enter_loop_shape("f".into(), safe);
162            let _stdin = enter_stdin_shape(safe);
163            assert_eq!(loop_shape("f"), safe);
164            assert_eq!(loop_shape("g"), UNKNOWN);
165            assert_eq!(stdin_shape(), safe);
166            {
167                let _inner = enter_loop_shape("f".into(), UNKNOWN);
168                assert_eq!(loop_shape("f"), UNKNOWN);
169            }
170            assert_eq!(loop_shape("f"), safe);
171        }
172        assert_eq!(loop_shape("f"), UNKNOWN);
173        assert_eq!(stdin_shape(), UNKNOWN);
174    }
175
176    #[test]
177    fn facts_combine_and_clear() {
178        let mut f = ItemShape::NONE;
179        assert!(!f.has(LEAD));
180        f.set(LEAD, false);
181        assert_eq!(f, ItemShape::NONE);
182        f.set(LEAD, true);
183        assert!(f.has(LEAD) && !f.has(SPLIT));
184        assert_eq!(LEAD | SPLIT | EMPTY, UNKNOWN);
185        f.clear(LEAD);
186        assert_eq!(f, ItemShape::NONE);
187        assert_eq!(ItemShape::when(SPLIT, true), SPLIT);
188        assert_eq!(ItemShape::when(SPLIT, false), ItemShape::NONE);
189    }
190}