use crate::common::report::{report_error, syntax_error};
use crate::common::syntax::{Mode, OptionSpec, parse_arguments};
use std::num::ParseIntError;
use std::ops::ControlFlow::Break;
use yash_env::Env;
use yash_env::builtin::Result;
use yash_env::semantics::Divert;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_syntax::source::Location;
const OPTION_SPECS: &[OptionSpec] = &[OptionSpec::new().short('n').long("no-return")];
async fn operand_parse_error(env: &mut Env, location: &Location, error: ParseIntError) -> Result {
syntax_error(env, &error.to_string(), location).await
}
pub async fn main(env: &mut Env, args: Vec<Field>) -> Result {
let (options, operands) = match parse_arguments(OPTION_SPECS, Mode::with_env(env), args) {
Ok(result) => result,
Err(error) => return report_error(env, &error).await,
};
let mut no_return = false;
for option in options {
match option.spec.get_short() {
Some('n') => no_return = true,
_ => unreachable!("parse_arguments() returned an unknown option"),
}
}
if let Some(arg) = operands.get(1) {
return syntax_error(env, "too many operands", &arg.origin).await;
}
let exit_status = match operands.first() {
None => None,
Some(arg) => match arg.value.parse() {
Ok(exit_status) if exit_status >= 0 => Some(ExitStatus(exit_status)),
Ok(_) => return syntax_error(env, "negative exit status", &arg.origin).await,
Err(e) => return operand_parse_error(env, &arg.origin, e).await,
},
};
if no_return {
Result::new(exit_status.unwrap_or(env.exit_status))
} else {
Result::with_exit_status_and_divert(env.exit_status, Break(Divert::Return(exit_status)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::FutureExt;
use std::rc::Rc;
use yash_env::VirtualSystem;
use yash_env::semantics::ExitStatus;
use yash_env::stack::Builtin;
use yash_env::stack::Frame;
use yash_env_test_helper::assert_stderr;
#[test]
fn return_without_arguments_with_exit_status_0() {
let mut env = Env::new_virtual();
let actual_result = main(&mut env, vec![]).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::SUCCESS, Break(Divert::Return(None)));
assert_eq!(actual_result, expected_result);
}
#[test]
fn return_without_arguments_with_non_zero_exit_status() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus(42);
let actual_result = main(&mut env, vec![]).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus(42), Break(Divert::Return(None)));
assert_eq!(actual_result, expected_result);
}
#[test]
fn returns_exit_status_specified_without_n_option() {
let mut env = Env::new_virtual();
let args = Field::dummies(["42"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result = Result::with_exit_status_and_divert(
ExitStatus::SUCCESS,
Break(Divert::Return(Some(ExitStatus(42)))),
);
assert_eq!(actual_result, expected_result);
}
#[test]
fn returns_exit_status_12_with_n_option() {
let mut env = Env::new_virtual();
let args = Field::dummies(["-n", "12"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result, Result::new(ExitStatus(12)));
}
#[test]
fn returns_exit_status_47_with_n_option() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus(24);
let args = Field::dummies(["-n", "47"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result, Result::new(ExitStatus(47)));
}
#[test]
fn returns_previous_exit_status_with_n_option_without_operand() {
let mut env = Env::new_virtual();
env.exit_status = ExitStatus(24);
let args = Field::dummies(["-n"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result, Result::new(ExitStatus(24)));
}
#[test]
fn return_with_negative_exit_status_operand() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("return"),
is_special: true,
}));
let args = Field::dummies(["-1"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::ERROR, Break(Divert::Interrupt(None)));
assert_eq!(actual_result, expected_result);
assert_stderr(&state, |stderr| {
assert!(stderr.contains("-1"), "stderr = {stderr:?}")
});
}
#[test]
fn exit_with_non_integer_exit_status_operand() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("return"),
is_special: true,
}));
let args = Field::dummies(["foo"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::ERROR, Break(Divert::Interrupt(None)));
assert_eq!(actual_result, expected_result);
assert_stderr(&state, |stderr| {
assert!(stderr.contains("foo"), "stderr = {stderr:?}")
});
}
#[test]
fn return_with_too_large_exit_status_operand() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("return"),
is_special: true,
}));
let args = Field::dummies(["999999999999999999999999999999"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::ERROR, Break(Divert::Interrupt(None)));
assert_eq!(actual_result, expected_result);
assert_stderr(&state, |stderr| {
assert!(
stderr.contains("999999999999999999999999999999"),
"stderr = {stderr:?}"
)
});
}
#[test]
fn option_operand_separator() {
let mut env = Env::new_virtual();
let args = Field::dummies(["-n", "--", "12"]);
let result = main(&mut env, args).now_or_never().unwrap();
assert_eq!(result, Result::new(ExitStatus(12)));
}
#[test]
fn return_with_too_many_operands() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("return"),
is_special: true,
}));
let args = Field::dummies(["1", "2"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::ERROR, Break(Divert::Interrupt(None)));
assert_eq!(actual_result, expected_result);
assert_stderr(&state, |stderr| {
assert!(stderr.contains("too many operands"), "stderr = {stderr:?}")
});
}
#[test]
fn return_with_invalid_option() {
let system = Box::new(VirtualSystem::new());
let state = Rc::clone(&system.state);
let mut env = Env::with_system(system);
let mut env = env.push_frame(Frame::Builtin(Builtin {
name: Field::dummy("return"),
is_special: true,
}));
let args = Field::dummies(["--invalid-option"]);
let actual_result = main(&mut env, args).now_or_never().unwrap();
let expected_result =
Result::with_exit_status_and_divert(ExitStatus::ERROR, Break(Divert::Interrupt(None)));
assert_eq!(actual_result, expected_result);
assert_stderr(&state, |stderr| {
assert!(stderr.contains("unknown option"), "stderr = {stderr:?}");
assert!(stderr.contains("--invalid-option"), "stderr = {stderr:?}");
});
}
}