Skip to content

sed: treat [.c.] and [=c=] in a bracket expression as the character c - #626

Open
qodelicious wants to merge 2 commits into
uutils:mainfrom
qodelicious:collating-symbol-single-char
Open

qodelicious wants to merge 2 commits into
uutils:mainfrom
qodelicious:collating-symbol-single-char

Conversation

@qodelicious

Copy link
Copy Markdown
Contributor

[.c.] and [=c=] inside a bracket expression were handed to the regex engine as they were, and it reads them as a nested set. So [[.a.]] also matched ., [[.-.]] didn't match -, [[.].]] didn't match ], [b-[.d.]] was a regex error, and names like [[.dzs.]] or [[.space.]] were accepted and matched their letters.

Now, like GNU sed in the C locale, a collating symbol or an equivalence class has to name exactly one character, and that character is added to the set as a literal (punctuation gets escaped so it can't turn into class syntax). So ranges like [[.a.]-[.c.]] work too. An empty or multi-character name gives "Invalid collation character" like GNU. Character escapes inside the name are decoded like in the rest of the bracket, so [[.\n.]] is a newline and [[.\].]] is still an error, same as GNU. In the byte (C locale) mode a multibyte character counts as more than one, which is what GNU does there too.

This covers the C locale part of #322. Real multi-character collating elements from locales like hu_HU, and the locale equivalence classes from #321, still aren't supported since there's no locale collation data.

Added test_bracket_collating_symbol_and_equivalence_class, test_bracket_collating_symbol_with_character_escape and test_bracket_invalid_collation_character, plus unit tests in delimited_parser.rs. The outputs were checked against GNU sed 4.9. The first and third fail on main. cargo test, cargo fmt --check and cargo clippy --all-targets --all-features -- -D warnings pass.

Related to #322

@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.41270% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 95.41%. Comparing base (cab1c6e) to head (2ab9404).

Files with missing lines Patch % Lines
src/sed/delimited_parser.rs 98.41% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #626      +/-   ##
==========================================
+ Coverage   95.39%   95.41%   +0.01%     
==========================================
  Files          14       14              
  Lines        7665     7720      +55     
  Branches      457      463       +6     
==========================================
+ Hits         7312     7366      +54     
- Misses        352      353       +1     
  Partials        1        1              
Flag Coverage Δ
macos_latest 96.87% <98.41%> (+<0.01%) ⬆️
ubuntu_latest 96.80% <98.41%> (+0.01%) ⬆️
windows_latest 0.00% <0.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/sed/delimited_parser.rs Outdated
/// escaping ASCII punctuation so that it can't be read as class syntax.
fn push_class_member(result: &mut Vec<u8>, member: &[u8]) {
if let [b] = member
&& b.is_ascii_punctuation()

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.

i think [[.<.]] and [[=>=]] break now: \< / \> are word boundaries for the regex crate, rejected inside a class
maybe only escape what matters in a class (\ [ ] ^ - & ~ |)?

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.

Yeah \< and \> were the problem. Now only \ [ ] ^ - & ~ | get escaped inside a class, so [[.<.]] and [[=>=]] work again. Fixed in 834bf51.

Comment thread tests/by-util/test_sed.rs Outdated
(r"s/[[.-.]]/X/g", r".=a]\^Xc[&~|9z"),
(r"s/[[.\.]]/X/g", r".=a]X^-c[&~|9z"),
(r"s/[[.^.][.[.]]/X/g", r".=a]\X-cX&~|9z"),
(r"s/[[.&.][.~.][.|.]]/X/g", r".=a]\^-c[XXX9z"),

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.

please add < and > here, so we don't regress

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.

Added <> to the input plus cases for [[.<.]], [[=>=]] and both together. All 15 cases give the same output as GNU sed 4.9.

Comment thread src/sed/delimited_parser.rs Outdated
CharacterMode::Byte => name.len() == 1,
CharacterMode::Utf8 => str::from_utf8(name).is_ok_and(|s| s.chars().count() == 1),
};
is_single.then_some(name)

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.

it only ever returns name, could just return a bool, 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.

It returns a bool now.

@sylvestre

Copy link
Copy Markdown
Contributor

thanks for looking into this!
I think [[.<.]] and [[=>=]] now give a regex error because of the punctuation escaping, see inline.
Could you please also run the GNU test suite (git clone https://github.com/mirror/sed.git ../gnu.sed once, then ./util/run-gnu-testsuite.sh) and say whether anything changes?

@qodelicious

Copy link
Copy Markdown
Contributor Author

Ran the GNU suite on the base (5302985) and on this branch. Both give 19 pass, 30 fail, 16 skip out of 65, and no test changed status.

They were passed to the regex engine as they were, which read them as a nested set. So [[.a.]] also matched a dot, [[.-.]] did not match a dash, and names like [[.dzs.]] were accepted.

Like GNU sed in the C locale, a collating symbol or equivalence class now has to name a single character, which becomes a literal member of the set. Anything else is "Invalid collation character".
Keep < and > as they are in [.c.] and [=c=], since \< and \> are
word boundaries for the regex crate. Return a bool from the single
character check.
@qodelicious
qodelicious force-pushed the collating-symbol-single-char branch from 834bf51 to 2ab9404 Compare November 11, 2026 01:57
@codspeed

codspeed Bot commented Nov 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 4.5%

1 warning

⚠️ 7 benchmarks with a significant change ran on different hardware

Their base and head results were measured on machines with a different CPU, core count or memory, so the changes might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

⚡ 7 improved benchmarks
✅ 4 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ no_op_short 2.2 s 2 s +7.87%
⚡ access_log_append 1,054.7 ms 995.8 ms +5.92%
⚡ access_log_no_op 1.6 s 1.5 s +5.4%
⚡ access_log_translit 991.9 ms 949.6 ms +4.46%
⚡ remove_cr 374.1 ms 363.3 ms +2.99%
⚡ access_log_no_subst 863.2 ms 838.7 ms +2.92%
⚡ number_fix 583.7 ms 572.1 ms +2.03%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing qodelicious:collating-symbol-single-char (2ab9404) with main (cab1c6e)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants