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_builtin::set::SHELL_OPTIONS_HELP;
35use yash_env::Env;
36use yash_env::RealSystem;
37use yash_env::option::{Interactive, On, Portable};
38use yash_env::semantics::{Divert, ExitStatus, exit_or_raise};
39use yash_env::system::concurrency::WriteAll;
40use yash_env::system::resource::GetRlimit;
41use yash_env::system::{
42 Chdir, Concurrent, Disposition, Errno, GetCwd, GetUid, Isatty, Sigaction as _, Signals as _,
43 Sysconf, TcGetPgrp, Times, Umask, Write,
44};
45use yash_semantics::trap::run_exit_trap;
46use yash_semantics::{Runtime, interactive_read_eval_loop, read_eval_loop};
47
48async fn print_help<S>(env: &mut Env<S>)
49where
50 S: Isatty + WriteAll,
51{
52 let mut help = "\
53Usage:
54 yash3 [OPTION...] [FILE [ARGUMENT...]]
55 yash3 [OPTION...] -c COMMAND [COMMAND_NAME [ARGUMENT...]]
56 yash3 [OPTION...] -s [ARGUMENT...]
57
58Startup options:
59 -c, --cmdline
60 -s, --stdin
61 -i, --interactive
62 -l, --login
63 --profile=FILE
64 --noprofile
65 --rcfile=FILE
66 --norcfile
67 --help
68 -V, --version
69
70"
71 .to_owned();
72 help.push_str(SHELL_OPTIONS_HELP);
73 help.push_str(concat!(
74 "\nSee <",
75 env!("CARGO_PKG_HOMEPAGE"),
76 "> for details.\n"
77 ));
78
79 let result = yash_builtin::common::output(env, &help).await;
80 env.exit_status = result.exit_status();
81}
82
83async fn print_version<S>(env: &mut Env<S>)
84where
85 S: Isatty + WriteAll,
86{
87 let version = env!("CARGO_PKG_VERSION");
88 let result = yash_builtin::common::output(env, &format!("yash {version}\n")).await;
89 env.exit_status = result.exit_status();
90}
91
92#[allow(
93 clippy::await_holding_refcell_ref,
94 reason = "`print_error` does not run concurrently with the input decorators or read-eval loop"
95)]
96async fn run_as_shell_process<S>(env: &mut Env<S>)
97where
98 S: Chdir
99 + Clone
100 + GetCwd
101 + GetRlimit
102 + GetUid
103 + Runtime
104 + Sysconf
105 + TcGetPgrp
106 + Times
107 + Umask
108 + Write
109 + 'static,
110{
111 let run = match self::startup::args::parse(std::env::args()) {
113 Ok(Parse::Help) => return print_help(env).await,
114 Ok(Parse::Version) => return print_version(env).await,
115 Ok(Parse::Run(run)) => run,
116 Err(e) => {
117 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
118 env.system.print_error(&format!("{arg0}: {e}\n")).await;
119 env.exit_status = ExitStatus::ERROR;
120 return;
121 }
122 };
123
124 let portable = run
126 .options
127 .iter()
128 .rev()
129 .find_map(|&(option, state)| (option == Portable).then_some(state))
130 == Some(On);
131 env.variables.extend_env(
132 std::env::vars()
133 .filter(|(name, _)| !portable || yash_env::variable::is_portable_variable_name(name)),
134 );
135
136 let work = self::startup::configure_environment(env, run).await;
137
138 let is_interactive = env.options.get(Interactive) == On;
139
140 run_rcfile(env, work.rcfile).await;
143
144 let ref_env = RefCell::new(env);
146 let lexer = match prepare_input(&ref_env, &work.source).await {
147 Ok(lexer) => lexer,
148 Err(e) => {
149 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
150 let message = format!("{arg0}: {e}\n");
151 let mut env = ref_env.borrow_mut();
156 env.system.print_error(&message).await;
157 env.exit_status = match e.errno {
158 Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
159 _ => ExitStatus::NOEXEC,
160 };
161 return;
162 }
163 };
164
165 let result = if is_interactive {
167 interactive_read_eval_loop(&ref_env, &mut { lexer }).await
168 } else {
169 read_eval_loop(&ref_env, &mut { lexer }).await
170 };
171
172 let env = ref_env.into_inner();
173 env.apply_result(result);
174
175 match result {
176 Continue(())
177 | Break(Divert::Continue { .. })
178 | Break(Divert::Break { .. })
179 | Break(Divert::Return(_))
180 | Break(Divert::Interrupt(_))
181 | Break(Divert::Exit(_)) => run_exit_trap(env).await,
182 Break(Divert::Abort(_)) => (),
183 }
184}
185
186pub fn main() -> ! {
187 let system = unsafe { RealSystem::new() };
190
191 system
195 .sigaction(RealSystem::SIGPIPE, Disposition::Default)
196 .ok();
197
198 let system = Rc::new(Concurrent::new(system));
199 let runner = Rc::clone(&system);
200 let task = async {
201 let mut env = Env::with_system(system);
202 run_as_shell_process(&mut env).await;
203 exit_or_raise(&env.system, env.exit_status).await
204 };
205 runner.run_real(task)
206}