Skip to content

Move to using % as an escape in format! #9161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
70 changes: 36 additions & 34 deletions src/libstd/fmt/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,10 @@ impl<'self> Iterator<Piece<'self>> for Parser<'self> {
Some((_, '{')) => {
self.cur.next();
let ret = Some(Argument(self.argument()));
if !self.consume('}') {
self.err(~"unterminated format string");
}
self.must_consume('}');
ret
}
Some((pos, '\\')) => {
Some((pos, '%')) => {
self.cur.next();
self.escape(); // ensure it's a valid escape sequence
Some(String(self.string(pos + 1))) // skip the '\' character
Expand Down Expand Up @@ -211,11 +209,29 @@ impl<'self> Parser<'self> {
}
}

/// Forcibly consume the specified character, or else emit an error
fn must_consume(&mut self, expected: char) {
match self.cur.clone().next() {
Some((_, maybe)) if expected == maybe => { self.cur.next(); }
Some((_, found)) => {
self.err(fmt!("expected '%c' but found '%c'", expected, found));
}
None => {
self.err(fmt!("expected '%c' but string was terminated", expected))
}
}
}

/// Attempts to consume any amount of whitespace followed by a character
fn wsconsume(&mut self, c: char) -> bool {
self.ws(); self.consume(c)
}

/// Similar to `wsconsume`, but invokes must_consume instead of consume
fn ws_must_consume(&mut self, c: char) {
self.ws(); self.must_consume(c)
}

/// Consumes all whitespace characters until the first non-whitespace
/// character
fn ws(&mut self) {
Expand All @@ -232,7 +248,7 @@ impl<'self> Parser<'self> {
fn escape(&mut self) -> char {
match self.cur.next() {
Some((_, c @ '#')) | Some((_, c @ '{')) |
Some((_, c @ '\\')) | Some((_, c @ '}')) => { c }
Some((_, c @ '%')) | Some((_, c @ '}')) => { c }
Some((_, c)) => {
self.err(fmt!("invalid escape character `%c`", c));
c
Expand All @@ -251,7 +267,7 @@ impl<'self> Parser<'self> {
loop {
// we may not consume the character, so clone the iterator
match self.cur.clone().next() {
Some((pos, '\\')) | Some((pos, '#')) |
Some((pos, '%')) | Some((pos, '#')) |
Some((pos, '}')) | Some((pos, '{')) => {
return self.input.slice(start, pos);
}
Expand Down Expand Up @@ -362,15 +378,11 @@ impl<'self> Parser<'self> {
self.ws();
match self.word() {
"select" => {
if !self.wsconsume(',') {
self.err(~"`select` must be followed by `,`");
}
self.ws_must_consume(',');
Some(self.select())
}
"plural" => {
if !self.wsconsume(',') {
self.err(~"`plural` must be followed by `,`");
}
self.ws_must_consume(',');
Some(self.plural())
}
"" => {
Expand All @@ -396,15 +408,11 @@ impl<'self> Parser<'self> {
self.err(~"cannot have an empty selector");
break
}
if !self.wsconsume('{') {
self.err(~"selector must be followed by `{`");
}
self.ws_must_consume('{');
self.depth += 1;
let pieces = self.collect();
self.depth -= 1;
if !self.wsconsume('}') {
self.err(~"selector case must be terminated by `}`");
}
self.ws_must_consume('}');
if selector == "other" {
if !other.is_none() {
self.err(~"multiple `other` statements in `select");
Expand Down Expand Up @@ -451,9 +459,7 @@ impl<'self> Parser<'self> {
self.err(fmt!("expected `offset`, found `%s`",
word));
} else {
if !self.consume(':') {
self.err(~"`offset` must be followed by `:`");
}
self.must_consume(':');
match self.integer() {
Some(i) => { offset = Some(i); }
None => {
Expand Down Expand Up @@ -499,15 +505,11 @@ impl<'self> Parser<'self> {
}
}
};
if !self.wsconsume('{') {
self.err(~"selector must be followed by `{`");
}
self.ws_must_consume('{');
self.depth += 1;
let pieces = self.collect();
self.depth -= 1;
if !self.wsconsume('}') {
self.err(~"selector case must be terminated by `}`");
}
self.ws_must_consume('}');
if isother {
if !other.is_none() {
self.err(~"multiple `other` statements in `select");
Expand Down Expand Up @@ -629,16 +631,16 @@ mod tests {
#[test]
fn simple() {
same("asdf", ~[String("asdf")]);
same("a\\{b", ~[String("a"), String("{b")]);
same("a\\#b", ~[String("a"), String("#b")]);
same("a\\}b", ~[String("a"), String("}b")]);
same("a\\}", ~[String("a"), String("}")]);
same("\\}", ~[String("}")]);
same("a%{b", ~[String("a"), String("{b")]);
same("a%#b", ~[String("a"), String("#b")]);
same("a%}b", ~[String("a"), String("}b")]);
same("a%}", ~[String("a"), String("}")]);
same("%}", ~[String("}")]);
}

#[test] #[should_fail] fn invalid01() { musterr("{") }
#[test] #[should_fail] fn invalid02() { musterr("\\") }
#[test] #[should_fail] fn invalid03() { musterr("\\a") }
#[test] #[should_fail] fn invalid02() { musterr("%") }
#[test] #[should_fail] fn invalid03() { musterr("%a") }
#[test] #[should_fail] fn invalid04() { musterr("{3a}") }
#[test] #[should_fail] fn invalid05() { musterr("{:|}") }
#[test] #[should_fail] fn invalid06() { musterr("{:>>>}") }
Expand Down
14 changes: 7 additions & 7 deletions src/test/compile-fail/ifmt-bad-arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ fn main() {

// bad syntax of the format string

format!("{"); //~ ERROR: unterminated format string
format!("\\ "); //~ ERROR: invalid escape
format!("\\"); //~ ERROR: expected an escape
format!("{"); //~ ERROR: expected '}'
format!("% "); //~ ERROR: invalid escape
format!("%"); //~ ERROR: expected an escape

format!("{0, }", 1); //~ ERROR: expected method
format!("{0, foo}", 1); //~ ERROR: unknown method
format!("{0, select}", "a"); //~ ERROR: must be followed by
format!("{0, plural}", 1); //~ ERROR: must be followed by
format!("{0, select}", "a"); //~ ERROR: expected ',' but found '}'
format!("{0, plural}", 1); //~ ERROR: expected ',' but found '}'

format!("{0, select, a{{}", 1); //~ ERROR: must be terminated
format!("{0, select, a{{}", 1); //~ ERROR: expected '}' but
format!("{0, select, {} other{}}", "a"); //~ ERROR: empty selector
format!("{0, select, other{} other{}}", "a"); //~ ERROR: multiple `other`
format!("{0, plural, offset: other{}}", "a"); //~ ERROR: must be an integer
format!("{0, plural, offset 1 other{}}", "a"); //~ ERROR: be followed by `:`
format!("{0, plural, offset 1 other{}}", "a"); //~ ERROR: expected ':'
format!("{0, plural, =a{} other{}}", "a"); //~ ERROR: followed by an integer
format!("{0, plural, a{} other{}}", "a"); //~ ERROR: unexpected plural
format!("{0, select, a{}}", "a"); //~ ERROR: must provide an `other`
Expand Down
10 changes: 5 additions & 5 deletions src/test/run-pass/ifmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn main() {
// Various edge cases without formats
t!(format!(""), "");
t!(format!("hello"), "hello");
t!(format!("hello \\{"), "hello {");
t!(format!("hello %{"), "hello {");

// default formatters should work
t!(format!("{}", 1i), "1");
Expand Down Expand Up @@ -222,10 +222,10 @@ pub fn main() {
t!(format!("{:+10.3f}", -1.0f), " -1.000");

// Escaping
t!(format!("\\{"), "{");
t!(format!("\\}"), "}");
t!(format!("\\#"), "#");
t!(format!("\\\\"), "\\");
t!(format!("%{"), "{");
t!(format!("%}"), "}");
t!(format!("%#"), "#");
t!(format!("%%"), "%");

test_write();
test_print();
Expand Down