1pub mod startup;
26use self::startup::args::Parse;
29use self::startup::init_file::run_rcfile;
30use self::startup::input::prepare_input;
31use std::cell::RefCell;
32use std::ops::ControlFlow::{Break, Continue};
33use std::rc::Rc;
34use yash_env::Env;
35use yash_env::RealSystem;
36use yash_env::option::{Interactive, On, Portable};
37use yash_env::semantics::{Divert, ExitStatus, exit_or_raise};
38use yash_env::system::concurrency::WriteAll;
39use yash_env::system::resource::GetRlimit;
40use yash_env::system::{
41 Chdir, Concurrent, Disposition, Errno, GetCwd, GetUid, Isatty, Sigaction as _, Signals as _,
42 Sysconf, TcGetPgrp, Times, Umask, Write,
43};
44use yash_semantics::trap::run_exit_trap;
45use yash_semantics::{Runtime, interactive_read_eval_loop, read_eval_loop};
46
47async fn print_version<S>(env: &mut Env<S>)
48where
49 S: Isatty + WriteAll,
50{
51 let version = env!("CARGO_PKG_VERSION");
52 let result = yash_builtin::common::output(env, &format!("yash {version}\n")).await;
53 env.exit_status = result.exit_status();
54}
55
56#[allow(
57 clippy::await_holding_refcell_ref,
58 reason = "`print_error` does not run concurrently with the input decorators or read-eval loop"
59)]
60async fn run_as_shell_process<S>(env: &mut Env<S>)
61where
62 S: Chdir
63 + Clone
64 + GetCwd
65 + GetRlimit
66 + GetUid
67 + Runtime
68 + Sysconf
69 + TcGetPgrp
70 + Times
71 + Umask
72 + Write
73 + 'static,
74{
75 let run = match self::startup::args::parse(std::env::args()) {
77 Ok(Parse::Help) => todo!("print help"),
78 Ok(Parse::Version) => return print_version(env).await,
79 Ok(Parse::Run(run)) => run,
80 Err(e) => {
81 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
82 env.system.print_error(&format!("{arg0}: {e}\n")).await;
83 env.exit_status = ExitStatus::ERROR;
84 return;
85 }
86 };
87
88 let portable = run
90 .options
91 .iter()
92 .rev()
93 .find_map(|&(option, state)| (option == Portable).then_some(state))
94 == Some(On);
95 env.variables.extend_env(
96 std::env::vars()
97 .filter(|(name, _)| !portable || yash_env::variable::is_portable_variable_name(name)),
98 );
99
100 let work = self::startup::configure_environment(env, run).await;
101
102 let is_interactive = env.options.get(Interactive) == On;
103
104 run_rcfile(env, work.rcfile).await;
107
108 let ref_env = RefCell::new(env);
110 let lexer = match prepare_input(&ref_env, &work.source).await {
111 Ok(lexer) => lexer,
112 Err(e) => {
113 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
114 let message = format!("{arg0}: {e}\n");
115 let mut env = ref_env.borrow_mut();
120 env.system.print_error(&message).await;
121 env.exit_status = match e.errno {
122 Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
123 _ => ExitStatus::NOEXEC,
124 };
125 return;
126 }
127 };
128
129 let result = if is_interactive {
131 interactive_read_eval_loop(&ref_env, &mut { lexer }).await
132 } else {
133 read_eval_loop(&ref_env, &mut { lexer }).await
134 };
135
136 let env = ref_env.into_inner();
137 env.apply_result(result);
138
139 match result {
140 Continue(())
141 | Break(Divert::Continue { .. })
142 | Break(Divert::Break { .. })
143 | Break(Divert::Return(_))
144 | Break(Divert::Interrupt(_))
145 | Break(Divert::Exit(_)) => run_exit_trap(env).await,
146 Break(Divert::Abort(_)) => (),
147 }
148}
149
150pub fn main() -> ! {
151 let system = unsafe { RealSystem::new() };
154
155 system
159 .sigaction(RealSystem::SIGPIPE, Disposition::Default)
160 .ok();
161
162 let system = Rc::new(Concurrent::new(system));
163 let runner = Rc::clone(&system);
164 let task = async {
165 let mut env = Env::with_system(system);
166 run_as_shell_process(&mut env).await;
167 exit_or_raise(&env.system, env.exit_status).await
168 };
169 runner.run_real(task)
170}