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::fmt::Write as _;
33use std::ops::ControlFlow::{Break, Continue};
34use std::rc::Rc;
35use yash_env::Env;
36use yash_env::RealSystem;
37use yash_env::option::{Interactive, Off, 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 --help
60 -V, --version
61 --profile=FILE
62 --noprofile
63 --rcfile=FILE
64 --norcfile
65
66Shell options:
67 Turn on an option with -o NAME or --NAME, and turn it off with +o NAME or
68 ++NAME. Prefixing NAME with \"no\" reverses the effect, as in -o noglob.
69 Each single-letter option below does the same as the -o form beside it,
70 and swapping its - or + reverses the effect.
71
72"
73 .to_owned();
74 for option in yash_env::option::Option::iter() {
75 match option.short_name() {
76 Some((name, On)) => writeln!(help, " -{name} -o {option}"),
77 Some((name, Off)) => writeln!(help, " +{name} -o {option}"),
78 None => writeln!(help, " -o {option}"),
79 }
80 .unwrap();
81 }
82 help.push_str(concat!(
83 "\nSee <",
84 env!("CARGO_PKG_HOMEPAGE"),
85 "> for details.\n"
86 ));
87
88 let result = yash_builtin::common::output(env, &help).await;
89 env.exit_status = result.exit_status();
90}
91
92async fn print_version<S>(env: &mut Env<S>)
93where
94 S: Isatty + WriteAll,
95{
96 let version = env!("CARGO_PKG_VERSION");
97 let result = yash_builtin::common::output(env, &format!("yash {version}\n")).await;
98 env.exit_status = result.exit_status();
99}
100
101#[allow(
102 clippy::await_holding_refcell_ref,
103 reason = "`print_error` does not run concurrently with the input decorators or read-eval loop"
104)]
105async fn run_as_shell_process<S>(env: &mut Env<S>)
106where
107 S: Chdir
108 + Clone
109 + GetCwd
110 + GetRlimit
111 + GetUid
112 + Runtime
113 + Sysconf
114 + TcGetPgrp
115 + Times
116 + Umask
117 + Write
118 + 'static,
119{
120 let run = match self::startup::args::parse(std::env::args()) {
122 Ok(Parse::Help) => return print_help(env).await,
123 Ok(Parse::Version) => return print_version(env).await,
124 Ok(Parse::Run(run)) => run,
125 Err(e) => {
126 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
127 env.system.print_error(&format!("{arg0}: {e}\n")).await;
128 env.exit_status = ExitStatus::ERROR;
129 return;
130 }
131 };
132
133 let portable = run
135 .options
136 .iter()
137 .rev()
138 .find_map(|&(option, state)| (option == Portable).then_some(state))
139 == Some(On);
140 env.variables.extend_env(
141 std::env::vars()
142 .filter(|(name, _)| !portable || yash_env::variable::is_portable_variable_name(name)),
143 );
144
145 let work = self::startup::configure_environment(env, run).await;
146
147 let is_interactive = env.options.get(Interactive) == On;
148
149 run_rcfile(env, work.rcfile).await;
152
153 let ref_env = RefCell::new(env);
155 let lexer = match prepare_input(&ref_env, &work.source).await {
156 Ok(lexer) => lexer,
157 Err(e) => {
158 let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
159 let message = format!("{arg0}: {e}\n");
160 let mut env = ref_env.borrow_mut();
165 env.system.print_error(&message).await;
166 env.exit_status = match e.errno {
167 Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
168 _ => ExitStatus::NOEXEC,
169 };
170 return;
171 }
172 };
173
174 let result = if is_interactive {
176 interactive_read_eval_loop(&ref_env, &mut { lexer }).await
177 } else {
178 read_eval_loop(&ref_env, &mut { lexer }).await
179 };
180
181 let env = ref_env.into_inner();
182 env.apply_result(result);
183
184 match result {
185 Continue(())
186 | Break(Divert::Continue { .. })
187 | Break(Divert::Break { .. })
188 | Break(Divert::Return(_))
189 | Break(Divert::Interrupt(_))
190 | Break(Divert::Exit(_)) => run_exit_trap(env).await,
191 Break(Divert::Abort(_)) => (),
192 }
193}
194
195pub fn main() -> ! {
196 let system = unsafe { RealSystem::new() };
199
200 system
204 .sigaction(RealSystem::SIGPIPE, Disposition::Default)
205 .ok();
206
207 let system = Rc::new(Concurrent::new(system));
208 let runner = Rc::clone(&system);
209 let task = async {
210 let mut env = Env::with_system(system);
211 run_as_shell_process(&mut env).await;
212 exit_or_raise(&env.system, env.exit_status).await
213 };
214 runner.run_real(task)
215}