]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2013 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Macro support for format strings | |
12 | //! | |
13 | //! These structures are used when parsing format strings for the compiler. | |
14 | //! Parsing does not happen at runtime: structures of `std::fmt::rt` are | |
15 | //! generated instead. | |
16 | ||
c34b1796 AL |
17 | // Do not remove on snapshot creation. Needed for bootstrap. (Issue #22364) |
18 | #![cfg_attr(stage0, feature(custom_attribute))] | |
1a4d82fc | 19 | #![crate_name = "fmt_macros"] |
e9174d1e | 20 | #![unstable(feature = "rustc_private", issue = "27812")] |
92a42be0 | 21 | #![cfg_attr(stage0, staged_api)] |
1a4d82fc JJ |
22 | #![crate_type = "rlib"] |
23 | #![crate_type = "dylib"] | |
e9174d1e | 24 | #![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", |
62682a34 | 25 | html_favicon_url = "https://doc.rust-lang.org/favicon.ico", |
e9174d1e | 26 | html_root_url = "https://doc.rust-lang.org/nightly/", |
92a42be0 SL |
27 | html_playground_url = "https://play.rust-lang.org/", |
28 | test(attr(deny(warnings))))] | |
1a4d82fc | 29 | |
85aaf69f SL |
30 | #![feature(staged_api)] |
31 | #![feature(unicode)] | |
1a4d82fc JJ |
32 | |
33 | pub use self::Piece::*; | |
34 | pub use self::Position::*; | |
35 | pub use self::Alignment::*; | |
36 | pub use self::Flag::*; | |
37 | pub use self::Count::*; | |
38 | ||
39 | use std::str; | |
40 | use std::string; | |
e9174d1e | 41 | use std::iter; |
1a4d82fc JJ |
42 | |
43 | /// A piece is a portion of the format string which represents the next part | |
44 | /// to emit. These are emitted as a stream by the `Parser` class. | |
c34b1796 | 45 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
46 | pub enum Piece<'a> { |
47 | /// A literal string which should directly be emitted | |
48 | String(&'a str), | |
49 | /// This describes that formatting should process the next argument (as | |
50 | /// specified inside) for emission. | |
51 | NextArgument(Argument<'a>), | |
52 | } | |
53 | ||
54 | /// Representation of an argument specification. | |
c34b1796 | 55 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
56 | pub struct Argument<'a> { |
57 | /// Where to find this argument | |
58 | pub position: Position<'a>, | |
59 | /// How to format the argument | |
60 | pub format: FormatSpec<'a>, | |
61 | } | |
62 | ||
63 | /// Specification for the formatting of an argument in the format string. | |
c34b1796 | 64 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
65 | pub struct FormatSpec<'a> { |
66 | /// Optionally specified character to fill alignment with | |
67 | pub fill: Option<char>, | |
68 | /// Optionally specified alignment | |
69 | pub align: Alignment, | |
70 | /// Packed version of various flags provided | |
c34b1796 | 71 | pub flags: u32, |
1a4d82fc JJ |
72 | /// The integer precision to use |
73 | pub precision: Count<'a>, | |
74 | /// The string width requested for the resulting format | |
75 | pub width: Count<'a>, | |
76 | /// The descriptor string representing the name of the format desired for | |
77 | /// this argument, this can be empty or any number of characters, although | |
78 | /// it is required to be one word. | |
b039eaaf | 79 | pub ty: &'a str, |
1a4d82fc JJ |
80 | } |
81 | ||
82 | /// Enum describing where an argument for a format can be located. | |
c34b1796 | 83 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
84 | pub enum Position<'a> { |
85 | /// The argument will be in the next position. This is the default. | |
86 | ArgumentNext, | |
87 | /// The argument is located at a specific index. | |
c34b1796 | 88 | ArgumentIs(usize), |
1a4d82fc JJ |
89 | /// The argument has a name. |
90 | ArgumentNamed(&'a str), | |
91 | } | |
92 | ||
93 | /// Enum of alignments which are supported. | |
c34b1796 | 94 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
95 | pub enum Alignment { |
96 | /// The value will be aligned to the left. | |
97 | AlignLeft, | |
98 | /// The value will be aligned to the right. | |
99 | AlignRight, | |
100 | /// The value will be aligned in the center. | |
101 | AlignCenter, | |
102 | /// The value will take on a default alignment. | |
103 | AlignUnknown, | |
104 | } | |
105 | ||
106 | /// Various flags which can be applied to format strings. The meaning of these | |
107 | /// flags is defined by the formatters themselves. | |
c34b1796 | 108 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
109 | pub enum Flag { |
110 | /// A `+` will be used to denote positive numbers. | |
111 | FlagSignPlus, | |
112 | /// A `-` will be used to denote negative numbers. This is the default. | |
113 | FlagSignMinus, | |
114 | /// An alternate form will be used for the value. In the case of numbers, | |
115 | /// this means that the number will be prefixed with the supplied string. | |
116 | FlagAlternate, | |
117 | /// For numbers, this means that the number will be padded with zeroes, | |
118 | /// and the sign (`+` or `-`) will precede them. | |
119 | FlagSignAwareZeroPad, | |
120 | } | |
121 | ||
122 | /// A count is used for the precision and width parameters of an integer, and | |
123 | /// can reference either an argument or a literal integer. | |
c34b1796 | 124 | #[derive(Copy, Clone, PartialEq)] |
1a4d82fc JJ |
125 | pub enum Count<'a> { |
126 | /// The count is specified explicitly. | |
c34b1796 | 127 | CountIs(usize), |
1a4d82fc JJ |
128 | /// The count is specified by the argument with the given name. |
129 | CountIsName(&'a str), | |
130 | /// The count is specified by the argument at the given index. | |
c34b1796 | 131 | CountIsParam(usize), |
1a4d82fc JJ |
132 | /// The count is specified by the next parameter. |
133 | CountIsNextParam, | |
134 | /// The count is implied and cannot be explicitly specified. | |
135 | CountImplied, | |
136 | } | |
137 | ||
138 | /// The parser structure for interpreting the input format string. This is | |
b039eaaf | 139 | /// modeled as an iterator over `Piece` structures to form a stream of tokens |
1a4d82fc JJ |
140 | /// being output. |
141 | /// | |
142 | /// This is a recursive-descent parser for the sake of simplicity, and if | |
143 | /// necessary there's probably lots of room for improvement performance-wise. | |
144 | pub struct Parser<'a> { | |
145 | input: &'a str, | |
e9174d1e | 146 | cur: iter::Peekable<str::CharIndices<'a>>, |
1a4d82fc JJ |
147 | /// Error messages accumulated during parsing |
148 | pub errors: Vec<string::String>, | |
149 | } | |
150 | ||
151 | impl<'a> Iterator for Parser<'a> { | |
152 | type Item = Piece<'a>; | |
153 | ||
154 | fn next(&mut self) -> Option<Piece<'a>> { | |
e9174d1e SL |
155 | if let Some(&(pos, c)) = self.cur.peek() { |
156 | match c { | |
157 | '{' => { | |
158 | self.cur.next(); | |
159 | if self.consume('{') { | |
160 | Some(String(self.string(pos + 1))) | |
161 | } else { | |
162 | let ret = Some(NextArgument(self.argument())); | |
163 | self.must_consume('}'); | |
164 | ret | |
165 | } | |
1a4d82fc | 166 | } |
e9174d1e SL |
167 | '}' => { |
168 | self.cur.next(); | |
169 | if self.consume('}') { | |
170 | Some(String(self.string(pos + 1))) | |
171 | } else { | |
172 | self.err("unmatched `}` found"); | |
173 | None | |
174 | } | |
1a4d82fc | 175 | } |
e9174d1e | 176 | _ => Some(String(self.string(pos))), |
1a4d82fc | 177 | } |
e9174d1e SL |
178 | } else { |
179 | None | |
1a4d82fc JJ |
180 | } |
181 | } | |
182 | } | |
183 | ||
184 | impl<'a> Parser<'a> { | |
185 | /// Creates a new parser for the given format string | |
186 | pub fn new(s: &'a str) -> Parser<'a> { | |
187 | Parser { | |
188 | input: s, | |
e9174d1e | 189 | cur: s.char_indices().peekable(), |
92a42be0 | 190 | errors: vec![], |
1a4d82fc JJ |
191 | } |
192 | } | |
193 | ||
194 | /// Notifies of an error. The message doesn't actually need to be of type | |
195 | /// String, but I think it does when this eventually uses conditions so it | |
196 | /// might as well start using it now. | |
197 | fn err(&mut self, msg: &str) { | |
e9174d1e | 198 | self.errors.push(msg.to_owned()); |
1a4d82fc JJ |
199 | } |
200 | ||
201 | /// Optionally consumes the specified character. If the character is not at | |
202 | /// the current position, then the current iterator isn't moved and false is | |
203 | /// returned, otherwise the character is consumed and true is returned. | |
204 | fn consume(&mut self, c: char) -> bool { | |
e9174d1e | 205 | if let Some(&(_, maybe)) = self.cur.peek() { |
b039eaaf SL |
206 | if c == maybe { |
207 | self.cur.next(); | |
208 | true | |
209 | } else { | |
210 | false | |
211 | } | |
e9174d1e SL |
212 | } else { |
213 | false | |
1a4d82fc JJ |
214 | } |
215 | } | |
216 | ||
217 | /// Forces consumption of the specified character. If the character is not | |
218 | /// found, an error is emitted. | |
219 | fn must_consume(&mut self, c: char) { | |
220 | self.ws(); | |
e9174d1e SL |
221 | if let Some(&(_, maybe)) = self.cur.peek() { |
222 | if c == maybe { | |
1a4d82fc | 223 | self.cur.next(); |
e9174d1e SL |
224 | } else { |
225 | self.err(&format!("expected `{:?}`, found `{:?}`", c, maybe)); | |
1a4d82fc | 226 | } |
e9174d1e SL |
227 | } else { |
228 | self.err(&format!("expected `{:?}` but string was terminated", c)); | |
1a4d82fc JJ |
229 | } |
230 | } | |
231 | ||
232 | /// Consumes all whitespace characters until the first non-whitespace | |
233 | /// character | |
234 | fn ws(&mut self) { | |
e9174d1e | 235 | while let Some(&(_, c)) = self.cur.peek() { |
b039eaaf SL |
236 | if c.is_whitespace() { |
237 | self.cur.next(); | |
238 | } else { | |
92a42be0 | 239 | break; |
b039eaaf | 240 | } |
1a4d82fc JJ |
241 | } |
242 | } | |
243 | ||
244 | /// Parses all of a string which is to be considered a "raw literal" in a | |
245 | /// format string. This is everything outside of the braces. | |
c34b1796 | 246 | fn string(&mut self, start: usize) -> &'a str { |
e9174d1e SL |
247 | // we may not consume the character, peek the iterator |
248 | while let Some(&(pos, c)) = self.cur.peek() { | |
249 | match c { | |
b039eaaf SL |
250 | '{' | '}' => { |
251 | return &self.input[start..pos]; | |
252 | } | |
253 | _ => { | |
254 | self.cur.next(); | |
255 | } | |
1a4d82fc JJ |
256 | } |
257 | } | |
e9174d1e | 258 | &self.input[start..self.input.len()] |
1a4d82fc JJ |
259 | } |
260 | ||
261 | /// Parses an Argument structure, or what's contained within braces inside | |
262 | /// the format string | |
263 | fn argument(&mut self) -> Argument<'a> { | |
264 | Argument { | |
265 | position: self.position(), | |
266 | format: self.format(), | |
267 | } | |
268 | } | |
269 | ||
270 | /// Parses a positional argument for a format. This could either be an | |
271 | /// integer index of an argument, a named argument, or a blank string. | |
272 | fn position(&mut self) -> Position<'a> { | |
e9174d1e SL |
273 | if let Some(i) = self.integer() { |
274 | ArgumentIs(i) | |
275 | } else { | |
276 | match self.cur.peek() { | |
92a42be0 | 277 | Some(&(_, c)) if c.is_alphabetic() => ArgumentNamed(self.word()), |
b039eaaf | 278 | _ => ArgumentNext, |
1a4d82fc JJ |
279 | } |
280 | } | |
281 | } | |
282 | ||
283 | /// Parses a format specifier at the current position, returning all of the | |
284 | /// relevant information in the FormatSpec struct. | |
285 | fn format(&mut self) -> FormatSpec<'a> { | |
286 | let mut spec = FormatSpec { | |
287 | fill: None, | |
288 | align: AlignUnknown, | |
289 | flags: 0, | |
290 | precision: CountImplied, | |
291 | width: CountImplied, | |
85aaf69f | 292 | ty: &self.input[..0], |
1a4d82fc | 293 | }; |
b039eaaf | 294 | if !self.consume(':') { |
92a42be0 | 295 | return spec; |
b039eaaf | 296 | } |
1a4d82fc JJ |
297 | |
298 | // fill character | |
e9174d1e SL |
299 | if let Some(&(_, c)) = self.cur.peek() { |
300 | match self.cur.clone().skip(1).next() { | |
301 | Some((_, '>')) | Some((_, '<')) | Some((_, '^')) => { | |
302 | spec.fill = Some(c); | |
303 | self.cur.next(); | |
1a4d82fc | 304 | } |
e9174d1e | 305 | _ => {} |
1a4d82fc | 306 | } |
1a4d82fc JJ |
307 | } |
308 | // Alignment | |
309 | if self.consume('<') { | |
310 | spec.align = AlignLeft; | |
311 | } else if self.consume('>') { | |
312 | spec.align = AlignRight; | |
313 | } else if self.consume('^') { | |
314 | spec.align = AlignCenter; | |
315 | } | |
316 | // Sign flags | |
317 | if self.consume('+') { | |
c34b1796 | 318 | spec.flags |= 1 << (FlagSignPlus as u32); |
1a4d82fc | 319 | } else if self.consume('-') { |
c34b1796 | 320 | spec.flags |= 1 << (FlagSignMinus as u32); |
1a4d82fc JJ |
321 | } |
322 | // Alternate marker | |
323 | if self.consume('#') { | |
c34b1796 | 324 | spec.flags |= 1 << (FlagAlternate as u32); |
1a4d82fc JJ |
325 | } |
326 | // Width and precision | |
327 | let mut havewidth = false; | |
328 | if self.consume('0') { | |
329 | // small ambiguity with '0$' as a format string. In theory this is a | |
330 | // '0' flag and then an ill-formatted format string with just a '$' | |
331 | // and no count, but this is better if we instead interpret this as | |
332 | // no '0' flag and '0$' as the width instead. | |
333 | if self.consume('$') { | |
334 | spec.width = CountIsParam(0); | |
335 | havewidth = true; | |
336 | } else { | |
c34b1796 | 337 | spec.flags |= 1 << (FlagSignAwareZeroPad as u32); |
1a4d82fc JJ |
338 | } |
339 | } | |
340 | if !havewidth { | |
341 | spec.width = self.count(); | |
342 | } | |
343 | if self.consume('.') { | |
344 | if self.consume('*') { | |
345 | spec.precision = CountIsNextParam; | |
346 | } else { | |
347 | spec.precision = self.count(); | |
348 | } | |
349 | } | |
350 | // Finally the actual format specifier | |
351 | if self.consume('?') { | |
352 | spec.ty = "?"; | |
353 | } else { | |
354 | spec.ty = self.word(); | |
355 | } | |
e9174d1e | 356 | spec |
1a4d82fc JJ |
357 | } |
358 | ||
359 | /// Parses a Count parameter at the current position. This does not check | |
360 | /// for 'CountIsNextParam' because that is only used in precision, not | |
361 | /// width. | |
362 | fn count(&mut self) -> Count<'a> { | |
e9174d1e | 363 | if let Some(i) = self.integer() { |
b039eaaf SL |
364 | if self.consume('$') { |
365 | CountIsParam(i) | |
366 | } else { | |
367 | CountIs(i) | |
368 | } | |
e9174d1e SL |
369 | } else { |
370 | let tmp = self.cur.clone(); | |
371 | let word = self.word(); | |
372 | if word.is_empty() { | |
373 | self.cur = tmp; | |
374 | CountImplied | |
375 | } else { | |
1a4d82fc | 376 | if self.consume('$') { |
e9174d1e | 377 | CountIsName(word) |
1a4d82fc | 378 | } else { |
e9174d1e SL |
379 | self.cur = tmp; |
380 | CountImplied | |
1a4d82fc JJ |
381 | } |
382 | } | |
383 | } | |
384 | } | |
385 | ||
386 | /// Parses a word starting at the current position. A word is considered to | |
387 | /// be an alphabetic character followed by any number of alphanumeric | |
388 | /// characters. | |
389 | fn word(&mut self) -> &'a str { | |
e9174d1e | 390 | let start = match self.cur.peek() { |
b039eaaf SL |
391 | Some(&(pos, c)) if c.is_xid_start() => { |
392 | self.cur.next(); | |
393 | pos | |
394 | } | |
395 | _ => { | |
396 | return &self.input[..0]; | |
397 | } | |
1a4d82fc | 398 | }; |
e9174d1e SL |
399 | while let Some(&(pos, c)) = self.cur.peek() { |
400 | if c.is_xid_continue() { | |
401 | self.cur.next(); | |
402 | } else { | |
403 | return &self.input[start..pos]; | |
1a4d82fc JJ |
404 | } |
405 | } | |
e9174d1e | 406 | &self.input[start..self.input.len()] |
1a4d82fc JJ |
407 | } |
408 | ||
409 | /// Optionally parses an integer at the current position. This doesn't deal | |
410 | /// with overflow at all, it's just accumulating digits. | |
c34b1796 | 411 | fn integer(&mut self) -> Option<usize> { |
1a4d82fc JJ |
412 | let mut cur = 0; |
413 | let mut found = false; | |
e9174d1e SL |
414 | while let Some(&(_, c)) = self.cur.peek() { |
415 | if let Some(i) = c.to_digit(10) { | |
416 | cur = cur * 10 + i as usize; | |
417 | found = true; | |
418 | self.cur.next(); | |
419 | } else { | |
92a42be0 | 420 | break; |
1a4d82fc JJ |
421 | } |
422 | } | |
b039eaaf SL |
423 | if found { |
424 | Some(cur) | |
425 | } else { | |
426 | None | |
427 | } | |
1a4d82fc JJ |
428 | } |
429 | } | |
430 | ||
431 | #[cfg(test)] | |
432 | mod tests { | |
433 | use super::*; | |
434 | ||
435 | fn same(fmt: &'static str, p: &[Piece<'static>]) { | |
85aaf69f | 436 | let parser = Parser::new(fmt); |
c34b1796 | 437 | assert!(parser.collect::<Vec<Piece<'static>>>() == p); |
1a4d82fc JJ |
438 | } |
439 | ||
440 | fn fmtdflt() -> FormatSpec<'static> { | |
441 | return FormatSpec { | |
442 | fill: None, | |
443 | align: AlignUnknown, | |
444 | flags: 0, | |
445 | precision: CountImplied, | |
446 | width: CountImplied, | |
447 | ty: "", | |
92a42be0 | 448 | }; |
1a4d82fc JJ |
449 | } |
450 | ||
451 | fn musterr(s: &str) { | |
452 | let mut p = Parser::new(s); | |
453 | p.next(); | |
9346a6ac | 454 | assert!(!p.errors.is_empty()); |
1a4d82fc JJ |
455 | } |
456 | ||
457 | #[test] | |
458 | fn simple() { | |
459 | same("asdf", &[String("asdf")]); | |
460 | same("a{{b", &[String("a"), String("{b")]); | |
461 | same("a}}b", &[String("a"), String("}b")]); | |
462 | same("a}}", &[String("a"), String("}")]); | |
463 | same("}}", &[String("}")]); | |
464 | same("\\}}", &[String("\\"), String("}")]); | |
465 | } | |
466 | ||
b039eaaf SL |
467 | #[test] |
468 | fn invalid01() { | |
469 | musterr("{") | |
470 | } | |
471 | #[test] | |
472 | fn invalid02() { | |
473 | musterr("}") | |
474 | } | |
475 | #[test] | |
476 | fn invalid04() { | |
477 | musterr("{3a}") | |
478 | } | |
479 | #[test] | |
480 | fn invalid05() { | |
481 | musterr("{:|}") | |
482 | } | |
483 | #[test] | |
484 | fn invalid06() { | |
485 | musterr("{:>>>}") | |
486 | } | |
1a4d82fc JJ |
487 | |
488 | #[test] | |
489 | fn format_nothing() { | |
b039eaaf SL |
490 | same("{}", |
491 | &[NextArgument(Argument { | |
492 | position: ArgumentNext, | |
493 | format: fmtdflt(), | |
494 | })]); | |
1a4d82fc JJ |
495 | } |
496 | #[test] | |
497 | fn format_position() { | |
b039eaaf SL |
498 | same("{3}", |
499 | &[NextArgument(Argument { | |
500 | position: ArgumentIs(3), | |
501 | format: fmtdflt(), | |
502 | })]); | |
1a4d82fc JJ |
503 | } |
504 | #[test] | |
505 | fn format_position_nothing_else() { | |
b039eaaf SL |
506 | same("{3:}", |
507 | &[NextArgument(Argument { | |
508 | position: ArgumentIs(3), | |
509 | format: fmtdflt(), | |
510 | })]); | |
1a4d82fc JJ |
511 | } |
512 | #[test] | |
513 | fn format_type() { | |
b039eaaf SL |
514 | same("{3:a}", |
515 | &[NextArgument(Argument { | |
516 | position: ArgumentIs(3), | |
517 | format: FormatSpec { | |
518 | fill: None, | |
519 | align: AlignUnknown, | |
520 | flags: 0, | |
521 | precision: CountImplied, | |
522 | width: CountImplied, | |
523 | ty: "a", | |
524 | }, | |
525 | })]); | |
1a4d82fc JJ |
526 | } |
527 | #[test] | |
528 | fn format_align_fill() { | |
b039eaaf SL |
529 | same("{3:>}", |
530 | &[NextArgument(Argument { | |
531 | position: ArgumentIs(3), | |
532 | format: FormatSpec { | |
533 | fill: None, | |
534 | align: AlignRight, | |
535 | flags: 0, | |
536 | precision: CountImplied, | |
537 | width: CountImplied, | |
538 | ty: "", | |
539 | }, | |
540 | })]); | |
541 | same("{3:0<}", | |
542 | &[NextArgument(Argument { | |
543 | position: ArgumentIs(3), | |
544 | format: FormatSpec { | |
545 | fill: Some('0'), | |
546 | align: AlignLeft, | |
547 | flags: 0, | |
548 | precision: CountImplied, | |
549 | width: CountImplied, | |
550 | ty: "", | |
551 | }, | |
552 | })]); | |
553 | same("{3:*<abcd}", | |
554 | &[NextArgument(Argument { | |
555 | position: ArgumentIs(3), | |
556 | format: FormatSpec { | |
557 | fill: Some('*'), | |
558 | align: AlignLeft, | |
559 | flags: 0, | |
560 | precision: CountImplied, | |
561 | width: CountImplied, | |
562 | ty: "abcd", | |
563 | }, | |
564 | })]); | |
1a4d82fc JJ |
565 | } |
566 | #[test] | |
567 | fn format_counts() { | |
b039eaaf SL |
568 | same("{:10s}", |
569 | &[NextArgument(Argument { | |
570 | position: ArgumentNext, | |
571 | format: FormatSpec { | |
572 | fill: None, | |
573 | align: AlignUnknown, | |
574 | flags: 0, | |
575 | precision: CountImplied, | |
576 | width: CountIs(10), | |
577 | ty: "s", | |
578 | }, | |
579 | })]); | |
580 | same("{:10$.10s}", | |
581 | &[NextArgument(Argument { | |
582 | position: ArgumentNext, | |
583 | format: FormatSpec { | |
584 | fill: None, | |
585 | align: AlignUnknown, | |
586 | flags: 0, | |
587 | precision: CountIs(10), | |
588 | width: CountIsParam(10), | |
589 | ty: "s", | |
590 | }, | |
591 | })]); | |
592 | same("{:.*s}", | |
593 | &[NextArgument(Argument { | |
594 | position: ArgumentNext, | |
595 | format: FormatSpec { | |
596 | fill: None, | |
597 | align: AlignUnknown, | |
598 | flags: 0, | |
599 | precision: CountIsNextParam, | |
600 | width: CountImplied, | |
601 | ty: "s", | |
602 | }, | |
603 | })]); | |
604 | same("{:.10$s}", | |
605 | &[NextArgument(Argument { | |
606 | position: ArgumentNext, | |
607 | format: FormatSpec { | |
608 | fill: None, | |
609 | align: AlignUnknown, | |
610 | flags: 0, | |
611 | precision: CountIsParam(10), | |
612 | width: CountImplied, | |
613 | ty: "s", | |
614 | }, | |
615 | })]); | |
616 | same("{:a$.b$s}", | |
617 | &[NextArgument(Argument { | |
618 | position: ArgumentNext, | |
619 | format: FormatSpec { | |
620 | fill: None, | |
621 | align: AlignUnknown, | |
622 | flags: 0, | |
623 | precision: CountIsName("b"), | |
624 | width: CountIsName("a"), | |
625 | ty: "s", | |
626 | }, | |
627 | })]); | |
1a4d82fc JJ |
628 | } |
629 | #[test] | |
630 | fn format_flags() { | |
b039eaaf SL |
631 | same("{:-}", |
632 | &[NextArgument(Argument { | |
633 | position: ArgumentNext, | |
634 | format: FormatSpec { | |
635 | fill: None, | |
636 | align: AlignUnknown, | |
637 | flags: (1 << FlagSignMinus as u32), | |
638 | precision: CountImplied, | |
639 | width: CountImplied, | |
640 | ty: "", | |
641 | }, | |
642 | })]); | |
643 | same("{:+#}", | |
644 | &[NextArgument(Argument { | |
645 | position: ArgumentNext, | |
646 | format: FormatSpec { | |
647 | fill: None, | |
648 | align: AlignUnknown, | |
649 | flags: (1 << FlagSignPlus as u32) | (1 << FlagAlternate as u32), | |
650 | precision: CountImplied, | |
651 | width: CountImplied, | |
652 | ty: "", | |
653 | }, | |
654 | })]); | |
1a4d82fc JJ |
655 | } |
656 | #[test] | |
657 | fn format_mixture() { | |
b039eaaf SL |
658 | same("abcd {3:a} efg", |
659 | &[String("abcd "), | |
660 | NextArgument(Argument { | |
661 | position: ArgumentIs(3), | |
662 | format: FormatSpec { | |
663 | fill: None, | |
664 | align: AlignUnknown, | |
665 | flags: 0, | |
666 | precision: CountImplied, | |
667 | width: CountImplied, | |
668 | ty: "a", | |
669 | }, | |
670 | }), | |
671 | String(" efg")]); | |
1a4d82fc JJ |
672 | } |
673 | } |