yash_cli/
lib.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! This is a library crate that implements the command-line frontend for the
18//! yash shell. It is used by the `yash3` binary crate to provide the shell
19//! functionality. Currently, this crate is not intended to be used as a library
20//! by other crates.
21//!
22//! The entry point for the shell is the [`main`] function, which is to be used
23//! as the `main` function in the binary crate. The function sets up the shell
24//! environment and runs the main read-eval loop.
25
26pub mod startup;
27// mod runner;
28
29use self::startup::args::Parse;
30use self::startup::init_file::run_rcfile;
31use self::startup::input::prepare_input;
32use self::startup::input::SourceInput;
33use std::cell::RefCell;
34use std::num::NonZeroU64;
35use std::ops::ControlFlow::{Break, Continue};
36use yash_env::option::{Interactive, On};
37use yash_env::signal;
38use yash_env::system::{Disposition, Errno};
39use yash_env::Env;
40use yash_env::RealSystem;
41use yash_env::System;
42use yash_executor::Executor;
43use yash_semantics::trap::run_exit_trap;
44use yash_semantics::{interactive_read_eval_loop, read_eval_loop};
45use yash_semantics::{Divert, ExitStatus};
46use yash_syntax::parser::lex::Lexer;
47
48async fn print_version(env: &mut Env) -> ExitStatus {
49    let version = env!("CARGO_PKG_VERSION");
50    let result = yash_builtin::common::output(env, &format!("yash {}\n", version)).await;
51    result.exit_status()
52}
53
54// The RefCell is local to this function, so it is safe to keep borrows across await points.
55#[allow(clippy::await_holding_refcell_ref)]
56async fn parse_and_print(mut env: Env) -> ExitStatus {
57    // Parse the command-line arguments
58    let run = match self::startup::args::parse(std::env::args()) {
59        Ok(Parse::Help) => todo!("print help"),
60        Ok(Parse::Version) => return print_version(&mut env).await,
61        Ok(Parse::Run(run)) => run,
62        Err(e) => {
63            let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
64            env.system.print_error(&format!("{}: {}\n", arg0, e)).await;
65            return ExitStatus::ERROR;
66        }
67    };
68
69    // Import environment variables
70    env.variables.extend_env(std::env::vars());
71
72    let work = self::startup::configure_environment(&mut env, run);
73
74    let is_interactive = env.options.get(Interactive) == On;
75
76    // Run initialization files
77    // TODO run profile if login
78    run_rcfile(&mut env, work.rcfile).await;
79
80    // Prepare the input for the main read-eval loop
81    let ref_env = &RefCell::new(&mut env);
82    let SourceInput { input, source } = match prepare_input(ref_env, &work.source) {
83        Ok(input) => input,
84        Err(e) => {
85            let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
86            let message = format!("{}: {}\n", arg0, e);
87            // The borrow checker of Rust 1.79.0 is not smart enough to reason
88            // about the lifetime of `e` here, so we re-borrow from `ref_env`
89            // instead of reusing `env`.
90            // env.system.print_error(&message).await;
91            ref_env.borrow_mut().system.print_error(&message).await;
92            return match e.errno {
93                Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
94                _ => ExitStatus::NOEXEC,
95            };
96        }
97    };
98    let line = NonZeroU64::new(1).unwrap();
99    let mut lexer = Lexer::new(input, line, source.into());
100
101    // Run the read-eval loop
102    let result = if is_interactive {
103        interactive_read_eval_loop(ref_env, &mut { lexer }).await
104    } else {
105        read_eval_loop(ref_env, &mut { lexer }).await
106    };
107
108    env.apply_result(result);
109
110    match result {
111        Continue(())
112        | Break(Divert::Continue { .. })
113        | Break(Divert::Break { .. })
114        | Break(Divert::Return(_))
115        | Break(Divert::Interrupt(_))
116        | Break(Divert::Exit(_)) => run_exit_trap(&mut env).await,
117        Break(Divert::Abort(_)) => (),
118    }
119
120    env.exit_status
121}
122
123pub fn main() -> ! {
124    // SAFETY: This is the only instance of RealSystem we create in the whole
125    // process.
126    let system = unsafe { RealSystem::new() };
127    let mut env = Env::with_system(Box::new(system));
128
129    // Rust by default sets SIGPIPE to SIG_IGN, which is not desired.
130    // As an imperfect workaround, we set SIGPIPE to SIG_DFL here.
131    // TODO Use unix_sigpipe: https://github.com/rust-lang/rust/issues/97889
132    let sigpipe = env
133        .system
134        .signal_number_from_name(signal::Name::Pipe)
135        .unwrap();
136    _ = env.system.sigaction(sigpipe, Disposition::Default);
137
138    let system = env.system.clone();
139    let executor = Executor::new();
140    let task = Box::pin(async {
141        let exit_status = parse_and_print(env).await;
142        std::process::exit(exit_status.0);
143    });
144    // SAFETY: We never create new threads in the whole process, so wakers are
145    // never shared between threads.
146    unsafe { executor.spawn_pinned(task) }
147    loop {
148        executor.run_until_stalled();
149        system.select(false).ok();
150    }
151}