1use 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
35const 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#[derive(Debug, thiserror::Error)]
115pub enum Error {
116 #[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 #[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#[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 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_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 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 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 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 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 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 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 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 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 if name == ".." {
857 return LocateExternalCommand::Dir("..".into());
858 }
859
860 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 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 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 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 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 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 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 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 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}