diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f743517..b4b6ae4 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.75.0" +channel = "1.76.0" components = ["clippy", "rustfmt"] diff --git a/src/parser.rs b/src/parser.rs index 2329955..348c894 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -708,10 +708,9 @@ fn parse_word_parts( preceded(ch('$'), |input| { if let Some(char) = input.chars().next() { // $$ - process id - // $? - last exit code // $# - number of arguments in $* // $* - list of arguments passed to the current process - if "$?#*".contains(char) { + if "$#*".contains(char) { return ParseError::fail( input, format!("${char} is currently not supported."), @@ -753,7 +752,10 @@ fn parse_word_parts( } let (input, parts) = many0(or7( - map(first_escaped_char(mode), PendingPart::Char), + or( + map(tag("$?"), |_| PendingPart::Variable("?")), + map(first_escaped_char(mode), PendingPart::Char), + ), map(parse_command_substitution, PendingPart::Command), map(preceded(ch('$'), parse_env_var_name), PendingPart::Variable), |input| { @@ -1419,11 +1421,6 @@ mod test { "$$", Err("$$ is currently not supported."), ); - run_test( - parse_unquoted_word, - "$?", - Err("$? is currently not supported."), - ); run_test( parse_unquoted_word, "$#", @@ -1458,6 +1455,7 @@ mod test { run_test(parse_u32, "4294967296", Err("backtrace")); } + #[track_caller] fn run_test<'a, T: PartialEq + std::fmt::Debug>( combinator: impl Fn(&'a str) -> ParseResult<'a, T>, input: &'a str, @@ -1466,6 +1464,7 @@ mod test { run_test_with_end(combinator, input, expected, ""); } + #[track_caller] fn run_test_with_end<'a, T: PartialEq + std::fmt::Debug>( combinator: impl Fn(&'a str) -> ParseResult<'a, T>, input: &'a str, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 2736777..659efda 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -172,6 +172,7 @@ fn execute_sequential_list( } ExecuteResult::Continue(exit_code, changes, handles) => { state.apply_changes(&changes); + state.apply_env_var("?", &exit_code.to_string()); final_changes.extend(changes); async_handles.extend(handles); // use the final sequential item's exit code @@ -258,6 +259,7 @@ fn execute_sequence( let (exit_code, mut async_handles) = match first_result { ExecuteResult::Exit(_, _) => return first_result, ExecuteResult::Continue(exit_code, sub_changes, async_handles) => { + state.apply_env_var("?", &exit_code.to_string()); state.apply_changes(&sub_changes); changes.extend(sub_changes); (exit_code, async_handles) diff --git a/src/shell/test.rs b/src/shell/test.rs index 8fa9bcd..bea34e2 100644 --- a/src/shell/test.rs +++ b/src/shell/test.rs @@ -331,6 +331,30 @@ async fn env_variables() { .await; } +#[tokio::test] +async fn exit_code_var() { + TestBuilder::new() + .command(r#"echo $? ; echo $? ; false ; echo $?"#) + .assert_stdout("\n0\n1\n") + .run() + .await; + TestBuilder::new() + .command(r#"(false || echo $?) && echo $?"#) + .assert_stdout("1\n0\n") + .run() + .await; + TestBuilder::new() + .command(r#"! false && echo $?"#) + .assert_stdout("0\n") + .run() + .await; + TestBuilder::new() + .command(r#"(deno eval 'Deno.exit(25)') || echo $?"#) + .assert_stdout("25\n") + .run() + .await; +} + #[tokio::test] async fn sequential_lists() { TestBuilder::new()