Skip to content

Commit 1ce3e53

Browse files
authored
Use an iterator instead of collecting to vec for string escapes (#819)
1 parent 8f7a0ac commit 1ce3e53

File tree

1 file changed

+38
-50
lines changed

1 file changed

+38
-50
lines changed

librubyfmt/src/string_escape.rs

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -60,45 +60,38 @@ pub fn escape_word_array_content(
6060
const TARGET_OPEN: char = '[';
6161
const TARGET_CLOSE: char = ']';
6262

63-
let chars: Vec<char> = content.chars().collect();
63+
let mut chars = content.chars().peekable();
6464
let mut output = String::new();
65-
let mut i = 0;
6665

67-
while i < chars.len() {
68-
let c = chars[i];
69-
70-
if c == '\\' && i + 1 < chars.len() {
71-
let next = chars[i + 1];
72-
if next == '\\' {
73-
// Escaped backslash, keep both
74-
output.push('\\');
75-
output.push('\\');
76-
i += 2;
77-
} else if next == orig_open_delim || next == orig_close_delim {
78-
// Original delimiter was escaped - unescape unless it's also a target delimiter
79-
if next == TARGET_OPEN || next == TARGET_CLOSE {
66+
while let Some(c) = chars.next() {
67+
if c == '\\' {
68+
if let Some(&next) = chars.peek() {
69+
if next == '\\' {
70+
// Escaped backslash, keep both
71+
output.push('\\');
72+
output.push('\\');
73+
} else if next == orig_open_delim || next == orig_close_delim {
74+
// Original delimiter was escaped - unescape unless it's also a target delimiter
75+
if next == TARGET_OPEN || next == TARGET_CLOSE {
76+
output.push('\\');
77+
}
78+
output.push(next);
79+
} else if next == TARGET_OPEN || next == TARGET_CLOSE {
80+
// Already escaped target delimiter, keep it
8081
output.push('\\');
82+
output.push(next);
83+
} else {
84+
output.push('\\');
85+
output.push(next);
8186
}
82-
output.push(next);
83-
i += 2;
84-
} else if next == TARGET_OPEN || next == TARGET_CLOSE {
85-
// Already escaped target delimiter, keep it
86-
output.push('\\');
87-
output.push(next);
88-
i += 2;
89-
} else {
90-
output.push('\\');
91-
output.push(next);
92-
i += 2;
87+
chars.next();
9388
}
9489
} else if c == TARGET_OPEN || c == TARGET_CLOSE {
9590
// Unescaped target delimiter needs escaping
9691
output.push('\\');
9792
output.push(c);
98-
i += 1;
9993
} else {
10094
output.push(c);
101-
i += 1;
10295
}
10396
}
10497

@@ -110,75 +103,72 @@ fn escape_string(content: &str, opening_delim: char, closing_delim: char) -> Str
110103
return content.to_string();
111104
}
112105

113-
let chars = content.chars().collect::<Vec<char>>();
114-
let mut i = 0;
115-
106+
let mut chars = content.chars().peekable();
116107
let mut output = String::new();
117108

118-
while let Some(i_char) = chars.get(i) {
119-
match i_char {
109+
while let Some(c) = chars.next() {
110+
match c {
120111
'"' => {
121112
output.push('\\');
122113
}
123114
'\\' => {
124-
if let Some(next_char) = chars.get(i + 1) {
115+
if let Some(&next_char) = chars.peek() {
125116
match next_char {
126117
'\'' => {
127118
// String#inspect strips the leading backslash from \', despite it being a valid
128119
// escape character in double-quoted strings as well. Leaving the behavior the same
129120
// for consistency with the previous behavior.
130121
output.push('\'');
131-
i += 2;
122+
chars.next();
132123
continue;
133124
}
134125
// '\\' is considered an escape sequence in both single and double quoted strings
135126
// and thus we don't need to "double-escape" it to "\\\\"
136127
'\\' => {
137128
output.push_str("\\\\");
138-
i += 2;
129+
chars.next();
139130
continue;
140131
}
141132
// '\"' is a slash char and a double-quote char, not an escaped double-quote,
142133
// so here we print an escaped slash character and then an escaped quote character: `"\\\""`
143134
'"' => {
144135
output.push_str("\\\\\\\"");
145-
i += 2;
136+
chars.next();
146137
continue;
147138
}
148139
delim_char
149-
if (*delim_char == opening_delim
150-
|| *delim_char == closing_delim)
140+
if (delim_char == opening_delim || delim_char == closing_delim)
151141
// We only care about the "non-standard" delimiters like %() etc.
152142
// For single-quoted strings, these characters should *not* be treated as escape sequences.
153143
&& opening_delim != '\'' =>
154144
{
155145
// In percent-strings, the opening and closing delimiters can be escaped to prevent terminating the string.
156-
output.push(*delim_char);
157-
i += 2;
146+
output.push(delim_char);
147+
chars.next();
158148
continue;
159149
}
160150
// For everything else, this is not an escape sequnce, so we need to
161151
// escape the slash and then print the next character.
162152
_ => {
163153
output.push('\\');
164154
output.push('\\');
165-
output.push(*next_char);
166-
i += 2;
155+
output.push(next_char);
156+
chars.next();
167157
continue;
168158
}
169159
}
170160
}
171161
}
172162
'#' => {
173-
if let Some(next_char) = chars.get(i + 1) {
163+
if let Some(&next_char) = chars.peek() {
174164
match next_char {
175165
// These are shorthands for embedded variables when double-quoted,
176166
// e.g. "#@foo", "#$GLOBAL", and "#{1}", so they must be escaped
177167
'$' | '@' | '{' => {
178168
output.push('\\');
179-
output.push(*i_char);
180-
output.push(*next_char);
181-
i += 2;
169+
output.push(c);
170+
output.push(next_char);
171+
chars.next();
182172
continue;
183173
}
184174
_ => {}
@@ -187,9 +177,7 @@ fn escape_string(content: &str, opening_delim: char, closing_delim: char) -> Str
187177
}
188178
_ => {}
189179
};
190-
191-
output.push(*i_char);
192-
i += 1;
180+
output.push(c);
193181
}
194182

195183
output

0 commit comments

Comments
 (0)