Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow esac as the first pattern of a case branch #426

Merged
merged 1 commit into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions yash-cli/CHANGELOG-bin.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- The shell's syntax now allows `esac` as the first pattern of a case branch
as in `case esac in (esac|case) echo ok; esac`. Previously, it was a syntax
error, but POSIX.1-2024 allows it.
- The `bg` built-in now updates the `!` special parameter to the process ID of
the background job, as required by POSIX.1-2024.
- The `exec` built-in no longer exits the shell when the specified command is
Expand Down
8 changes: 7 additions & 1 deletion yash-cli/tests/scripted_test/case-p.sh
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,13 @@ test_reserved_word_as_pattern "$LINENO" then
test_reserved_word_as_pattern "$LINENO" until
test_reserved_word_as_pattern "$LINENO" while

test_oE 'reserved word esac as (non-first) pattern'
test_oE 'esac as first pattern'
case esac in (esac) echo matched;; esac
__IN__
matched
__OUT__

test_oE 'esac as non-first pattern'
case esac in -|esac) echo matched;; esac
__IN__
matched
Expand Down
10 changes: 10 additions & 0 deletions yash-syntax/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- In the `parser::Parser::case_item` method, the parser now accepts an `esac`
token as a pattern after an opening parenthesis, as required by POSIX.1-2024.
The previous version of POSIX did not allow `esac` as the first pattern, so
the method was returning `SyntaxError::EsacAsPattern` in that case.
- Internal dependency versions:
- futures-util 0.3.28 → 0.3.31

### Deprecated

- `parser::SyntaxError::EsacAsPattern`
- This variant is deprecated because this error condition is no longer
possible as described above.

## [0.12.0] - 2024-09-29

### Added
Expand Down
14 changes: 5 additions & 9 deletions yash-syntax/src/parser/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ impl Parser<'_, '_> {
pub async fn case_item(&mut self) -> Result<Option<CaseItem>> {
fn pattern_error_cause(token_id: TokenId) -> SyntaxError {
match token_id {
Token(Some(Esac)) => SyntaxError::EsacAsPattern,
Token(_) => unreachable!(),
Operator(CloseParen) | Operator(Bar) | Operator(Newline) | EndOfInput => {
SyntaxError::MissingPattern
Expand All @@ -63,8 +62,7 @@ impl Parser<'_, '_> {
Operator(OpenParen) => {
let next_token = self.take_token_auto(&[Esac]).await?;
match next_token.id {
// TODO Allow `esac` if not in POSIXly-correct mode
Token(keyword) if keyword != Some(Esac) => next_token.word,
Token(_) => next_token.word,
_ => {
let cause = pattern_error_cause(next_token.id).into();
let location = next_token.word.location;
Expand Down Expand Up @@ -318,12 +316,10 @@ mod tests {
let mut lexer = Lexer::from_memory("(esac)", Source::Unknown);
let mut parser = Parser::new(&mut lexer, &EmptyGlossary);

let e = parser.case_item().now_or_never().unwrap().unwrap_err();
assert_eq!(e.cause, ErrorCause::Syntax(SyntaxError::EsacAsPattern));
assert_eq!(*e.location.code.value.borrow(), "(esac)");
assert_eq!(e.location.code.start_line_number.get(), 1);
assert_eq!(*e.location.code.source, Source::Unknown);
assert_eq!(e.location.range, 1..5);
let item = parser.case_item().now_or_never().unwrap().unwrap().unwrap();
assert_eq!(item.patterns.len(), 1);
assert_eq!(item.patterns[0].to_string(), "esac");
assert_eq!(item.body.0, []);
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions yash-syntax/src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub enum SyntaxError {
/// The pattern is not a valid word token.
InvalidPattern,
/// The first pattern of a case item is `esac`.
#[deprecated = "this error no longer occurs"]
EsacAsPattern,
/// An `esac` or `;;` appears outside a case command.
UnopenedCase,
Expand Down Expand Up @@ -219,6 +220,7 @@ impl SyntaxError {
UnclosedPatternList => "The pattern list is not properly closed by a `)`",
MissingPattern => "A pattern is missing in the `case` command",
InvalidPattern => "The pattern is not a valid word token",
#[allow(deprecated)]
EsacAsPattern => "`esac` cannot be the first of a pattern list",
UnclosedCase { .. } => "The `case` command is missing its closing `esac`",
UnmatchedParenthesis => "`)` is missing after `(`",
Expand Down Expand Up @@ -290,6 +292,7 @@ impl SyntaxError {
UnopenedIf => "not in an `if` command",
UnclosedIf { .. } => "expected `fi`",
MissingIn { .. } => "expected `in`",
#[allow(deprecated)]
EsacAsPattern => "needs quoting",
UnopenedCase => "not in a `case` command",
UnclosedCase { .. } => "expected `esac`",
Expand Down
Loading