Skip to content

fix: #2255 read \1 to \9 in a sed replacement as the groups they name - #2293

Open
sherenkov wants to merge 1 commit into
objectionary:masterfrom
sherenkov:bug/#2255/sed-back-reference
Open

sherenkov wants to merge 1 commit into
objectionary:masterfrom
sherenkov:bug/#2255/sed-back-reference

Conversation

@sherenkov

Copy link
Copy Markdown
Contributor

A sed replacement written the unix way, s/([0-9]+)/\\1!/g, put the digit 1 in place of the group, so the rule of #2255 turned "Hello, 2025" into "Hello, 1!", with no error. nextUntilSlash reads the replacement with escape on and dropped the backslash before any character (src/Functions.hs:171), so \1 reached substituteGroups of src/Regexp.hs as a plain 1, and there only $ followed by digits named a group.

Now nextUntilSlash keeps the backslash in the replacement when a digit or another backslash follows it, and substituteGroups reads \1 to \9 as the group of that one digit, as sed does, so \10 is group one followed by 0, and reads \\ as one backslash, so \\1 stays a literal \1. An escaped slash or brace is read as before, and so is $1. A back reference to a group the pattern does not have is kept as it is, the way $9 already is. The README now says that \1 to \9 and $1 name the groups.

FunctionsSpec runs the rule of the issue through sed and expects "Hello, 2025!", and checks that \\1 stays a backslash and a 1; RegexpSpec checks the back reference, the single digit it takes, one out of bounds and the escaped backslash in substituteGroups. The first of them gives "Hello, 1!" without the change.

Fixes #2255

@sherenkov

Copy link
Copy Markdown
Contributor Author

@yegor256 CI is green and the branch is up to date with master, could you take a look when you have a moment? It makes sed read \1 to \9 in a replacement as the groups they name and \\ as one backslash, instead of dropping every backslash.

@sherenkov
sherenkov force-pushed the bug/#2255/sed-back-reference branch from 23eb238 to 21019a6 Compare November 7, 2026 09:01
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Benchmark comparison

Median of 5 round(s) per side, interleaved on the same runner against the PR merge-base, with the bench fixture frozen across both builds. Noise is the spread between the fastest and the slowest round, taken from the noisier side.

Bench base (μs) head (μs) delta noise
morph/symbolic/accum/0 113.89 109.11 -4.2% ±1.9%
morph/symbolic/accum/400 134.06 128.16 -4.4% ±24.0%
morph/symbolic/demo/e1 132.89 131.18 -1.3% ±3.1%
morph/symbolic/demo/e2 132.73 128.88 -2.9% ±2.4%
morph/symbolic/demo/e3 133.91 129.76 -3.1% ±1.8%
morph/symbolic/demo/e4 134.19 129.69 -3.4% ±2.0%
morph/symbolic/demo/e5 135.71 131.71 -3.0% ±1.6%
morph/symbolic/native/e5 1158.04 1131.37 -2.3% ±26.3%
parse/phi 234777.47 235534.27 +0.3% ±2.1%
parse/xmir 947819.74 931013.63 -1.8% ±1.3%
print/salty/multiline 1750699.97 1807544.54 +3.2% ±2.9%
print/sweet/flat 66647.89 66878.76 +0.3% ±5.7%
print/sweet/multiline 538542.21 560425.49 +4.1% ±3.3%
rewrite/normalize 2.23 2.17 -2.7% ±5.6%

Scaling

Each sized case against its /0 sibling, the same work over nothing extra: a ratio near 1 means the extra size is free.

Bench against base head
morph/symbolic/accum/400 morph/symbolic/accum/0 1.18× 1.17×

No case moved by more than 20% beyond its own noise.
This report is informational, a delta never fails the build.

This branch has not been deployed

No deployments
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.

sed turns the back-reference \1 into a literal 1, since the replacement drops every backslash and only $1 names a group (src/Functions.hs:171)

1 participant