Skip to main content

bash_loadable/ffi/
word.rs

1use std::{ffi::{CStr, c_char, c_int}, mem::transmute, ptr::NonNull};
2//
3use paste::paste;
4
5// /* Possible values for the `flags' field of a WORD_DESC. */
6// #define W_HASDOLLAR	(1 << 0)	/* Dollar sign present. */
7// #define W_QUOTED	(1 << 1)	/* Some form of quote character is present. */
8// #define W_ASSIGNMENT	(1 << 2)	/* This word is a variable assignment. */
9// #define W_SPLITSPACE	(1 << 3)	/* Split this word on " " regardless of IFS */
10// #define W_NOSPLIT	(1 << 4)	/* Do not perform word splitting on this word because ifs is empty string. */
11// #define W_NOGLOB	(1 << 5)	/* Do not perform globbing on this word. */
12// #define W_NOSPLIT2	(1 << 6)	/* Don't split word except for $@ expansion (using spaces) because context does not allow it. */
13// #define W_TILDEEXP	(1 << 7)	/* Tilde expand this assignment word */
14// #define W_DOLLARAT	(1 << 8)	/* UNUSED - $@ and its special handling */
15// #define W_ARRAYREF	(1 << 9)	/* word is a valid array reference */
16// #define W_NOCOMSUB	(1 << 10)	/* Don't perform command substitution on this word */
17// #define W_ASSIGNRHS	(1 << 11)	/* Word is rhs of an assignment statement */
18// #define W_NOTILDE	(1 << 12)	/* Don't perform tilde expansion on this word */
19// #define W_NOASSNTILDE	(1 << 13)	/* don't do tilde expansion like an assignment statement */
20// #define W_EXPANDRHS	(1 << 14)	/* Expanding word in ${paramOPword} */
21// #define W_COMPASSIGN	(1 << 15)	/* Compound assignment */
22// #define W_ASSNBLTIN	(1 << 16)	/* word is a builtin command that takes assignments */
23// #define W_ASSIGNARG	(1 << 17)	/* word is assignment argument to command */
24// #define W_HASQUOTEDNULL	(1 << 18)	/* word contains a quoted null character */
25// #define W_DQUOTE	(1 << 19)	/* UNUSED - word should be treated as if double-quoted */
26// #define W_NOPROCSUB	(1 << 20)	/* don't perform process substitution */
27// #define W_SAWQUOTEDNULL	(1 << 21)	/* word contained a quoted null that was removed */
28// #define W_ASSIGNASSOC	(1 << 22)	/* word looks like associative array assignment */
29// #define W_ASSIGNARRAY	(1 << 23)	/* word looks like a compound indexed array assignment */
30// #define W_ARRAYIND	(1 << 24)	/* word is an array index being expanded */
31// #define W_ASSNGLOBAL	(1 << 25)	/* word is a global assignment to declare (declare/typeset -g) */
32// #define W_NOBRACE	(1 << 26)	/* Don't perform brace expansion */
33// #define W_COMPLETE	(1 << 27)	/* word is being expanded for completion */
34// #define W_CHKLOCAL	(1 << 28)	/* check for local vars on assignment */
35// #define W_FORCELOCAL	(1 << 29)	/* force assignments to be to local variables, non-fatal on assignment errors */
36
37#[repr(transparent)]
38#[derive(Clone, Copy, PartialEq, Eq, Hash)]
39pub struct FFIWordFlags(c_int);
40
41impl FFIWordFlags {
42    pub const NONE: Self = Self(0);
43
44    #[must_use]
45    #[inline(always)]
46    pub const fn has_flags(self, flags: Self) -> bool {
47        self.0 & flags.0 == flags.0
48    }
49
50    #[inline(always)]
51    pub const fn add_flags(&mut self, flags: Self) {
52        self.0 |= flags.0;
53    }
54
55    #[inline(always)]
56    pub const fn remove_flags(&mut self, flags: Self) {
57        self.0 &= !flags.0;
58    }
59
60    #[inline(always)]
61    pub const fn toggle_flags(&mut self, flags: Self) {
62        self.0 ^= flags.0;
63    }
64
65    #[must_use]
66    #[inline(always)]
67    pub const fn with_flags(mut self, flags: Self) -> Self {
68        self.add_flags(flags);
69        self
70    }
71
72    #[must_use]
73    #[inline(always)]
74    pub const fn without_flags(mut self, flags: Self) -> Self {
75        self.remove_flags(flags);
76        self
77    }
78
79    #[must_use]
80    #[inline]
81    pub const fn count_ones(self) -> u32 {
82        self.0.count_ones()
83    }
84
85    #[must_use]
86    #[inline]
87    pub const fn pop_bottom_index(&mut self) -> Option<u32> {
88        if self.0 == 0 {
89            return None;
90        }
91        let mut mask = self.0.cast_unsigned();
92        let next_bit = mask.trailing_zeros();
93        mask ^= 1 << next_bit;
94        self.0 = mask.cast_signed();
95        Some(next_bit)
96    }
97
98    #[must_use]
99    #[inline]
100    pub fn get_flags(self) -> Box<[(Self, &'static str)]> {
101        self.collect()
102    }
103}
104
105impl Iterator for FFIWordFlags {
106    type Item = (FFIWordFlags, &'static str);
107    fn size_hint(&self) -> (usize, Option<usize>) {
108        (self.count_ones() as usize, Some(self.count_ones() as usize))
109    }
110
111    fn next(&mut self) -> Option<Self::Item> {
112        let index = self.pop_bottom_index()?;
113        let index = index as usize;
114        Some((Self::ALL_FLAGS[index], Self::FLAG_NAMES[index]))
115    }
116}
117
118macro_rules! define_word_flags {
119    (
120        $(
121            #[$doc_meta:meta]
122            $const_name:ident $func_name:ident = $value:expr
123        ),*
124        $(,)
125    ?) => {
126        paste!{
127            impl FFIWordFlags {
128                pub const ALL: Self = {
129                    let mut builder = Self::NONE;
130                    $(
131                        builder.add_flags(Self::$const_name);
132                    )*
133                    builder
134                };
135                pub const ALL_FLAGS: &'static [Self] = &[
136                    $(
137                        Self::$const_name,
138                    )*
139                ];
140                pub const FLAG_NAMES: &'static [&'static str] = &[
141                    $(
142                        stringify!($func_name),
143                    )*
144                ];
145                $(
146                    #[$doc_meta]
147                    pub const $const_name: Self = Self($value);
148                    
149                    #[must_use]
150                    #[inline(always)]
151                    pub const fn [<get_ $func_name>](self) -> bool {
152                        self.has_flags(Self::$const_name)
153                    }
154
155                    #[inline(always)]
156                    pub const fn [<add_ $func_name>](&mut self) {
157                        self.add_flags(Self::$const_name);
158                    }
159
160                    #[inline(always)]
161                    pub const fn [<remove_ $func_name>](&mut self) {
162                        self.remove_flags(Self::$const_name);
163                    }
164
165                    #[inline]
166                    pub const fn [<set_ $func_name>](&mut self, on: bool) {
167                        if on {
168                            self.add_flags(Self::$const_name);
169                        } else {
170                            self.remove_flags(Self::$const_name);
171                        }
172                    }
173
174                    #[inline(always)]
175                    pub const fn [<toggle_ $func_name>](&mut self) {
176                        self.0 ^= Self::$const_name.0;
177                    }
178
179                    #[must_use]
180                    #[inline(always)]
181                    pub const fn [<with_ $func_name>](self) -> Self {
182                        self.with_flags(Self::$const_name)
183                    }
184
185                    #[must_use]
186                    #[inline(always)]
187                    pub const fn [<without_ $func_name>](self) -> Self {
188                        self.without_flags(Self::$const_name)
189                    }
190                )*
191            }
192        }
193    };
194}
195
196define_word_flags!(
197    /// Dollar sign present.
198    HAS_DOLLAR
199        has_dollar              = 1 << 0,
200    /// Some form of quoted character is present.
201    QUOTED
202        quoted                  = 1 << 1,
203    /// This word is a variable assignment.
204    ASSIGNMENT
205        assignment              = 1 << 2,
206    /// Split this word on " " regardless of IFS.
207    SPLIT_SPACE
208        split_space             = 1 << 3,
209    /// Do not perform word splitting on this word because IFS is empty string.
210    NO_SPLIT
211        no_split                = 1 << 4,
212    /// Do not perform globbing on this word.
213    NO_GLOB
214        no_glob                 = 1 << 5,
215    /// Don't split word except for $@ expansion (using spaces) because context does not allow it.
216    NO_SPLIT2
217        no_split2               = 1 << 6,
218    /// Tilde expand this assignment word.
219    TILDE_EXP
220        tilde_exp               = 1 << 7,
221    /// $@ and its special handling. (Unused)
222    DOLLAR_AT
223        dollar_at               = 1 << 8,
224    /// Word is a valid array reference.
225    ARRAY_REF
226        array_ref               = 1 << 9,
227    /// Don't perform command substitution on this word.
228    NO_COMMAND_SUBSTITUTION
229        no_command_substitution = 1 << 10,
230    /// Word is RHS of an assignment statement.
231    ASSIGN_RHS
232        assign_rhs              = 1 << 11,
233    /// Don't perform tilde expansion on this word.
234    NO_TILDE
235        no_tilde                = 1 << 12,
236    /// Don't do tilde expansion like an assignment statement.
237    NO_ASSIGN_TILDE
238        no_assign_tilde         = 1 << 13,
239    /// Expanding word in ${paramOPword}
240    EXPAND_RHS
241        expand_rhs              = 1 << 14,
242    /// Compound assignment. (no idea what that means, better look it up.) // TODO
243    COMPOUND_ASSIGNMENT
244        compound_assignment     = 1 << 15,
245    /// Word is a builtin command that takes assignments
246    ASSIGN_BUILTIN
247        assign_builtin          = 1 << 16,
248    /// Word is assignment argument to command.
249    ASSIGN_ARG
250        assign_arg              = 1 << 17,
251    /// Word contains a quoted null character.
252    HAS_QUOTED_NULL
253        has_quoted_null         = 1 << 18,
254    /// Word should be treated as if double-quoted. (Unused)
255    DOUBLE_QUOTE
256        double_quote            = 1 << 19,
257    /// Don't perform process substitution.
258    NO_PROCESS_SUBSTITUTION
259        no_process_substitution = 1 << 20,
260    /// Word contained a quoted null that was removed.
261    SAW_QUOTED_NULL
262        saw_quoted_null         = 1 << 21,
263    /// Word looks like associative array assignment.
264    ASSIGN_ASSOC
265        assign_assoc            = 1 << 22,
266    /// Word looks like a compound indexed array assignment.
267    ASSIGN_ARRAY
268        assign_array            = 1 << 23,
269    /// Word is an array index being expanded.
270    ARRAY_INDEX
271        array_index             = 1 << 24,
272    /// Word is a global assignment to declare
273    ASSIGN_GLOBAL
274        assign_global           = 1 << 25,
275    /// Don't perform brace expansion
276    NO_BRACE
277        no_brace                = 1 << 26,
278    /// Word is being expanded for completion.
279    COMPLETION
280        completion              = 1 << 27,
281    /// Check for local vars on assignment.
282    CHECK_LOCAL
283        check_local             = 1 << 28,
284    /// Force assignment to be local variables, non-fatal on assignment errors.]    
285    FORCE_LOCAL
286        force_local             = 1 << 29
287);
288
289#[repr(C)]
290#[derive(Clone, Copy)]
291pub struct FFIWord {
292    pub word: *const c_char,
293    pub flags: FFIWordFlags,
294}
295
296impl FFIWord {
297    pub const EMTPY: Self = Self { word: c"".as_ptr(), flags: FFIWordFlags::NONE };
298}
299
300#[repr(transparent)]
301#[derive(Clone, Copy)]
302pub struct WordRef(Option<NonNull<FFIWord>>);
303
304impl WordRef {
305    #[must_use]
306    #[inline(always)]
307    pub const fn get(&self) -> Option<&FFIWord> {
308        unsafe { transmute(self.0) }
309    }
310
311    #[must_use]
312    #[inline]
313    pub fn to_str(&self) -> Option<&str> {
314        if let Some(word) = self.get() {
315            if word.word.is_null() {
316                return None;
317            }
318            let cstr = unsafe { CStr::from_ptr(word.word) };
319            let len = cstr.count_bytes();
320            Some(unsafe { transmute(std::slice::from_raw_parts(word.word.cast::<u8>(), len)) })
321        } else {
322            None
323        }
324    }
325}
326
327impl std::fmt::Display for WordRef {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        if let Some(s) = self.to_str() {
330            write!(f, "{s}")
331        } else {
332            Ok(())
333        }
334    }
335}
336
337#[repr(C)]
338#[derive(Clone, Copy)]
339pub struct FFIWordList {
340    pub next: WordListRef,
341    pub word: WordRef,
342}
343
344impl FFIWordList {
345    const NULL: Self = Self { next: WordListRef(None), word: WordRef(None) };
346
347    #[must_use]
348    #[inline]
349    pub const fn next(&self) -> Option<&FFIWordList> {
350        self.next.get()
351    }
352
353    #[must_use]
354    #[inline]
355    pub const fn word(&self) -> Option<&FFIWord> {
356        self.word.get()
357    }
358
359    #[must_use]
360    #[inline]
361    pub fn word_str(&self) -> Option<&str> {
362        self.word.to_str()
363    }
364}
365
366#[repr(transparent)]
367#[derive(Clone, Copy)]
368pub struct WordListRef(Option<NonNull<FFIWordList>>);
369
370impl WordListRef {
371    #[must_use]
372    #[inline(always)]
373    pub const fn get(&self) -> Option<&FFIWordList> {
374        unsafe { transmute(self.0) }
375    }
376
377    #[must_use]
378    #[inline]
379    pub const fn as_ref(&self) -> &FFIWordList {
380        if let Some(inner) = self.get() {
381            inner
382        } else {
383            &FFIWordList::NULL
384        }
385    }
386}