pub mod startup;
use self::startup::args::Parse;
use self::startup::init_file::run_rcfile;
use self::startup::input::prepare_input;
use std::cell::RefCell;
use std::ops::ControlFlow::{Break, Continue};
use std::rc::Rc;
use yash_env::Env;
use yash_env::RealSystem;
use yash_env::option::{Interactive, On, Portable};
use yash_env::semantics::{Divert, ExitStatus, exit_or_raise};
use yash_env::system::concurrency::WriteAll;
use yash_env::system::resource::GetRlimit;
use yash_env::system::{
Chdir, Concurrent, Disposition, Errno, GetCwd, GetUid, Isatty, Sigaction as _, Signals as _,
Sysconf, TcGetPgrp, Times, Umask, Write,
};
use yash_semantics::trap::run_exit_trap;
use yash_semantics::{Runtime, interactive_read_eval_loop, read_eval_loop};
async fn print_version<S>(env: &mut Env<S>)
where
S: Isatty + WriteAll,
{
let version = env!("CARGO_PKG_VERSION");
let result = yash_builtin::common::output(env, &format!("yash {version}\n")).await;
env.exit_status = result.exit_status();
}
#[allow(
clippy::await_holding_refcell_ref,
reason = "`print_error` does not run concurrently with the input decorators or read-eval loop"
)]
async fn run_as_shell_process<S>(env: &mut Env<S>)
where
S: Chdir
+ Clone
+ GetCwd
+ GetRlimit
+ GetUid
+ Runtime
+ Sysconf
+ TcGetPgrp
+ Times
+ Umask
+ Write
+ 'static,
{
let run = match self::startup::args::parse(std::env::args()) {
Ok(Parse::Help) => todo!("print help"),
Ok(Parse::Version) => return print_version(env).await,
Ok(Parse::Run(run)) => run,
Err(e) => {
let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
env.system.print_error(&format!("{arg0}: {e}\n")).await;
env.exit_status = ExitStatus::ERROR;
return;
}
};
let portable = run
.options
.iter()
.rev()
.find_map(|&(option, state)| (option == Portable).then_some(state))
== Some(On);
env.variables.extend_env(
std::env::vars()
.filter(|(name, _)| !portable || yash_env::variable::is_portable_variable_name(name)),
);
let work = self::startup::configure_environment(env, run).await;
let is_interactive = env.options.get(Interactive) == On;
run_rcfile(env, work.rcfile).await;
let ref_env = RefCell::new(env);
let lexer = match prepare_input(&ref_env, &work.source).await {
Ok(lexer) => lexer,
Err(e) => {
let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
let message = format!("{arg0}: {e}\n");
let mut env = ref_env.borrow_mut();
env.system.print_error(&message).await;
env.exit_status = match e.errno {
Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
_ => ExitStatus::NOEXEC,
};
return;
}
};
let result = if is_interactive {
interactive_read_eval_loop(&ref_env, &mut { lexer }).await
} else {
read_eval_loop(&ref_env, &mut { lexer }).await
};
let env = ref_env.into_inner();
env.apply_result(result);
match result {
Continue(())
| Break(Divert::Continue { .. })
| Break(Divert::Break { .. })
| Break(Divert::Return(_))
| Break(Divert::Interrupt(_))
| Break(Divert::Exit(_)) => run_exit_trap(env).await,
Break(Divert::Abort(_)) => (),
}
}
pub fn main() -> ! {
let system = unsafe { RealSystem::new() };
system
.sigaction(RealSystem::SIGPIPE, Disposition::Default)
.ok();
let system = Rc::new(Concurrent::new(system));
let runner = Rc::clone(&system);
let task = async {
let mut env = Env::with_system(system);
run_as_shell_process(&mut env).await;
exit_or_raise(&env.system, env.exit_status).await
};
runner.run_real(task)
}