ghoti_exec/
lib.rs

1//! WIP
2use std::borrow::Cow;
3use std::cell::{Ref, RefCell};
4use std::cmp::Reverse;
5use std::collections::{BTreeMap, BTreeSet};
6use std::fs::OpenOptions;
7use std::ops::{ControlFlow, Deref, DerefMut};
8use std::path::{Path, PathBuf};
9use std::rc::Rc;
10use std::sync::Arc;
11use std::{fmt, io as stdio, mem, slice};
12
13use builtins::FunctionOpts;
14use command::{BoxCommand, Command, UserFunc, UserFuncImpl};
15use either::Either;
16use ghoti_syntax::{
17    self as ast, ParseError, RedirectMode, RedirectPort, Stmt, WordFrag, parse_source,
18};
19use io::StdioCollectSink;
20use itertools::Itertools;
21use tokio::io::AsyncRead;
22use utils::{access_can_exec, access_can_read, validate_variable_name};
23
24use crate::io::{Io, IoConfig};
25use crate::utils::validate_function_name;
26
27pub mod builtins;
28pub mod command;
29pub mod io;
30mod utils;
31
32#[cfg(test)]
33mod tests;
34
35// Well-known variables.
36const VAR_STATUS: &str = "status";
37const VAR_STATUS_GENERATION: &str = "status_generation";
38const VAR_PIPE_STATUS: &str = "pipestatus";
39const VAR_PATH: &str = "PATH";
40const VAR_HOME: &str = "HOME";
41const VAR_AUTOLOAD_PATH: &str = "fish_function_path";
42
43const AUTOLOAD_NAME_SUFFIX: &str = ".fish";
44
45pub type Result<T, E = Error> = std::result::Result<T, E>;
46
47pub type ExecResult<T = Status> = Result<T>;
48
49#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Status(pub u8);
51
52impl Status {
53    pub fn is_success(self) -> bool {
54        self == Self::SUCCESS
55    }
56}
57
58impl Status {
59    pub const SUCCESS: Self = Self(0);
60    pub const FAILURE: Self = Self(1);
61}
62
63impl fmt::Display for Status {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        self.0.fmt(f)
66    }
67}
68
69impl From<u8> for Status {
70    fn from(n: u8) -> Self {
71        Self(n)
72    }
73}
74
75impl From<usize> for Status {
76    fn from(n: usize) -> Self {
77        Self(u8::try_from(n).unwrap_or(u8::MAX))
78    }
79}
80
81impl From<bool> for Status {
82    fn from(b: bool) -> Self {
83        match b {
84            true => Self::SUCCESS,
85            false => Self::FAILURE,
86        }
87    }
88}
89
90impl std::process::Termination for Status {
91    fn report(self) -> std::process::ExitCode {
92        self.0.into()
93    }
94}
95
96type ExecControlFlow = ControlFlow<ExecBreak>;
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99enum ExecBreak {
100    LoopBreak,
101    LoopContinue,
102    Return,
103}
104
105pub type Value = String;
106pub type ValueList = Vec<String>;
107
108/// An error triggered at runtime.
109///
110/// Typically, it will not break the execution but only print a message and set a non-zero status
111/// code.
112/// [`ExecContext::emit_error`] can be used to report it with backtrace and get a corresponding
113/// [`Status`].
114#[derive(Debug, thiserror::Error)]
115pub enum Error {
116    // User errors.
117    #[error("{0}")]
118    Custom(String),
119    #[error("invalid options: {0}")]
120    InvalidOptions(clap::Error),
121    #[error("invalid identifier: {0:?}")]
122    InvalidIdentifier(String),
123    #[error("invalid integer: {0:?}")]
124    InvalidInteger(String),
125    #[error("expecting a single word, got {0} words")]
126    NotOneWord(usize),
127    #[error("empty command")]
128    EmptyCommand,
129    #[error("command not found: {0}")]
130    CommandNotFound(String),
131    #[error("cannot modify special variable: {0:?}")]
132    ModifySpecialVariable(String),
133    #[error("syntax error: {}", .0.kind)]
134    SyntaxError(ParseError),
135
136    // System errors.
137    #[error("read/write error: {0}")]
138    ReadWrite(stdio::Error),
139    #[error("failed open redirection port {0:?}")]
140    InvalidRedirectionPort(String),
141    #[error("failed open redirection file {0:?}: {1}")]
142    OpenRedirectionFile(PathBuf, stdio::Error),
143    #[error("failed to spawn process {0:?}: {1}")]
144    SpawnProcess(PathBuf, stdio::Error),
145    #[error("pipe closed")]
146    PipeClosed,
147    #[error("failed to create pipe: {0}")]
148    CreatePipe(stdio::Error),
149    #[error("failed to clone fd: {0}")]
150    CloneHandle(stdio::Error),
151    #[error("failed to wait process: {0}")]
152    WaitProcess(stdio::Error),
153    #[error("cannot change the current directory: {0}")]
154    ChangeCurrentDir(stdio::Error),
155    #[error("invalid UTF8")]
156    InvalidUtf8,
157}
158
159impl From<String> for Error {
160    fn from(s: String) -> Self {
161        Self::Custom(s)
162    }
163}
164
165impl Error {
166    pub fn to_status(&self) -> Status {
167        Status(match self {
168            Error::Custom(_) => 1,
169            Error::InvalidOptions(_) => 121,
170            Error::InvalidIdentifier(_) => 2,
171            Error::InvalidInteger(_) => 2,
172            Error::NotOneWord(_) => 121,
173            Error::EmptyCommand => 123,
174            Error::CommandNotFound(_) => 127,
175            Error::ModifySpecialVariable(_) => 1,
176            Error::SyntaxError(_) => 127,
177            Error::SpawnProcess(..) => 125,
178            Error::ReadWrite(_) => 1,
179            Error::InvalidRedirectionPort(_) => 2,
180            Error::OpenRedirectionFile(..) => 1,
181            Error::PipeClosed => 1,
182            Error::CreatePipe(_) => 1,
183            Error::CloneHandle(_) => 1,
184            Error::WaitProcess(_) => 1,
185            Error::ChangeCurrentDir(_) => 1,
186            Error::InvalidUtf8 => 123,
187        })
188    }
189}
190
191#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
192pub enum VarScope {
193    #[default]
194    Auto,
195    Local,
196    Function,
197    Global,
198    Universal,
199}
200
201// FIXME: Cloning this is no good.
202#[derive(Debug, Clone)]
203pub struct Variable {
204    pub value: ValueList,
205    pub export: bool,
206    cache: Option<RefCell<Box<VarCache>>>,
207}
208
209#[derive(Debug, Clone, Default)]
210struct VarCache {
211    /// For `VAR_AUTOLOAD_PATH`, this is a map of function-name => autoload-path.
212    name_to_path: Option<BTreeMap<String, PathBuf>>,
213}
214
215impl Variable {
216    pub fn new(val: impl Into<String>) -> Self {
217        Self::new_list(Some(val.into()))
218    }
219
220    pub fn new_list(vals: impl IntoIterator<Item = String>) -> Self {
221        Self {
222            value: vals.into_iter().collect(),
223            export: false,
224            cache: None,
225        }
226    }
227
228    pub fn exported(mut self) -> Self {
229        self.export = true;
230        self
231    }
232
233    fn with_cache(mut self) -> Self {
234        self.cache = Some(Default::default());
235        self
236    }
237
238    fn name_to_path_cache(&self) -> Ref<'_, Option<BTreeMap<String, PathBuf>>> {
239        Ref::map(self.cache.as_ref().expect("has cache").borrow(), |cache| {
240            &cache.name_to_path
241        })
242    }
243
244    fn set_name_to_path_cache(&self, cache: BTreeMap<String, PathBuf>) {
245        self.cache
246            .as_ref()
247            .expect("has cache")
248            .borrow_mut()
249            .name_to_path = Some(cache);
250    }
251}
252
253impl From<String> for Variable {
254    fn from(s: String) -> Self {
255        Self::new(s)
256    }
257}
258
259impl From<Vec<String>> for Variable {
260    fn from(vals: Vec<String>) -> Self {
261        Self::new_list(vals)
262    }
263}
264
265type DynSpecialVarGetter = dyn Fn(&ExecContext<'_>) -> ValueList;
266
267pub struct Executor {
268    builtin_funcs: BTreeMap<String, Box<dyn Command>>,
269    /// Special read-only variables implemented as getters.
270    special_vars: BTreeMap<String, Box<DynSpecialVarGetter>>,
271
272    global_funcs: RefCell<BTreeMap<String, Box<dyn Command>>>,
273    global_vars: RefCell<BTreeMap<String, Variable>>,
274
275    error_renderer: annotate_snippets::Renderer,
276}
277
278impl std::fmt::Debug for Executor {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        f.debug_struct("Executor")
281            .field("builtin_funcs", &self.builtin_funcs)
282            .field("global_funcs", &self.global_funcs)
283            .field("global_vars", &self.global_vars)
284            .field("special_vars", &self.special_vars.keys())
285            .finish()
286    }
287}
288
289impl Default for Executor {
290    fn default() -> Self {
291        let mut this = Self::new();
292        this.error_renderer = annotate_snippets::Renderer::styled();
293
294        this.builtin_funcs
295            .extend(builtins::all_builtins().map(|(name, cmd)| (name.to_owned(), cmd)));
296
297        this.set_special_var(VAR_STATUS, |ctx| vec![ctx.last_status().to_string()]);
298        // TODO?
299        this.set_special_var(VAR_STATUS_GENERATION, |_ctx| vec!["0".into()]);
300        this.set_special_var(VAR_PIPE_STATUS, |ctx| {
301            ctx.last_pipe_status()
302                .iter()
303                .map(|s| s.to_string())
304                .collect()
305        });
306
307        this.set_global_var(VAR_AUTOLOAD_PATH, Variable::new_list([]).with_cache());
308
309        this
310    }
311}
312
313impl Executor {
314    pub fn new() -> Self {
315        Self {
316            builtin_funcs: BTreeMap::new(),
317            special_vars: BTreeMap::new(),
318
319            global_funcs: RefCell::new(BTreeMap::new()),
320            global_vars: RefCell::new(BTreeMap::new()),
321
322            error_renderer: annotate_snippets::Renderer::plain(),
323        }
324    }
325
326    pub fn default_from_env() -> Self {
327        let mut this = Self::default();
328        this.import_from_env();
329        this
330    }
331
332    pub fn import_from_env(&mut self) {
333        // TODO: Non-UTF8
334        for (name, value) in std::env::vars() {
335            self.set_global_var(name, Variable::new(value).exported());
336        }
337    }
338
339    pub fn builtins(&self) -> impl ExactSizeIterator<Item = (&str, &BoxCommand)> {
340        self.builtin_funcs.iter().map(|(name, cmd)| (&**name, cmd))
341    }
342
343    pub fn set_builtin(&mut self, name: String, cmd: impl Command) {
344        self.builtin_funcs.insert(name, Box::new(cmd));
345    }
346
347    pub fn get_builtin(&self, name: &str) -> Option<BoxCommand> {
348        self.builtin_funcs.get(name).cloned()
349    }
350
351    pub fn special_vars(&self) -> impl Iterator<Item = (&str, &DynSpecialVarGetter)> {
352        self.special_vars
353            .iter()
354            .map(|(s, getter)| (&**s, &**getter))
355    }
356
357    pub fn set_special_var(
358        &mut self,
359        name: impl Into<String>,
360        getter: impl Fn(&ExecContext<'_>) -> ValueList + 'static,
361    ) {
362        self.special_vars.insert(name.into(), Box::new(getter));
363    }
364
365    pub fn remove_special_var(&mut self, name: &str) -> bool {
366        self.special_vars.remove(name).is_some()
367    }
368
369    pub fn get_global_func(&self, name: &str) -> Option<BoxCommand> {
370        self.global_funcs.borrow().get(name).cloned()
371    }
372
373    pub fn set_global_func(&self, name: String, cmd: impl Command) {
374        self.global_funcs.borrow_mut().insert(name, Box::new(cmd));
375    }
376
377    pub fn remove_global_func(&self, name: &str) -> bool {
378        self.global_funcs.borrow_mut().remove(name).is_some()
379    }
380
381    pub(crate) fn global_vars(&self) -> Ref<'_, BTreeMap<String, Variable>> {
382        self.global_vars.borrow()
383    }
384
385    pub fn get_global_var(&self, name: &str) -> Option<impl Deref<Target = Variable> + use<'_>> {
386        Ref::filter_map(self.global_vars.borrow(), |m| m.get(name)).ok()
387    }
388
389    pub fn set_global_var(&self, name: impl Into<String>, var: Variable) {
390        self.global_vars.borrow_mut().insert(name.into(), var);
391    }
392
393    pub fn remove_global_var(&self, name: &str) -> bool {
394        self.global_vars.borrow_mut().remove(name).is_some()
395    }
396}
397
398#[derive(Debug)]
399pub struct ExecContext<'a> {
400    local_vars_by_name: BTreeMap<(String, Reverse<u32>), Variable>,
401    local_vars_by_scope: BTreeSet<(u32, String)>,
402    cur_scope_idx: u32,
403
404    root: &'a Executor,
405    outer: Option<&'a ExecContext<'a>>,
406    last_status: Status,
407    last_pipe_status: Vec<Status>,
408    io: IoConfig,
409
410    backtrace: Vec<FrameInfo>,
411}
412
413#[derive(Debug)]
414struct SourceFile {
415    origin: Option<String>,
416    text: String,
417}
418
419#[derive(Debug)]
420struct FrameInfo {
421    kind: FrameKind,
422    source: Rc<SourceFile>,
423    pos: u32,
424}
425
426#[derive(Debug)]
427enum FrameKind {
428    Function { name: String, def_pos: u32 },
429    Source,
430    Pipe { def_pos: u32 },
431    CommandSubst { def_pos: u32 },
432}
433
434#[derive(Debug)]
435pub enum LocateExternalCommand {
436    ExecFile(PathBuf),
437    NotExecFile(PathBuf),
438    Dir(PathBuf),
439    NotFound,
440}
441
442impl Deref for ExecContext<'_> {
443    type Target = Executor;
444
445    fn deref(&self) -> &Self::Target {
446        self.root
447    }
448}
449
450impl<'a> ExecContext<'a> {
451    pub fn new(root: &'a Executor) -> Self {
452        Self {
453            local_vars_by_name: BTreeMap::new(),
454            local_vars_by_scope: BTreeSet::new(),
455            cur_scope_idx: 0,
456
457            root,
458            outer: None,
459            last_status: Status::SUCCESS,
460            last_pipe_status: Vec::new(),
461            io: IoConfig::default(),
462
463            backtrace: Vec::new(),
464        }
465    }
466
467    pub(crate) fn new_inside(outer: &'a ExecContext<'a>, frame: FrameInfo) -> Self {
468        let mut this = Self::new(outer.root);
469        this.outer = Some(outer);
470        this.io = outer.io.clone();
471        this.backtrace.push(frame);
472        this
473    }
474
475    pub fn last_status(&self) -> Status {
476        self.last_status
477    }
478
479    pub fn set_last_status(&mut self, n: impl Into<Status>) {
480        self.last_status = n.into();
481    }
482
483    pub fn last_pipe_status(&self) -> &[Status] {
484        &self.last_pipe_status
485    }
486
487    pub fn io(&self) -> &IoConfig {
488        &self.io
489    }
490
491    pub fn backtrace_context(&self) -> impl Iterator<Item = &Self> {
492        std::iter::successors(Some(self), |ctx| ctx.outer)
493    }
494
495    fn backtrace(&self) -> impl Iterator<Item = &FrameInfo> {
496        self.backtrace_context()
497            .flat_map(|ctx| ctx.backtrace.iter().rev())
498    }
499
500    fn render_error(&self, err: &Error) -> String {
501        use annotate_snippets::{Level, Snippet};
502        use std::fmt::Write;
503
504        let err_str = err.to_string();
505        let mut msg = Level::Error.title(&err_str);
506        let first_note;
507
508        let mut iter = self.backtrace();
509        {
510            let frame = iter.next().unwrap();
511            let mut span = frame.pos as usize..frame.pos as usize;
512            if let Error::SyntaxError(err) = err {
513                span = err.span();
514            }
515
516            let mut snip = Snippet::source(&frame.source.text)
517                .annotation(Level::Error.span(span.clone()))
518                .fold(true);
519            if let Some(origin) = &frame.source.origin {
520                snip = snip.origin(origin);
521            } else if span.start == 0 {
522                // Special simple case for errors on the only command in interactive shell.
523                return self.error_renderer.render(msg).to_string();
524            }
525
526            let (def_note, def_pos) = match &frame.kind {
527                FrameKind::Function { name, def_pos } => {
528                    first_note = format!("in function {name:?}");
529                    (first_note.as_str(), *def_pos)
530                }
531                FrameKind::Pipe { def_pos } => ("in pipeline", *def_pos),
532                FrameKind::CommandSubst { def_pos } => ("in command substitution", *def_pos),
533                FrameKind::Source => ("before sourcing this file", 0),
534            };
535            snip = snip.annotation(
536                Level::Note
537                    .span(def_pos as usize..def_pos as usize)
538                    .label(def_note),
539            );
540
541            msg = msg.snippet(snip);
542        }
543
544        let footers = iter
545            .filter_map(|frame| {
546                let mut s = match &frame.kind {
547                    FrameKind::Function { name, .. } => format!("from function {name:?}"),
548                    FrameKind::Source => "from sourcing".into(),
549                    FrameKind::Pipe { .. } => "from pipeline".into(),
550                    FrameKind::CommandSubst { .. } => "from command substitution".into(),
551                };
552                if let Some(origin) = &frame.source.origin {
553                    let line = 1 + frame.source.text[..frame.pos as usize]
554                        .bytes()
555                        .filter(|&b| b == b'\n')
556                        .count();
557                    write!(s, " {origin}:{line}").unwrap();
558                } else if let FrameKind::Source = frame.kind {
559                    return None;
560                }
561                Some(s)
562            })
563            .collect::<Vec<String>>();
564        msg = msg.footers(footers.iter().map(|msg| Level::Note.title(msg)));
565
566        format!("{}\n", self.error_renderer.render(msg))
567    }
568
569    pub async fn emit_error(&mut self, err: Error, set_status: bool) {
570        let msg = self.render_error(&err);
571        let _: ExecResult<_> = self.io().stderr.write_all(msg).await;
572        if set_status {
573            self.set_last_status(err.to_status());
574        }
575    }
576
577    pub async fn list_funcs<B>(
578        &mut self,
579        f: impl FnMut(&str, Option<&BoxCommand>) -> ControlFlow<B>,
580    ) -> ControlFlow<B> {
581        self.populate_autoload_func_cache().await;
582        self.list_funcs_without_autoload(f)
583    }
584
585    pub fn list_funcs_without_autoload<B>(
586        &self,
587        mut f: impl FnMut(&str, Option<&BoxCommand>) -> ControlFlow<B>,
588    ) -> ControlFlow<B> {
589        let global_funcs = self.global_funcs.borrow();
590        let global_funcs = global_funcs
591            .iter()
592            .map(|(name, cmd)| (&name[..], Some(cmd)));
593
594        let builtin_funcs = self.builtins().map(|(name, cmd)| (name, Some(cmd)));
595
596        let autoload_var = self.get_var(VAR_AUTOLOAD_PATH);
597        let autoload_cache = autoload_var.as_ref().map(|var| var.name_to_path_cache());
598        let autoload_funcs = autoload_cache
599            .as_ref()
600            .and_then(|cache| cache.as_ref())
601            .into_iter()
602            .flat_map(|map| map.keys())
603            .map(|name| (name.as_str(), None));
604
605        for (name, cmd) in global_funcs
606            .merge_join_by(autoload_funcs, |lhs, rhs| lhs.0.cmp(rhs.0))
607            .map(|either| either.into_left())
608            .merge_join_by(builtin_funcs, |lhs, rhs| lhs.0.cmp(rhs.0))
609            .map(|either| either.into_left())
610        {
611            f(name, cmd)?;
612        }
613        ControlFlow::Continue(())
614    }
615
616    pub async fn get_or_autoload_func(&mut self, name: &str) -> Option<BoxCommand> {
617        if let Some(func) = self.get_global_func(name) {
618            return Some(func);
619        }
620
621        if validate_function_name(name).is_ok() && self.try_autoload_func(name).await.is_some() {
622            if let Some(cmd) = self.get_global_func(name) {
623                return Some(cmd);
624            }
625        }
626
627        self.get_builtin(name)
628    }
629
630    pub fn has_special_var(&self, name: &str) -> bool {
631        self.special_vars.contains_key(name)
632    }
633
634    pub fn get_special_var(&self, name: &str) -> Option<ValueList> {
635        self.special_vars.get(name).map(|getter| getter(self))
636    }
637
638    pub fn local_vars(&self) -> impl Iterator<Item = (&str, &Variable)> {
639        let mut iter = self.local_vars_by_name.iter().peekable();
640        std::iter::from_fn(move || {
641            let ((name, sid), elem) = iter.next()?;
642            while iter.next_if(|((_, next_sid), _)| sid == next_sid).is_some() {}
643            Some((name.as_str(), elem))
644        })
645    }
646
647    pub fn list_vars<B>(
648        &self,
649        scope: VarScope,
650        mut f: impl FnMut(&str, &Variable) -> ControlFlow<B>,
651    ) -> ControlFlow<B> {
652        match scope {
653            // Does not quite make sense, fish also returns empty for this.
654            VarScope::Function => ControlFlow::Continue(()),
655            VarScope::Global => self
656                .global_vars()
657                .iter()
658                .try_for_each(|(name, var)| f(name, var)),
659            VarScope::Local => self.local_vars().try_for_each(|(name, var)| f(name, var)),
660            VarScope::Auto => itertools::merge_join_by(
661                self.local_vars(),
662                self.global_vars()
663                    .iter()
664                    .map(|(name, var)| (name.as_str(), var)),
665                |lhs, rhs| Ord::cmp(lhs.0, rhs.0),
666            )
667            .map(|either| either.into_left())
668            .try_for_each(|(name, var)| f(name, var)),
669            VarScope::Universal => todo!(),
670        }
671    }
672
673    fn get_local_var(&self, name: String) -> Option<(&Variable, u32)> {
674        let key = (name, Reverse(u32::MAX));
675        let ((varname, sid), val) = self.local_vars_by_name.range(&key..).next()?;
676        (*varname == key.0).then_some((val, sid.0))
677    }
678
679    fn get_local_var_mut(&mut self, name: String) -> Option<(&mut Variable, u32)> {
680        let key = (name, Reverse(u32::MAX));
681        let ((varname, sid), val) = self.local_vars_by_name.range_mut(&key..).next()?;
682        (*varname == key.0).then_some((val, sid.0))
683    }
684
685    pub fn get_var(&self, name: &str) -> Option<impl Deref<Target = Variable> + use<'_>> {
686        if let Some(value) = self.get_special_var(name) {
687            return Some(Either::Left(Cow::Owned(Variable::from(value))));
688        }
689        if let Some((var, _sid)) = self.get_local_var(name.to_owned()) {
690            return Some(Either::Left(Cow::Borrowed(var)));
691        }
692        if let Some(var) = self.get_global_var(name) {
693            return Some(Either::Right(var));
694        }
695        None
696    }
697
698    fn set_local_var_at(&mut self, name: String, sid: u32, var: Variable) {
699        if self
700            .local_vars_by_name
701            .insert((name.clone(), Reverse(sid)), var)
702            .is_none()
703        {
704            self.local_vars_by_scope.insert((sid, name));
705        }
706    }
707
708    pub fn set_var(&mut self, name: impl Into<String>, scope: VarScope, var: impl Into<Variable>) {
709        let (name, mut var) = (name.into(), var.into());
710        if name == VAR_AUTOLOAD_PATH {
711            var = var.with_cache();
712        }
713        match scope {
714            VarScope::Local | VarScope::Function => {
715                let sid = if scope == VarScope::Local {
716                    self.cur_scope_idx
717                } else {
718                    0
719                };
720                self.set_local_var_at(name, sid, var);
721            }
722            VarScope::Global => {
723                self.set_global_var(name, var);
724            }
725            VarScope::Universal => todo!(),
726            VarScope::Auto => {
727                if let Some((place, _)) = self.get_local_var_mut(name.clone()) {
728                    *place = var;
729                } else if let Some(place) = self.global_vars.borrow_mut().get_mut(&name) {
730                    *place = var;
731                } else {
732                    // Function scope by default.
733                    self.set_local_var_at(name, 0, var);
734                }
735            }
736        }
737    }
738
739    fn remove_local_var_at(&mut self, name: String, sid: u32) -> bool {
740        let key = (name, Reverse(sid));
741        if self.local_vars_by_name.remove(&key).is_some() {
742            assert!(self.local_vars_by_scope.remove(&(sid, key.0)));
743            true
744        } else {
745            false
746        }
747    }
748
749    pub fn remove_var(&mut self, scope: VarScope, name: &str) -> bool {
750        match scope {
751            VarScope::Local if self.cur_scope_idx > 0 => {
752                if let Some((_var, sid)) = self.get_local_var(name.to_owned()) {
753                    assert!(self.remove_local_var_at(name.to_owned(), sid));
754                    true
755                } else {
756                    false
757                }
758            }
759            VarScope::Local | VarScope::Function => self.remove_local_var_at(name.to_owned(), 0),
760            VarScope::Global => self.remove_global_var(name),
761            VarScope::Universal => todo!(),
762            VarScope::Auto => {
763                self.remove_var(VarScope::Local, name) || self.remove_global_var(name)
764            }
765        }
766    }
767
768    /// Populate autoload functions cache if it's uninitialized or dirty.
769    pub async fn populate_autoload_func_cache(&mut self) {
770        let Some(var) = self.get_var(VAR_AUTOLOAD_PATH) else {
771            return;
772        };
773
774        if var.name_to_path_cache().is_some() {
775            return;
776        }
777
778        let paths = var.value.clone();
779        let map = tokio::task::spawn_blocking(move || {
780            let mut map = BTreeMap::new();
781            for (name, path) in paths
782                .iter()
783                .flat_map(std::fs::read_dir)
784                .flatten()
785                .filter_map(|ent| {
786                    let ent = ent.ok()?;
787                    ent.file_type().ok().filter(|ft| ft.is_file())?;
788                    let mut name = ent.file_name().into_string().ok()?;
789                    name.truncate(name.strip_suffix(AUTOLOAD_NAME_SUFFIX)?.len());
790                    let path = ent.path();
791                    access_can_read(&path).then_some((name, path))
792                })
793            {
794                map.entry(name).or_insert(path);
795            }
796            map
797        })
798        .await
799        .expect("no panic");
800
801        var.set_name_to_path_cache(map);
802    }
803
804    /// Try to find and source the autoload function `name`.
805    ///
806    /// Return `Some(())` if a file is found and sourced, `None` otherwise.
807    /// It does not check if the sourced file actually contains any relevant definition.
808    async fn try_autoload_func(&mut self, name: &str) -> Option<()> {
809        debug_assert!(!name.contains("/"));
810
811        self.populate_autoload_func_cache().await;
812        let path = self
813            .get_var(VAR_AUTOLOAD_PATH)?
814            .name_to_path_cache()
815            .as_ref()
816            .expect("populated")
817            .get(name)?
818            .to_owned();
819
820        let (ret, path) =
821            tokio::task::spawn_blocking(move || (std::fs::read_to_string(&path), path))
822                .await
823                .expect("no panic");
824        match ret {
825            Ok(text) => {
826                self.exec_source(Some(path.to_string_lossy().into_owned()), text)
827                    .await;
828                Some(())
829            }
830            Err(err) => {
831                self.emit_error(Error::ReadWrite(err), false).await;
832                None
833            }
834        }
835    }
836
837    pub fn locate_external_command(&self, name: &str) -> LocateExternalCommand {
838        // Absolute or relative path.
839        if name.contains("/") {
840            let p = Path::new(name);
841            return match p.metadata() {
842                Ok(m) => {
843                    if m.is_dir() {
844                        LocateExternalCommand::Dir(p.into())
845                    } else if m.is_file() && access_can_exec(p) {
846                        LocateExternalCommand::ExecFile(p.into())
847                    } else {
848                        LocateExternalCommand::NotExecFile(p.into())
849                    }
850                }
851                Err(_) => LocateExternalCommand::NotFound,
852            };
853        }
854
855        // Special case.
856        if name == ".." {
857            return LocateExternalCommand::Dir("..".into());
858        }
859
860        // Finally, search for external commands in PATH.
861        if let Some(path) = self.get_var(VAR_PATH) {
862            let mut found_file = None;
863            for dir in path.value.iter().flat_map(|s| s.split(':')) {
864                let p = Path::new(dir).join(name);
865                if let Ok(m) = p.metadata() {
866                    if m.is_file() && access_can_exec(&p) {
867                        return LocateExternalCommand::ExecFile(p);
868                    }
869                    found_file.get_or_insert(p);
870                }
871            }
872            if let Some(p) = found_file {
873                // No special treatment for directories here.
874                return LocateExternalCommand::NotExecFile(p);
875            }
876        }
877
878        LocateExternalCommand::NotFound
879    }
880
881    pub fn enter_local_scope(&mut self) -> impl DerefMut<Target = &mut Self> {
882        self.cur_scope_idx += 1;
883        scopeguard::guard(self, |this| {
884            let scope = this
885                .local_vars_by_scope
886                .split_off(&(this.cur_scope_idx, String::new()));
887            for (sid, name) in scope {
888                this.local_vars_by_name.remove(&(name, Reverse(sid)));
889            }
890            this.cur_scope_idx -= 1;
891        })
892    }
893
894    pub async fn exec_source(&mut self, origin: Option<String>, text: String) {
895        self.backtrace.push(FrameInfo {
896            kind: FrameKind::Source,
897            source: Rc::new(SourceFile { origin, text }),
898            pos: 0,
899        });
900        let mut this = scopeguard::guard(self, |this| {
901            this.backtrace.pop();
902        });
903        let text = &this.backtrace.last().unwrap().source.text;
904
905        let ast = match parse_source(text) {
906            Ok(src) => src,
907            Err(mut errs) => {
908                // FIXME: More errors?
909                errs.truncate(1);
910                let err = Error::SyntaxError(errs.pop().unwrap());
911                this.emit_error(err, true).await;
912                return;
913            }
914        };
915
916        for stmt in &ast.stmts {
917            if this.exec_stmt(stmt).await.is_break() {
918                break;
919            }
920        }
921    }
922
923    async fn exec_stmt(&mut self, stmt: &Stmt) -> ExecControlFlow {
924        Box::pin(self.exec_stmt_inner(stmt)).await
925    }
926
927    async fn exec_stmt_inner(&mut self, stmt: &Stmt) -> ExecControlFlow {
928        macro_rules! bail {
929            ($err:expr) => {
930                bail!(self, $err)
931            };
932            ($this:expr, $err:expr) => {{
933                $this.emit_error($err, true).await;
934                return ControlFlow::Continue(());
935            }};
936        }
937        macro_rules! tri {
938            ($e:expr) => {
939                tri!(self, $e)
940            };
941            ($this:expr, $e:expr) => {
942                match $e {
943                    Ok(v) => v,
944                    Err(err) => bail!($this, err),
945                }
946            };
947        }
948
949        self.backtrace.last_mut().unwrap().pos = stmt.pos();
950
951        match stmt {
952            Stmt::Command(_pos, words) => {
953                let words = self.expand_words(words).await;
954                tri!(self.exec_command(&words).await);
955            }
956            Stmt::Block(_pos, stmts) => {
957                // FIXME: Exclude group-only blocks, eg. if conditions.
958                let this = &mut **self.enter_local_scope();
959                for stmt in stmts {
960                    this.exec_stmt(stmt).await?;
961                }
962            }
963            Stmt::If(_pos, cond, then, else_) => {
964                self.exec_stmt(cond).await?;
965                if self.last_status().is_success() {
966                    self.exec_stmt(then).await?;
967                } else if let Some(else_) = else_ {
968                    self.exec_stmt(else_).await?;
969                }
970            }
971            Stmt::Switch(..) => todo!(),
972            Stmt::While(_pos, cond, body) => loop {
973                self.exec_stmt(cond).await?;
974                if !self.last_status().is_success() {
975                    break;
976                }
977                match self.exec_stmt(body).await {
978                    ControlFlow::Break(ExecBreak::LoopContinue) => {}
979                    ControlFlow::Break(ExecBreak::LoopBreak) => break,
980                    ctl => ctl?,
981                }
982            },
983            Stmt::For(_pos, var, elem_ws, body) => {
984                let var = self.expand_words(slice::from_ref(var)).await;
985                if var.len() != 1 {
986                    bail!(Error::NotOneWord(var.len()));
987                }
988                let var = tri!(validate_variable_name(&var[0]));
989                let elems = self.expand_words(elem_ws).await;
990                for elem in elems {
991                    self.set_var(var, VarScope::Auto, elem);
992                    match self.exec_stmt(body).await {
993                        ControlFlow::Break(ExecBreak::LoopContinue) => {}
994                        ControlFlow::Break(ExecBreak::LoopBreak) => break,
995                        ctl => ctl?,
996                    }
997                }
998            }
999            Stmt::Break(_pos) => return ControlFlow::Break(ExecBreak::LoopBreak),
1000            Stmt::Continue(_pos) => return ControlFlow::Break(ExecBreak::LoopContinue),
1001            Stmt::Function(pos, words, stmt) => {
1002                let words = &*self.expand_words(words).await;
1003                let name = tri!(words.first().ok_or(Error::NotOneWord(0)));
1004                let name = tri!(validate_function_name(name));
1005                let opts = tri!(
1006                    <FunctionOpts as clap::Parser>::try_parse_from(words)
1007                        .map_err(Error::InvalidOptions)
1008                );
1009                let user_func = UserFunc(Rc::new(UserFuncImpl {
1010                    name: name.to_owned(),
1011                    // FIXME: Slow?
1012                    stmt: (**stmt).clone(),
1013                    description: opts.description,
1014                    source: self.backtrace.last().unwrap().source.clone(),
1015                    def_pos: *pos,
1016                }));
1017                self.set_global_func(name.into(), user_func);
1018            }
1019            Stmt::Return(_, w) => {
1020                let st = match w {
1021                    None => Status::SUCCESS,
1022                    Some(w) => {
1023                        let expanded = self.expand_words(slice::from_ref(w)).await;
1024                        match &expanded[..] {
1025                            [] => Status::SUCCESS,
1026                            [n] => {
1027                                let n =
1028                                    tri!(n.parse().map_err(|_| Error::InvalidInteger(n.into())));
1029                                Status(n)
1030                            }
1031                            _ => bail!(Error::NotOneWord(expanded.len())),
1032                        }
1033                    }
1034                };
1035                self.set_last_status(st);
1036                return ControlFlow::Break(ExecBreak::Return);
1037            }
1038            Stmt::Redirect(_pos, stmt, redirects) => {
1039                let prev_io = self.io().clone();
1040                let mut this = scopeguard::guard(&mut *self, |this| this.io = prev_io);
1041
1042                for redir in redirects {
1043                    let dest = this.expand_words(slice::from_ref(&redir.dest)).await;
1044                    let [dest] = &*dest else {
1045                        bail!(this, Error::NotOneWord(dest.len()));
1046                    };
1047
1048                    let io = 'io: {
1049                        let mut opt = OpenOptions::new();
1050                        match redir.mode {
1051                            RedirectMode::Read | RedirectMode::ReadOrNull => opt.read(true),
1052                            RedirectMode::Write => opt.write(true).create(true),
1053                            RedirectMode::WriteNoClobber => opt.write(true).create_new(true),
1054                            RedirectMode::Append => opt.append(true).create(true),
1055                            RedirectMode::ReadFd | RedirectMode::WriteFd => {
1056                                let port = tri!(
1057                                    this,
1058                                    dest.parse::<RedirectPort>()
1059                                        .map_err(|_| Error::InvalidRedirectionPort(dest.into()))
1060                                );
1061                                break 'io match port {
1062                                    RedirectPort::STDIN => this.io.stdin.clone(),
1063                                    RedirectPort::STDOUT => this.io.stdout.clone(),
1064                                    RedirectPort::STDERR => this.io.stderr.clone(),
1065                                    RedirectPort::STDOUT_STDERR => unreachable!(),
1066                                    _ => todo!(),
1067                                };
1068                            }
1069                        };
1070                        match opt.open(dest) {
1071                            Ok(f) => Io::File(Arc::new(f)),
1072                            Err(_) if redir.mode == RedirectMode::ReadOrNull => Io::Close,
1073                            Err(err) => bail!(this, Error::OpenRedirectionFile(dest.into(), err)),
1074                        }
1075                    };
1076
1077                    match redir.port {
1078                        RedirectPort::STDIN => this.io.stdin = io,
1079                        RedirectPort::STDOUT => this.io.stdout = io,
1080                        RedirectPort::STDERR => this.io.stderr = io,
1081                        RedirectPort::STDOUT_STDERR => {
1082                            (this.io.stdout, this.io.stderr) = (io.clone(), io)
1083                        }
1084                        _ => todo!(),
1085                    }
1086                }
1087
1088                this.exec_stmt(stmt).await?;
1089            }
1090            Stmt::Pipe(pos, pipes, last) => {
1091                let task_cnt = pipes.len() + 1;
1092
1093                async fn task(mut ctx: ExecContext<'_>, stmt: &Stmt) -> Status {
1094                    ctx.exec_stmt(stmt).await;
1095                    ctx.last_status()
1096                }
1097
1098                let pipe_status = {
1099                    let mut tasks = Vec::with_capacity(task_cnt);
1100
1101                    let frame = || FrameInfo {
1102                        // TODO: Should be command pos.
1103                        kind: FrameKind::Pipe { def_pos: *pos },
1104                        source: self.backtrace.last().unwrap().source.clone(),
1105                        pos: *pos,
1106                    };
1107                    let mut subctx = ExecContext::new_inside(self, frame());
1108                    for (lhs, port) in pipes {
1109                        let (tx, rx) = tri!(subctx, Io::new_os_pipe());
1110                        match *port {
1111                            RedirectPort::STDIN => subctx.io.stdin = tx,
1112                            RedirectPort::STDOUT => subctx.io.stdout = tx,
1113                            RedirectPort::STDERR => subctx.io.stderr = tx,
1114                            RedirectPort::STDOUT_STDERR => {
1115                                (subctx.io.stdout, subctx.io.stderr) = (tx.clone(), tx)
1116                            }
1117                            _ => todo!(),
1118                        }
1119                        tasks.push(task(subctx, lhs));
1120
1121                        subctx = ExecContext::new_inside(self, frame());
1122                        subctx.io.stdin = rx;
1123                    }
1124                    tasks.push(task(subctx, last));
1125
1126                    futures_util::future::join_all(tasks).await
1127                };
1128
1129                self.set_last_status(pipe_status.last().copied().unwrap());
1130                self.last_pipe_status = pipe_status;
1131            }
1132            Stmt::Not(_pos, inner) => {
1133                self.exec_stmt(inner).await?;
1134                let ok = self.last_status().is_success();
1135                self.set_last_status(!ok);
1136            }
1137            Stmt::BinaryAnd(_pos, lhs, rhs) => {
1138                self.exec_stmt(lhs).await?;
1139                if self.last_status.is_success() {
1140                    self.exec_stmt(rhs).await?;
1141                }
1142            }
1143            Stmt::UnaryAnd(_pos, inner) => {
1144                if self.last_status.is_success() {
1145                    self.exec_stmt(inner).await?;
1146                }
1147            }
1148            Stmt::BinaryOr(_pos, lhs, rhs) => {
1149                self.exec_stmt(lhs).await?;
1150                if !self.last_status.is_success() {
1151                    self.exec_stmt(rhs).await?;
1152                }
1153            }
1154            Stmt::UnaryOr(_pos, inner) => {
1155                if !self.last_status.is_success() {
1156                    self.exec_stmt(inner).await?;
1157                }
1158            }
1159        }
1160
1161        ControlFlow::Continue(())
1162    }
1163
1164    pub async fn exec_command(&mut self, words: &[String]) -> ExecResult<()> {
1165        let cmd = words.first().ok_or(Error::EmptyCommand)?;
1166
1167        if let Some(cmd) = self.get_or_autoload_func(cmd).await {
1168            cmd.exec(self, words).await;
1169            return Ok(());
1170        }
1171
1172        let exe_path = match self.locate_external_command(cmd) {
1173            LocateExternalCommand::ExecFile(path) => path,
1174            LocateExternalCommand::Dir(path) => {
1175                std::env::set_current_dir(path).map_err(Error::ChangeCurrentDir)?;
1176                self.set_last_status(Status::SUCCESS);
1177                return Ok(());
1178            }
1179            LocateExternalCommand::NotExecFile(path) => {
1180                return Err(Error::CommandNotFound(format!(
1181                    "{} (candidate {} is not an executable file)",
1182                    cmd,
1183                    path.display(),
1184                )));
1185            }
1186            LocateExternalCommand::NotFound => {
1187                return Err(Error::CommandNotFound(cmd.into()));
1188            }
1189        };
1190
1191        let st = self.exec_external_command(&exe_path, words).await?;
1192        self.set_last_status(st);
1193        Ok(())
1194    }
1195
1196    pub async fn exec_external_command(
1197        &mut self,
1198        exe_path: &Path,
1199        args_with_0: &[String],
1200    ) -> ExecResult<Status> {
1201        use std::future::ready;
1202
1203        async fn copy_stdio_to_sink(
1204            rdr: impl AsyncRead + Unpin,
1205            sink: StdioCollectSink,
1206        ) -> ExecResult<()> {
1207            use tokio::io::AsyncBufReadExt;
1208
1209            let mut stdout = tokio::io::BufReader::new(rdr);
1210            loop {
1211                let buf = stdout.fill_buf().await.map_err(Error::ReadWrite)?;
1212                if buf.is_empty() {
1213                    return Ok(());
1214                }
1215                sink(buf)?;
1216                let len = buf.len();
1217                stdout.consume(len);
1218            }
1219        }
1220
1221        let (mut os_stdin, stdin_sink) = self.io.stdin.to_stdio()?;
1222        if stdin_sink.is_some() {
1223            os_stdin = std::process::Stdio::null();
1224        }
1225        let (os_stdout, stdout_sink) = self.io.stdout.to_stdio()?;
1226        let (os_stderr, stderr_sink) = self.io.stderr.to_stdio()?;
1227
1228        let mut child = {
1229            let mut builder = tokio::process::Command::new(exe_path);
1230            builder
1231                .kill_on_drop(true)
1232                .arg0(&args_with_0[0])
1233                .args(&args_with_0[1..])
1234                .stdin(os_stdin)
1235                .stdout(os_stdout)
1236                .stderr(os_stderr)
1237                .env_clear();
1238            self.list_vars::<()>(VarScope::Auto, |name, var| {
1239                if var.export {
1240                    builder.env(name, var.value.join(" "));
1241                }
1242                ControlFlow::Continue(())
1243            });
1244            builder
1245                .spawn()
1246                .map_err(|err| Error::SpawnProcess(exe_path.into(), err))?
1247        };
1248
1249        let mut copy_stdout = Either::Left(ready(ExecResult::Ok(())));
1250        let mut copy_stderr = Either::Left(ready(ExecResult::Ok(())));
1251        if let Some(sink) = stdout_sink {
1252            copy_stdout = Either::Right(copy_stdio_to_sink(child.stdout.take().unwrap(), sink));
1253        }
1254        if let Some(sink) = stderr_sink {
1255            copy_stderr = Either::Right(copy_stdio_to_sink(child.stderr.take().unwrap(), sink));
1256        }
1257
1258        let (ret_wait, ret_stdout, ret_stderr) =
1259            tokio::join!(child.wait(), copy_stdout, copy_stderr);
1260        ret_stdout?;
1261        ret_stderr?;
1262        let status = ret_wait.map_err(Error::WaitProcess)?;
1263        if status.success() {
1264            return Ok(Status::SUCCESS);
1265        }
1266
1267        if let Some(code) = status.code() {
1268            return Ok(Status(u8::try_from(code).unwrap_or(u8::MAX)));
1269        }
1270
1271        #[cfg(unix)]
1272        {
1273            use std::os::unix::process::ExitStatusExt;
1274            if let Some(sig) = status.signal() {
1275                let sig = sig.clamp(0, 128) as u8;
1276                return Ok(Status(127 + sig));
1277            }
1278        }
1279
1280        unreachable!();
1281    }
1282
1283    async fn expand_words(&mut self, words: &[ast::Word]) -> Vec<String> {
1284        let mut out = Vec::new();
1285        for w in words {
1286            self.expand_word_into(&mut out, w).await;
1287        }
1288        out
1289    }
1290
1291    // TODO: Word and size limit.
1292    async fn expand_word_into(&mut self, out: &mut Vec<String>, word: &ast::Word) {
1293        fn dfs(
1294            ret: &mut Vec<String>,
1295            stack: &mut String,
1296            frags: &[WordFrag],
1297            expanded: &[Vec<String>],
1298        ) {
1299            let Some((frag, rest_frags)) = frags.split_first() else {
1300                ret.push(stack.clone());
1301                return;
1302            };
1303            let prev_len = stack.len();
1304            match frag {
1305                WordFrag::Literal(s) => {
1306                    stack.push_str(s);
1307                    dfs(ret, stack, rest_frags, expanded);
1308                    stack.truncate(prev_len);
1309                }
1310                WordFrag::Variable { .. }
1311                | WordFrag::VariableNoSplit { .. }
1312                | WordFrag::Command(_)
1313                | WordFrag::CommandNoSplit(_)
1314                | WordFrag::Brace(_)
1315                | WordFrag::Home { .. } => {
1316                    let (words, rest_computed) = expanded.split_first().unwrap();
1317                    for w in words {
1318                        stack.push_str(w);
1319                        dfs(ret, stack, rest_frags, rest_computed);
1320                        stack.truncate(prev_len);
1321                    }
1322                }
1323                WordFrag::Wildcard | WordFrag::WildcardRecursive => {
1324                    todo!()
1325                }
1326            }
1327        }
1328
1329        // Pre-expand.
1330        let frags = match word {
1331            ast::Word::Simple(w) => {
1332                out.push(w.clone());
1333                return;
1334            }
1335            ast::Word::Complex(frags) => frags,
1336        };
1337
1338        let mut frag_expanded = Vec::with_capacity(frags.len());
1339        for frag in frags {
1340            match frag {
1341                WordFrag::Literal(_) => {}
1342                WordFrag::Variable { name, deref } | WordFrag::VariableNoSplit { name, deref } => {
1343                    let var1 = self.get_var(name);
1344                    let var2;
1345                    let mut vals = var1.as_ref().map_or(&[][..], |var| &var.value[..]);
1346                    match (*deref, vals.len()) {
1347                        (0, _) => {}
1348                        (1, 0) => vals = &[],
1349                        (1, 1) => {
1350                            var2 = self.get_var(&vals[0]);
1351                            vals = var2.as_ref().map_or(&[], |var| &var.value[..]);
1352                        }
1353                        _ => todo!(),
1354                    }
1355                    if matches!(frag, WordFrag::Variable { .. }) {
1356                        frag_expanded.push(vals.to_vec());
1357                    } else {
1358                        frag_expanded.push(vec![vals.join(" ")]);
1359                    }
1360                }
1361                WordFrag::Command(stmt) | WordFrag::CommandNoSplit(stmt) => {
1362                    let pos = stmt.pos();
1363                    let frame = FrameInfo {
1364                        kind: FrameKind::CommandSubst { def_pos: pos },
1365                        source: self.backtrace.last().unwrap().source.clone(),
1366                        pos,
1367                    };
1368
1369                    let buf = {
1370                        let buf = Rc::new(RefCell::new(Vec::new()));
1371                        let buf_weak = Rc::downgrade(&buf);
1372                        // FIXME: Avoid double Rc?
1373                        let io_collect = Io::Collect(Rc::new(move |bytes| {
1374                            buf_weak
1375                                .upgrade()
1376                                .ok_or(Error::PipeClosed)?
1377                                .borrow_mut()
1378                                .extend_from_slice(bytes);
1379                            Ok(Status::SUCCESS)
1380                        }));
1381
1382                        self.backtrace.push(frame);
1383                        let prev_stdout = mem::replace(&mut self.io.stdout, io_collect);
1384                        let mut this = scopeguard::guard(&mut *self, move |this| {
1385                            this.io.stdout = prev_stdout;
1386                            this.backtrace.pop();
1387                        });
1388                        this.exec_stmt(stmt).await;
1389                        Rc::into_inner(buf).unwrap().into_inner()
1390                    };
1391                    let mut buf = String::from_utf8(buf).expect("TODO");
1392
1393                    if let WordFrag::CommandNoSplit(_) = frag {
1394                        let len = buf.trim_end_matches('\n').len();
1395                        buf.truncate(len);
1396                        frag_expanded.push(vec![buf]);
1397                    } else {
1398                        frag_expanded.push(buf.lines().map(|s| s.to_owned()).collect());
1399                    }
1400                }
1401                WordFrag::Brace(words) => {
1402                    let mut alts = Vec::with_capacity(words.len());
1403                    for w in words {
1404                        Box::pin(self.expand_word_into(&mut alts, w)).await;
1405                    }
1406                    frag_expanded.push(alts);
1407                }
1408                WordFrag::Home { slash } => {
1409                    frag_expanded.push(self.get_home(*slash).into_iter().collect());
1410                }
1411                WordFrag::Wildcard | WordFrag::WildcardRecursive => {
1412                    todo!()
1413                }
1414            }
1415        }
1416
1417        if frag_expanded.iter().all(|alts| !alts.is_empty()) {
1418            dfs(out, &mut String::new(), frags, &frag_expanded);
1419        }
1420    }
1421
1422    fn get_home(&self, slash: bool) -> Option<String> {
1423        let var = self.get_var(VAR_HOME)?;
1424        let mut s = var.value.join(" ");
1425        let pos = s.trim_end_matches('/').len();
1426        s.truncate(pos);
1427        if slash || s.is_empty() {
1428            s.push('/');
1429        }
1430        Some(s)
1431    }
1432}