Skip to content
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
2 changes: 2 additions & 0 deletions src/uu/tr/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ tr-error-invalid-char-class = invalid character class { $class }
tr-error-missing-equivalence-class-char = missing equivalence class character '[==]'
tr-error-multiple-char-repeat-in-set2 = only one [c*] repeat construct may appear in string2
tr-error-char-repeat-in-set1 = the [c*] repeat construct may not appear in string1
tr-error-char-repeat-in-set2-when-not-translating = the [c*] construct may appear in string2 only when translating
tr-error-equivalence-class-in-set2-when-translating = [=c=] expressions may not appear in string2 when translating
tr-error-invalid-repeat-count = invalid repeat count { $count } in [c*n] construct
tr-error-empty-set2-when-not-truncating = when not truncating set1, string2 must be non-empty
tr-error-class-except-lower-upper-in-set2 = when translating, the only character classes that may appear in set2 are 'upper' and 'lower'
Expand Down
2 changes: 2 additions & 0 deletions src/uu/tr/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ tr-error-invalid-char-class = classe de caractères non valide { $class }
tr-error-missing-equivalence-class-char = caractère de classe d'équivalence manquant '[==]'
tr-error-multiple-char-repeat-in-set2 = seule une construction de répétition [c*] peut apparaître dans string2
tr-error-char-repeat-in-set1 = la construction de répétition [c*] ne peut pas apparaître dans string1
tr-error-char-repeat-in-set2-when-not-translating = la construction [c*] ne peut apparaître dans string2 que lors de la traduction
tr-error-equivalence-class-in-set2-when-translating = les expressions [=c=] ne peuvent pas apparaître dans string2 lors de la traduction
tr-error-invalid-repeat-count = nombre de répétitions invalide { $count } dans la construction [c*n]
tr-error-empty-set2-when-not-truncating = quand on ne tronque pas set1, string2 doit être non-vide
tr-error-class-except-lower-upper-in-set2 = lors de la traduction, les seules classes de caractères qui peuvent apparaître dans set2 sont 'upper' et 'lower'
Expand Down
2 changes: 2 additions & 0 deletions src/uu/tr/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ fn describe(error: &BadSequence) -> Option<(Option<String>, Option<&'static str>
Some("tr-diag-help-backwards-range"),
)),
BadSequence::MultipleCharRepeatInSet2
| BadSequence::CharRepeatInSet2WhenNotTranslating
| BadSequence::EquivalenceClassInSet2WhenTranslating
| BadSequence::ClassExceptLowerUpperInSet2
| BadSequence::ClassInSet2NotMatchedBySet1 => Some((None, None)),
BadSequence::Set1LongerSet2EndsInClass => Some((
Expand Down
39 changes: 36 additions & 3 deletions src/uu/tr/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum BadSequence {
MissingEquivalentClassChar,
MultipleCharRepeatInSet2,
CharRepeatInSet1,
CharRepeatInSet2WhenNotTranslating,
EquivalenceClassInSet2WhenTranslating,
InvalidRepeatCount(String),
EmptySet2WhenNotTruncatingSet1,
ClassExceptLowerUpperInSet2,
Expand Down Expand Up @@ -87,6 +89,20 @@ impl Display for BadSequence {
Self::CharRepeatInSet1 => {
write!(f, "{}", translate!("tr-error-char-repeat-in-set1"))
}
Self::CharRepeatInSet2WhenNotTranslating => {
write!(
f,
"{}",
translate!("tr-error-char-repeat-in-set2-when-not-translating")
)
}
Self::EquivalenceClassInSet2WhenTranslating => {
write!(
f,
"{}",
translate!("tr-error-equivalence-class-in-set2-when-translating")
)
}
Self::InvalidRepeatCount(count) => {
write!(
f,
Expand Down Expand Up @@ -202,6 +218,8 @@ pub enum Class {
#[derive(Debug, Clone, Copy)]
pub enum Sequence {
Char(u8),
/// A `[=c=]` equivalence class, which is only the character itself.
Equivalence(u8),
CharRange(u8, u8),
CharStar(u8),
CharRepeat(u8, usize),
Expand All @@ -211,7 +229,7 @@ pub enum Sequence {
impl Sequence {
pub fn flatten(&self) -> Box<dyn Iterator<Item = u8>> {
match self {
Self::Char(c) => Box::new(std::iter::once(*c)),
Self::Char(c) | Self::Equivalence(c) => Box::new(std::iter::once(*c)),
Self::CharRange(l, r) => Box::new(*l..=*r),
Self::CharStar(c) => Box::new(std::iter::repeat(*c)),
Self::CharRepeat(c, n) => Box::new(std::iter::repeat_n(*c, *n)),
Expand Down Expand Up @@ -255,7 +273,7 @@ impl Sequence {
/// the count of a `[c*n]` repeat can be far too large to materialize.
fn expanded_len(&self) -> usize {
match self {
Self::Char(_) => 1,
Self::Char(_) | Self::Equivalence(_) => 1,
Self::CharRange(l, r) => usize::from(*r) - usize::from(*l) + 1,
// A star is only sized once it has been turned into a repeat.
Self::CharStar(_) => 0,
Expand Down Expand Up @@ -349,6 +367,21 @@ impl Sequence {
));
}

// `[=c=]` in set2 is only for the set to squeeze with `-d -s`, and `[c*]`
// only fills set2 up to the length of set1 when translating.
if translating && set2.iter().any(|s| matches!(s, Self::Equivalence(_))) {
return Err(SequenceError::whole_set(
BadSequence::EquivalenceClassInSet2WhenTranslating,
2,
));
}
if !translating && set2.iter().any(|s| is_char_star(&s)) {
return Err(SequenceError::whole_set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could reuse is_char_star here, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sylvestre yes, done in 9a983b1

BadSequence::CharRepeatInSet2WhenNotTranslating,
2,
));
}

if translating
&& set2.iter().any(|&x| {
matches!(x, Self::Class(_))
Expand Down Expand Up @@ -759,7 +792,7 @@ impl Sequence {
l,
match (a, b) {
(Err(()), _) => Err(BadSequence::MissingEquivalentClassChar),
(Ok(c), Ok(())) => Ok(Self::Char(c)),
(Ok(c), Ok(())) => Ok(Self::Equivalence(c)),
(Ok(c), Err(v)) => Err(BadSequence::MultipleCharInEquivalence(format!(
"{}{}",
String::from_utf8_lossy(&[c]),
Expand Down
48 changes: 48 additions & 0 deletions tests/by-util/test_tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,54 @@ fn test_repeat_in_set1_padded_by_star_in_set2() {
.stdout_only("yyz");
}

// With `-d -s` set2 is only the set to squeeze, which a star cannot fill.
#[rstest]
#[case::star("[b*]")]
#[case::star_zero("[b*0]")]
#[case::star_in_middle("b[b*]c")]
#[case::star_after_equivalence("[=b=][b*]")]
fn test_star_in_set2_only_when_translating(#[case] set2: &str) {
new_ucmd!()
.args(&["-ds", "a", set2])
.fails_with_code(1)
.stderr_only("tr: the [c*] construct may appear in string2 only when translating\n");
}

#[rstest]
#[case::translate(&["a", "[=b=]"])]
#[case::translate_after_char(&["a", "b[=b=]"])]
#[case::translate_and_squeeze(&["-s", "a", "[=b=]"])]
#[case::truncate(&["-t", "a", "[=b=]"])]
#[case::complement(&["-c", "a", "[=b=]"])]
#[case::before_class(&["a", "[=b=][:digit:]"])]
fn test_equivalence_class_in_set2_only_with_delete_and_squeeze(#[case] args: &[&str]) {
new_ucmd!()
.args(args)
.fails_with_code(1)
.stderr_only("tr: [=c=] expressions may not appear in string2 when translating\n");
}

// A repeat with a count still fills set2 with `-d -s`.
#[rstest]
#[case::repeat("[b*3]")]
#[case::equivalence("[=b=]")]
fn test_set2_constructs_allowed_with_delete_and_squeeze(#[case] set2: &str) {
new_ucmd!()
.args(&["-ds", "a", set2])
.pipe_in("xaabb")
.succeeds()
.stdout_only("xb");
}

#[test]
fn test_equivalence_class_in_set1_when_translating() {
new_ucmd!()
.args(&["[=a=]", "b"])
.pipe_in("xaab")
.succeeds()
.stdout_only("xbbb");
}

#[test]
fn test_non_digit_repeat() {
new_ucmd!()
Expand Down
Loading