1use crate::Env;
40use crate::builtin::Builtin;
41use crate::builtin::Type::{Special, Substitutive};
42use crate::function::Function;
43use crate::path::PathBuf;
44use crate::system::IsExecutableFile;
45use crate::variable::Expansion;
46use crate::variable::PATH;
47use std::ffi::CStr;
48use std::ffi::CString;
49use std::rc::Rc;
50
51pub enum Target<S> {
64 Builtin {
66 builtin: Builtin<S>,
68
69 path: CString,
79 },
80
81 Function(Rc<Function<S>>),
83
84 External {
86 path: CString,
97 },
98}
99
100impl<S> Clone for Target<S> {
102 fn clone(&self) -> Self {
103 match self {
104 Self::Builtin { builtin, path } => Self::Builtin {
105 builtin: *builtin,
106 path: path.clone(),
107 },
108 Self::Function(f) => Self::Function(f.clone()),
109 Self::External { path } => Self::External { path: path.clone() },
110 }
111 }
112}
113
114impl<S> PartialEq for Target<S> {
115 fn eq(&self, other: &Self) -> bool {
116 match (self, other) {
117 (
118 Self::Builtin {
119 builtin: l_builtin,
120 path: l_path,
121 },
122 Self::Builtin {
123 builtin: r_builtin,
124 path: r_path,
125 },
126 ) => l_builtin == r_builtin && l_path == r_path,
127 (Self::Function(l), Self::Function(r)) => l == r,
128 (Self::External { path: l_path }, Self::External { path: r_path }) => l_path == r_path,
129 _ => false,
130 }
131 }
132}
133
134impl<S> Eq for Target<S> {}
135
136impl<S> std::fmt::Debug for Target<S> {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 Self::Builtin { builtin, path } => f
140 .debug_struct("Builtin")
141 .field("builtin", builtin)
142 .field("path", path)
143 .finish(),
144 Self::Function(func) => f.debug_tuple("Function").field(func).finish(),
145 Self::External { path } => f.debug_struct("External").field("path", path).finish(),
146 }
147 }
148}
149
150impl<S> From<Rc<Function<S>>> for Target<S> {
151 #[inline]
152 fn from(function: Rc<Function<S>>) -> Target<S> {
153 Target::Function(function)
154 }
155}
156
157impl<S> From<Function<S>> for Target<S> {
158 #[inline]
159 fn from(function: Function<S>) -> Target<S> {
160 Target::Function(function.into())
161 }
162}
163
164pub trait ClassifyEnv<S> {
170 #[must_use]
172 fn builtin(&self, name: &str) -> Option<Builtin<S>>;
173
174 #[must_use]
176 fn function(&self, name: &str) -> Option<&Rc<Function<S>>>;
177}
178
179pub trait PathEnv {
181 #[must_use]
187 fn path(&self) -> Expansion<'_>;
188
189 #[must_use]
191 fn is_executable_file(&self, path: &CStr) -> bool;
192 }
194
195impl<S: IsExecutableFile> PathEnv for Env<S> {
196 fn path(&self) -> Expansion<'_> {
201 self.variables
202 .get(PATH)
203 .and_then(|var| {
204 assert_eq!(var.quirk, None, "PATH does not support quirks");
205 var.value.as_ref()
206 })
207 .into()
208 }
209
210 fn is_executable_file(&self, path: &CStr) -> bool {
211 self.system.is_executable_file(path)
212 }
213}
214
215impl<S> ClassifyEnv<S> for Env<S> {
216 fn builtin(&self, name: &str) -> Option<Builtin<S>> {
217 self.builtins.get(name).copied()
218 }
219
220 #[inline]
221 fn function(&self, name: &str) -> Option<&Rc<Function<S>>> {
222 self.functions.get(name)
223 }
224}
225
226#[must_use]
237pub fn search<S, E: ClassifyEnv<S> + PathEnv>(env: &mut E, name: &str) -> Option<Target<S>> {
238 let mut target = classify(env, name);
239
240 'fill_path: {
241 let path = match &mut target {
242 Target::Builtin { builtin, path } if builtin.r#type == Substitutive => {
243 path
245 }
246
247 Target::External { path } => {
248 if name.contains('/') {
249 *path = CString::new(name).ok()?;
251 break 'fill_path;
252 } else {
253 path
255 }
256 }
257
258 Target::Builtin { .. } | Target::Function(_) => {
259 break 'fill_path;
261 }
262 };
263
264 if let Some(real_path) = search_path(env, name) {
265 *path = real_path;
266 } else {
267 return None;
268 }
269 }
270
271 Some(target)
272}
273
274#[must_use]
285pub fn classify<S, E: ClassifyEnv<S>>(env: &E, name: &str) -> Target<S> {
286 if name.contains('/') {
287 return Target::External {
288 path: CString::default(),
289 };
290 }
291
292 let builtin = env.builtin(name);
293 if let Some(builtin) = builtin {
294 if builtin.r#type == Special {
295 let path = CString::default();
296 return Target::Builtin { builtin, path };
297 }
298 }
299
300 if let Some(function) = env.function(name) {
301 return Rc::clone(function).into();
302 }
303
304 if let Some(builtin) = builtin {
305 let path = CString::default();
306 return Target::Builtin { builtin, path };
307 }
308
309 Target::External {
310 path: CString::default(),
311 }
312}
313
314#[must_use]
324pub fn search_path<E: PathEnv>(env: &mut E, name: &str) -> Option<CString> {
325 env.path()
326 .split()
327 .filter_map(|dir| {
328 let candidate = PathBuf::from_iter([dir, name])
329 .into_unix_string()
330 .into_vec();
331 CString::new(candidate).ok()
332 })
333 .find(|path| env.is_executable_file(path))
334}
335
336#[allow(clippy::field_reassign_with_default)]
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::builtin::Type::{Elective, Extension, Mandatory};
341 use crate::function::{FunctionBody, FunctionBodyObject, FunctionSet};
342 use crate::source::Location;
343 use crate::variable::Value;
344 use assert_matches::assert_matches;
345 use std::collections::HashMap;
346 use std::collections::HashSet;
347
348 #[derive(Default)]
349 struct DummyEnv {
350 builtins: HashMap<&'static str, Builtin<()>>,
351 functions: FunctionSet<()>,
352 path: Expansion<'static>,
353 executables: HashSet<String>,
354 }
355
356 impl PathEnv for DummyEnv {
357 fn path(&self) -> Expansion<'_> {
358 self.path.as_ref()
359 }
360 fn is_executable_file(&self, path: &CStr) -> bool {
361 if let Ok(path) = path.to_str() {
362 self.executables.contains(path)
363 } else {
364 false
365 }
366 }
367 }
368
369 impl ClassifyEnv<()> for DummyEnv {
370 fn builtin(&self, name: &str) -> Option<Builtin<()>> {
371 self.builtins.get(name).copied()
372 }
373 fn function(&self, name: &str) -> Option<&Rc<Function<()>>> {
374 self.functions.get(name)
375 }
376 }
377
378 #[derive(Clone, Debug)]
379 struct FunctionBodyStub;
380
381 impl std::fmt::Display for FunctionBodyStub {
382 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 unreachable!()
384 }
385 }
386 impl<S> FunctionBody<S> for FunctionBodyStub {
387 async fn execute(&self, _: &mut Env<S>) -> crate::semantics::Result {
388 unreachable!()
389 }
390 }
391
392 fn function_body_stub<S>() -> Rc<dyn FunctionBodyObject<S>> {
393 Rc::new(FunctionBodyStub)
394 }
395
396 #[test]
397 fn nothing_is_found_in_empty_env() {
398 let mut env = DummyEnv::default();
399 let target = search(&mut env, "foo");
400 assert!(target.is_none(), "target = {target:?}");
401 }
402
403 #[test]
404 fn nothing_is_found_with_name_unmatched() {
405 let mut env = DummyEnv::default();
406 env.builtins
407 .insert("foo", Builtin::new(Special, |_, _| unreachable!()));
408 let function = Function::new("foo", function_body_stub(), Location::dummy(""));
409 env.functions.define(function).unwrap();
410
411 let target = search(&mut env, "bar");
412 assert!(target.is_none(), "target = {target:?}");
413 }
414
415 #[test]
416 fn classify_defaults_to_external() {
417 let env = DummyEnv::default();
420 let target = classify(&env, "foo");
421 assert_eq!(
422 target,
423 Target::External {
424 path: CString::default()
425 }
426 );
427 }
428
429 #[test]
430 fn special_builtin_is_found() {
431 let mut env = DummyEnv::default();
432 let builtin = Builtin::new(Special, |_, _| unreachable!());
433 env.builtins.insert("foo", builtin);
434
435 assert_matches!(
436 search(&mut env, "foo"),
437 Some(Target::Builtin { builtin: result, path }) => {
438 assert_eq!(result.r#type, builtin.r#type);
439 assert_eq!(*path, *c"");
440 }
441 );
442 assert_matches!(
443 classify(&env, "foo"),
444 Target::Builtin { builtin: result, path } => {
445 assert_eq!(result.r#type, builtin.r#type);
446 assert_eq!(*path, *c"");
447 }
448 );
449 }
450
451 #[test]
452 fn function_is_found_if_not_hidden_by_special_builtin() {
453 let mut env = DummyEnv::default();
454 let function = Rc::new(Function::new(
455 "foo",
456 function_body_stub(),
457 Location::dummy("location"),
458 ));
459 env.functions.define(function.clone()).unwrap();
460
461 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
462 assert_eq!(result, function);
463 });
464 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
465 assert_eq!(result, function);
466 });
467 }
468
469 #[test]
470 fn special_builtin_takes_priority_over_function() {
471 let mut env = DummyEnv::default();
472 let builtin = Builtin::new(Special, |_, _| unreachable!());
473 env.builtins.insert("foo", builtin);
474 let function = Function::new("foo", function_body_stub(), Location::dummy("location"));
475 env.functions.define(function).unwrap();
476
477 assert_matches!(
478 search(&mut env, "foo"),
479 Some(Target::Builtin { builtin: result, path }) => {
480 assert_eq!(result.r#type, builtin.r#type);
481 assert_eq!(*path, *c"");
482 }
483 );
484 assert_matches!(
485 classify(&env, "foo"),
486 Target::Builtin { builtin: result, path } => {
487 assert_eq!(result.r#type, builtin.r#type);
488 assert_eq!(*path, *c"");
489 }
490 );
491 }
492
493 #[test]
494 fn mandatory_builtin_is_found_if_not_hidden_by_function() {
495 let mut env = DummyEnv::default();
496 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
497 env.builtins.insert("foo", builtin);
498
499 assert_matches!(
500 search(&mut env, "foo"),
501 Some(Target::Builtin { builtin: result, path }) => {
502 assert_eq!(result.r#type, builtin.r#type);
503 assert_eq!(*path, *c"");
504 }
505 );
506 assert_matches!(
507 classify(&env, "foo"),
508 Target::Builtin { builtin: result, path } => {
509 assert_eq!(result.r#type, builtin.r#type);
510 assert_eq!(*path, *c"");
511 }
512 );
513 }
514
515 #[test]
516 fn elective_builtin_is_found_if_not_hidden_by_function() {
517 let mut env = DummyEnv::default();
518 let builtin = Builtin::new(Elective, |_, _| unreachable!());
519 env.builtins.insert("foo", builtin);
520
521 assert_matches!(
522 search(&mut env, "foo"),
523 Some(Target::Builtin { builtin: result, path }) => {
524 assert_eq!(result.r#type, builtin.r#type);
525 assert_eq!(*path, *c"");
526 }
527 );
528 assert_matches!(
529 classify(&env, "foo"),
530 Target::Builtin { builtin: result, path } => {
531 assert_eq!(result.r#type, builtin.r#type);
532 assert_eq!(*path, *c"");
533 }
534 );
535 }
536
537 #[test]
538 fn extension_builtin_is_found_if_not_hidden_by_function_or_option() {
539 let mut env = DummyEnv::default();
540 let builtin = Builtin::new(Extension, |_, _| unreachable!());
541 env.builtins.insert("foo", builtin);
542
543 assert_matches!(
544 search(&mut env, "foo"),
545 Some(Target::Builtin { builtin: result, path }) => {
546 assert_eq!(result.r#type, builtin.r#type);
547 assert_eq!(*path, *c"");
548 }
549 );
550 assert_matches!(
551 classify(&env, "foo"),
552 Target::Builtin { builtin: result, path } => {
553 assert_eq!(result.r#type, builtin.r#type);
554 assert_eq!(*path, *c"");
555 }
556 );
557 }
558
559 #[test]
560 fn function_takes_priority_over_mandatory_builtin() {
561 let mut env = DummyEnv::default();
562 env.builtins
563 .insert("foo", Builtin::new(Mandatory, |_, _| unreachable!()));
564
565 let function = Rc::new(Function::new(
566 "foo",
567 function_body_stub(),
568 Location::dummy("location"),
569 ));
570 env.functions.define(function.clone()).unwrap();
571
572 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
573 assert_eq!(result, function);
574 });
575 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
576 assert_eq!(result, function);
577 });
578 }
579
580 #[test]
581 fn function_takes_priority_over_elective_builtin() {
582 let mut env = DummyEnv::default();
583 env.builtins
584 .insert("foo", Builtin::new(Elective, |_, _| unreachable!()));
585
586 let function = Rc::new(Function::new(
587 "foo",
588 function_body_stub(),
589 Location::dummy("location"),
590 ));
591 env.functions.define(function.clone()).unwrap();
592
593 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
594 assert_eq!(result, function);
595 });
596 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
597 assert_eq!(result, function);
598 });
599 }
600
601 #[test]
602 fn function_takes_priority_over_extension_builtin() {
603 let mut env = DummyEnv::default();
604 env.builtins
605 .insert("foo", Builtin::new(Extension, |_, _| unreachable!()));
606
607 let function = Rc::new(Function::new(
608 "foo",
609 function_body_stub(),
610 Location::dummy("location"),
611 ));
612 env.functions.define(function.clone()).unwrap();
613
614 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
615 assert_eq!(result, function);
616 });
617 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
618 assert_eq!(result, function);
619 });
620 }
621
622 #[test]
623 fn substitutive_builtin_is_found_if_external_executable_exists() {
624 let mut env = DummyEnv::default();
625 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
626 env.builtins.insert("foo", builtin);
627 env.path = Expansion::from("/bin");
628 env.executables.insert("/bin/foo".to_string());
629
630 assert_matches!(
631 search(&mut env, "foo"),
632 Some(Target::Builtin { builtin: result, path }) => {
633 assert_eq!(result.r#type, builtin.r#type);
634 assert_eq!(*path, *c"/bin/foo");
635 }
636 );
637 assert_matches!(
638 classify(&env, "foo"),
639 Target::Builtin { builtin: result, path } => {
640 assert_eq!(result.r#type, builtin.r#type);
641 assert_eq!(*path, *c"");
642 }
643 );
644 }
645
646 #[test]
647 fn substitutive_builtin_is_not_found_without_external_executable() {
648 let mut env = DummyEnv::default();
649 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
650 env.builtins.insert("foo", builtin);
651
652 let target = search(&mut env, "foo");
653 assert!(target.is_none(), "target = {target:?}");
654 }
655
656 #[test]
657 fn substitutive_builtin_is_classified_even_without_external_executable() {
658 let mut env = DummyEnv::default();
659 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
660 env.builtins.insert("foo", builtin);
661
662 assert_matches!(
663 classify(&env, "foo"),
664 Target::Builtin { builtin: result, path } => {
665 assert_eq!(result.r#type, builtin.r#type);
666 assert_eq!(*path, *c"");
667 }
668 );
669 }
670
671 #[test]
672 fn function_takes_priority_over_substitutive_builtin() {
673 let mut env = DummyEnv::default();
674 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
675 env.builtins.insert("foo", builtin);
676 env.path = Expansion::from("/bin");
677 env.executables.insert("/bin/foo".to_string());
678
679 let function = Rc::new(Function::new(
680 "foo",
681 function_body_stub(),
682 Location::dummy("location"),
683 ));
684 env.functions.define(function.clone()).unwrap();
685
686 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
687 assert_eq!(result, function);
688 });
689 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
690 assert_eq!(result, function);
691 });
692 }
693
694 #[test]
695 fn external_utility_is_found_if_external_executable_exists() {
696 let mut env = DummyEnv::default();
697 env.path = Expansion::from("/bin");
698 env.executables.insert("/bin/foo".to_string());
699
700 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
701 assert_eq!(*path, *c"/bin/foo");
702 });
703 assert_matches!(classify(&env, "foo"), Target::External { path } => {
704 assert_eq!(*path, *c"");
705 });
706 }
707
708 #[test]
709 fn returns_external_utility_if_name_contains_slash() {
710 let mut env = DummyEnv::default();
712 let builtin = Builtin::new(Special, |_, _| unreachable!());
715 env.builtins.insert("bar/baz", builtin);
716
717 assert_matches!(search(&mut env, "bar/baz"), Some(Target::External { path }) => {
718 assert_eq!(*path, *c"bar/baz");
719 });
720 assert_matches!(classify(&env, "bar/baz"), Target::External { path } => {
721 assert_eq!(*path, *c"");
722 });
723 }
724
725 #[test]
726 fn external_target_is_first_executable_found_in_path_scalar() {
727 let mut env = DummyEnv::default();
728 env.path = Expansion::from("/usr/local/bin:/usr/bin:/bin");
729 env.executables.insert("/usr/bin/foo".to_string());
730 env.executables.insert("/bin/foo".to_string());
731
732 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
733 assert_eq!(*path, *c"/usr/bin/foo");
734 });
735
736 env.executables.insert("/usr/local/bin/foo".to_string());
737
738 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
739 assert_eq!(*path, *c"/usr/local/bin/foo");
740 });
741 }
742
743 #[test]
744 fn external_target_is_first_executable_found_in_path_array() {
745 let mut env = DummyEnv::default();
746 env.path = Expansion::from(Value::array(["/usr/local/bin", "/usr/bin", "/bin"]));
747 env.executables.insert("/usr/bin/foo".to_string());
748 env.executables.insert("/bin/foo".to_string());
749
750 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
751 assert_eq!(*path, *c"/usr/bin/foo");
752 });
753
754 env.executables.insert("/usr/local/bin/foo".to_string());
755
756 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
757 assert_eq!(*path, *c"/usr/local/bin/foo");
758 });
759 }
760
761 #[test]
762 fn empty_string_in_path_names_current_directory() {
763 let mut env = DummyEnv::default();
764 env.path = Expansion::from("/x::/y");
765 env.executables.insert("foo".to_string());
766
767 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
768 assert_eq!(*path, *c"foo");
769 });
770 }
771}