Skip to content

Commit

Permalink
Define CaseContinuation and conversions to/from Operator
Browse files Browse the repository at this point in the history
  • Loading branch information
magicant committed Nov 10, 2024
1 parent c732202 commit 7f2095c
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 16 deletions.
9 changes: 9 additions & 0 deletions yash-syntax/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to `yash-syntax` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.13.0] - Unreleased

### Added

- Extended the case item syntax to allow `;&`, `;|`, and `;;&` as terminators.
- The `SemicolonAnd`, `SemicolonSemicolonAnd`, and `SemicolonBar` variants
are added to the `parser::lex::Operator` enum.

## [0.12.1] - 2024-11-10

### Changed
Expand Down Expand Up @@ -353,6 +361,7 @@ command.
- Functionalities to parse POSIX shell scripts
- Alias substitution support

[0.13.0]: https://github.com/magicant/yash-rs/releases/tag/yash-syntax-0.13.0
[0.12.1]: https://github.com/magicant/yash-rs/releases/tag/yash-syntax-0.12.1
[0.12.0]: https://github.com/magicant/yash-rs/releases/tag/yash-syntax-0.12.0
[0.11.0]: https://github.com/magicant/yash-rs/releases/tag/yash-syntax-0.11.0
Expand Down
8 changes: 7 additions & 1 deletion yash-syntax/src/parser/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use super::error::SyntaxError;
use super::lex::Keyword::{Case, Esac, In};
use super::lex::Operator::{Bar, CloseParen, Newline, OpenParen, SemicolonSemicolon};
use super::lex::TokenId::{self, EndOfInput, Operator, Token};
use crate::syntax::CaseContinuation;
use crate::syntax::CaseItem;
use crate::syntax::CompoundCommand;

Expand Down Expand Up @@ -102,8 +103,13 @@ impl Parser<'_, '_> {
}

let body = self.maybe_compound_list_boxed().await?;
let continuation = CaseContinuation::default();

Ok(Some(CaseItem { patterns, body }))
Ok(Some(CaseItem {
patterns,
body,
continuation,
}))
}

/// Parses a case conditional construct.
Expand Down
25 changes: 22 additions & 3 deletions yash-syntax/src/parser/lex/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ pub enum Operator {
CloseParen,
/// `;`
Semicolon,
/// `;&`
SemicolonAnd,
/// `;;`
SemicolonSemicolon,
/// `;;&`
SemicolonSemicolonAnd,
/// `;|`
SemicolonBar,
/// `<`
Less,
/// `<&`
Expand Down Expand Up @@ -89,7 +95,10 @@ impl Operator {
OpenParen => "(",
CloseParen => ")",
Semicolon => ";",
SemicolonAnd => ";&",
SemicolonSemicolon => ";;",
SemicolonSemicolonAnd => ";;&",
SemicolonBar => ";|",
Less => "<",
LessAnd => "<&",
LessOpenParen => "<(",
Expand All @@ -110,13 +119,23 @@ impl Operator {

/// Determines if this token can be a delimiter of a clause.
///
/// This function returns `true` for `CloseParen` and `SemicolonSemicolon`,
/// and `false` for others.
/// This function returns `true` for the following operators:
///
/// - `CloseParen` (`)`)
/// - `SemicolonAnd` (`;&`)
/// - `SemicolonSemicolon` (`;;`)
/// - `SemicolonSemicolonAnd` (`;;&`)
/// - `SemicolonBar` (`;|`)
#[must_use]
pub const fn is_clause_delimiter(self) -> bool {
use Operator::*;
match self {
CloseParen | SemicolonSemicolon => true,
CloseParen
| SemicolonAnd
| SemicolonSemicolon
| SemicolonSemicolonAnd
| SemicolonBar => true,

Newline | And | AndAnd | OpenParen | Semicolon | Less | LessAnd | LessOpenParen
| LessLess | LessLessDash | LessLessLess | LessGreater | Greater | GreaterAnd
| GreaterOpenParen | GreaterGreater | GreaterGreaterBar | GreaterBar | Bar | BarBar => {
Expand Down
4 changes: 3 additions & 1 deletion yash-syntax/src/parser/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ fn error_type_for_trailing_token_in_command_line(token_id: TokenId) -> Option<Sy
And | AndAnd | Semicolon | Bar | BarBar => Some(InvalidCommandToken),
OpenParen => Some(MissingSeparator),
CloseParen => Some(UnopenedSubshell),
SemicolonSemicolon => Some(UnopenedCase),
SemicolonAnd | SemicolonSemicolon | SemicolonSemicolonAnd | SemicolonBar => {
Some(UnopenedCase)
}
Newline | Less | LessAnd | LessOpenParen | LessLess | LessLessDash | LessLessLess
| LessGreater | Greater | GreaterAnd | GreaterOpenParen | GreaterGreater
| GreaterGreaterBar | GreaterBar => unreachable!(),
Expand Down
105 changes: 94 additions & 11 deletions yash-syntax/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,60 @@ impl fmt::Display for ElifThen {
}
}

/// Symbol that terminates the body of a case branch and determines what to do
/// after executing it
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CaseContinuation {
/// `;;` (terminate the case construct)
#[default]
Break,
/// `;&` (unconditionally execute the body of the next case branch)
FallThrough,
/// `;|` or `;;&` (resume with the next case branch, performing pattern matching again)
Continue,
}

impl TryFrom<Operator> for CaseContinuation {
type Error = TryFromOperatorError;

/// Converts an operator into a case continuation.
///
/// The `SemicolonBar` and `SemicolonSemicolonAnd` operators are converted
/// into `Continue`; you cannot distinguish between the two from the return
/// value.
fn try_from(op: Operator) -> Result<CaseContinuation, TryFromOperatorError> {
use CaseContinuation::*;
use Operator::*;
match op {
SemicolonSemicolon => Ok(Break),
SemicolonAnd => Ok(FallThrough),
SemicolonBar | SemicolonSemicolonAnd => Ok(Continue),
_ => Err(TryFromOperatorError {}),
}
}
}

impl From<CaseContinuation> for Operator {
/// Converts a case continuation into an operator.
///
/// The `Continue` variant is converted into `SemicolonBar`.
fn from(cc: CaseContinuation) -> Operator {
use CaseContinuation::*;
use Operator::*;
match cc {
Break => SemicolonSemicolon,
FallThrough => SemicolonAnd,
Continue => SemicolonBar,
}
}
}

impl fmt::Display for CaseContinuation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Operator::from(*self).fmt(f)
}
}

/// Branch item of a `case` compound command
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CaseItem {
Expand All @@ -1230,15 +1284,18 @@ pub struct CaseItem {
pub patterns: Vec<Word>,
/// Commands that are executed if any of the patterns matched
pub body: List,
/// What to do after executing the body of this item
pub continuation: CaseContinuation,
}

impl fmt::Display for CaseItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({}) {};;",
"({}) {}{}",
self.patterns.iter().format(" | "),
self.body
self.body,
self.continuation,
)
}
}
Expand Down Expand Up @@ -2192,22 +2249,48 @@ mod tests {
assert_eq!(format!("{elif:#}"), "elif c& then b&");
}

#[test]
fn case_continuation_conversions() {
use CaseContinuation::*;
for cc in &[Break, FallThrough, Continue] {
let cc2 = CaseContinuation::try_from(Operator::from(*cc));
assert_eq!(cc2, Ok(*cc));
}
assert_eq!(
CaseContinuation::try_from(Operator::SemicolonSemicolonAnd),
Ok(Continue)
);
}

#[test]
fn case_item_display() {
let patterns = vec!["foo".parse().unwrap()];
let body = "".parse::<List>().unwrap();
let item = CaseItem { patterns, body };
let item = CaseItem {
patterns: vec!["foo".parse().unwrap()],
body: "".parse::<List>().unwrap(),
continuation: CaseContinuation::Break,
};
assert_eq!(item.to_string(), "(foo) ;;");

let patterns = vec!["bar".parse().unwrap()];
let body = "echo ok".parse::<List>().unwrap();
let item = CaseItem { patterns, body };
let item = CaseItem {
patterns: vec!["bar".parse().unwrap()],
body: "echo ok".parse::<List>().unwrap(),
continuation: CaseContinuation::Break,
};
assert_eq!(item.to_string(), "(bar) echo ok;;");

let patterns = ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect();
let body = "foo; bar&".parse::<List>().unwrap();
let item = CaseItem { patterns, body };
let item = CaseItem {
patterns: ["a", "b", "c"].iter().map(|s| s.parse().unwrap()).collect(),
body: "foo; bar&".parse::<List>().unwrap(),
continuation: CaseContinuation::Break,
};
assert_eq!(item.to_string(), "(a | b | c) foo; bar&;;");

let item = CaseItem {
patterns: vec!["foo".parse().unwrap()],
body: "bar".parse::<List>().unwrap(),
continuation: CaseContinuation::FallThrough,
};
assert_eq!(item.to_string(), "(foo) bar;&");
}

#[test]
Expand Down

0 comments on commit 7f2095c

Please sign in to comment.