1 | use super::{FmtArg, FmtStrComponent, FormatStr, ParseError, ParseErrorKind, WhichArg}; |
2 | |
3 | use crate::{ |
4 | formatting::{FormattingFlags, IsAlternate, NumberFormatting}, |
5 | parse_utils::StrRawness, |
6 | }; |
7 | |
8 | #[cfg (test)] |
9 | impl FmtStrComponent { |
10 | pub(super) fn str(s: &str) -> Self { |
11 | Self::Str(s.to_string(), StrRawness::dummy()) |
12 | } |
13 | pub(super) fn arg(which_arg: WhichArg, formatting: FormattingFlags) -> Self { |
14 | Self::Arg(FmtArg { |
15 | which_arg, |
16 | formatting, |
17 | rawness: StrRawness::dummy(), |
18 | }) |
19 | } |
20 | } |
21 | |
22 | impl FmtArg { |
23 | fn new(which_arg: WhichArg, formatting: FormattingFlags, rawness: StrRawness) -> Self { |
24 | Self { |
25 | which_arg, |
26 | formatting, |
27 | rawness, |
28 | } |
29 | } |
30 | } |
31 | |
32 | #[allow (dead_code)] |
33 | impl WhichArg { |
34 | pub(super) fn ident(s: &str) -> Self { |
35 | Self::Ident(s.to_string()) |
36 | } |
37 | } |
38 | |
39 | ///////////////////////////////////// |
40 | |
41 | #[cfg (test)] |
42 | impl std::str::FromStr for FormatStr { |
43 | type Err = ParseError; |
44 | |
45 | fn from_str(input: &str) -> Result<FormatStr, ParseError> { |
46 | parse_format_str(input, StrRawness::dummy()) |
47 | } |
48 | } |
49 | |
50 | impl FormatStr { |
51 | pub fn parse(input: &str, rawness: StrRawness) -> Result<FormatStr, ParseError> { |
52 | parse_format_str(input, rawness) |
53 | } |
54 | } |
55 | |
56 | fn parse_format_str(input: &str, rawness: StrRawness) -> Result<FormatStr, ParseError> { |
57 | let mut components = Vec::<FmtStrComponent>::new(); |
58 | |
59 | let mut arg_start = 0; |
60 | |
61 | loop { |
62 | let open_pos = input.find_from('{' , arg_start); |
63 | |
64 | let str = &input[arg_start..open_pos.unwrap_or(input.len())]; |
65 | components.push_arg_str(parse_mid_str(str, arg_start)?, rawness); |
66 | |
67 | if let Some(open_pos) = open_pos { |
68 | let after_open = open_pos + 1; |
69 | if input[after_open..].starts_with('{' ) { |
70 | components.push_arg_str("{" .to_string(), rawness); |
71 | |
72 | arg_start = open_pos + 2; |
73 | } else if let Some(close_pos) = input.find_from('}' , after_open) { |
74 | let after_close = close_pos + 1; |
75 | |
76 | let arg = parse_fmt_arg(&input[after_open..close_pos], after_open, rawness)?; |
77 | components.push(FmtStrComponent::Arg(arg)); |
78 | |
79 | arg_start = after_close; |
80 | } else { |
81 | return Err(ParseError { |
82 | pos: open_pos, |
83 | kind: ParseErrorKind::UnclosedArg, |
84 | }); |
85 | } |
86 | } else { |
87 | break; |
88 | } |
89 | } |
90 | |
91 | Ok(FormatStr { list: components }) |
92 | } |
93 | |
94 | /// Parses the text between arguments, to unescape `}}` into `}` |
95 | fn parse_mid_str(str: &str, starts_at: usize) -> Result<String, ParseError> { |
96 | let mut buffer: String = String::with_capacity(str.len()); |
97 | |
98 | let mut starts_pos: usize = 0; |
99 | let bytes: &[u8] = str.as_bytes(); |
100 | |
101 | while let Some(close_pos: usize) = str.find_from(c:'}' , from:starts_pos) { |
102 | let after_close: usize = close_pos + 1; |
103 | if bytes.get(index:after_close) == Some(&b'}' ) { |
104 | buffer.push_str(&str[starts_pos..after_close]); |
105 | starts_pos = after_close + 1; |
106 | } else { |
107 | return Err(ParseError { |
108 | pos: starts_at + close_pos, |
109 | kind: ParseErrorKind::InvalidClosedArg, |
110 | }); |
111 | } |
112 | } |
113 | buffer.push_str(&str[starts_pos..]); |
114 | |
115 | Ok(buffer) |
116 | } |
117 | |
118 | /// Parses the format arguments (`{:?}`, `{foo:}`, `{0}`, etc). |
119 | /// |
120 | /// `starts_at` is the offset of `input` in the formatting string. |
121 | fn parse_fmt_arg(input: &str, starts_at: usize, rawness: StrRawness) -> Result<FmtArg, ParseError> { |
122 | let colon: Option = input.find(':' ); |
123 | |
124 | let which_arg_str: &str = &input[..colon.unwrap_or(default:input.len())]; |
125 | let formatting_str: &'static str = colon.map_or(default:"" , |x: usize| &input[x + 1..]); |
126 | let formatting_starts_at: usize = colon.map_or(default:input.len(), |x: usize| starts_at + x + 1); |
127 | |
128 | Ok(FmtArg::new( |
129 | parse_which_arg(input:which_arg_str, starts_at)?, |
130 | parse_formatting(input:formatting_str, formatting_starts_at)?, |
131 | rawness, |
132 | )) |
133 | } |
134 | |
135 | /// Parses the name of the argument in `{foo}`, `{}`, `{bar:?}` |
136 | /// |
137 | /// `starts_at` is the offset of `input` in the formatting string. |
138 | fn parse_which_arg(input: &str, starts_at: usize) -> Result<WhichArg, ParseError> { |
139 | if input.is_empty() { |
140 | Ok(WhichArg::Positional(None)) |
141 | } else if input.as_bytes()[0].is_ascii_digit() { |
142 | match input.parse::<usize>() { |
143 | Ok(number: usize) => Ok(WhichArg::Positional(Some(number))), |
144 | Err(_) => Err(ParseError { |
145 | pos: starts_at, |
146 | kind: ParseErrorKind::NotANumber { |
147 | what: input.to_string(), |
148 | }, |
149 | }), |
150 | } |
151 | } else { |
152 | parse_ident(ident_str:input, starts_at) |
153 | } |
154 | } |
155 | |
156 | /// Parses the `?` and other formatters inside formatting arguments (`{}`). |
157 | /// |
158 | /// `starts_at` is the offset of `input` in the formatting string. |
159 | fn parse_formatting(input: &str, starts_at: usize) -> Result<FormattingFlags, ParseError> { |
160 | match input { |
161 | "#" => return Ok(FormattingFlags::display(IsAlternate::Yes)), |
162 | "" => return Ok(FormattingFlags::display(IsAlternate::No)), |
163 | _ => {} |
164 | } |
165 | |
166 | let mut bytes = input.as_bytes(); |
167 | |
168 | let make_error = || ParseError { |
169 | pos: starts_at, |
170 | kind: ParseErrorKind::UnknownFormatting { |
171 | what: input.to_string(), |
172 | }, |
173 | }; |
174 | |
175 | if let [before @ .., b'?' ] = bytes { |
176 | bytes = before; |
177 | } |
178 | |
179 | let mut num_fmt = NumberFormatting::Decimal; |
180 | let mut is_alternate = IsAlternate::No; |
181 | |
182 | for byte in bytes { |
183 | match byte { |
184 | b'b' if num_fmt.is_regular() => num_fmt = NumberFormatting::Binary, |
185 | b'x' if num_fmt.is_regular() => num_fmt = NumberFormatting::LowerHexadecimal, |
186 | b'X' if num_fmt.is_regular() => num_fmt = NumberFormatting::Hexadecimal, |
187 | b'#' => is_alternate = IsAlternate::Yes, |
188 | _ => return Err(make_error()), |
189 | } |
190 | } |
191 | Ok(FormattingFlags::debug(num_fmt, is_alternate)) |
192 | } |
193 | |
194 | /// Parses an identifier in a formatting argument. |
195 | /// |
196 | /// `starts_at` is the offset of `input` in the formatting string. |
197 | fn parse_ident(ident_str: &str, starts_at: usize) -> Result<WhichArg, ParseError> { |
198 | if is_ident(ident_str) { |
199 | Ok(WhichArg::Ident(ident_str.to_string())) |
200 | } else { |
201 | Err(ParseError { |
202 | pos: starts_at, |
203 | kind: ParseErrorKind::NotAnIdent { |
204 | what: ident_str.to_string(), |
205 | }, |
206 | }) |
207 | } |
208 | } |
209 | |
210 | //////////////////////////////////////////////////////////////////////////////// |
211 | |
212 | fn is_ident(s: &str) -> bool { |
213 | use unicode_xid::UnicodeXID; |
214 | |
215 | if s.is_empty() || s == "_" { |
216 | return false; |
217 | } |
218 | |
219 | let mut chars: Chars<'_> = s.chars(); |
220 | let first: char = chars.next().unwrap(); |
221 | |
222 | // For some reason '_' is not considered a valid character for the stard of an ident |
223 | (first.is_xid_start() || first == '_' ) && chars.all(|c: char| c.is_xid_continue()) |
224 | } |
225 | |
226 | //////////////////////////////////////////////////////////////////////////////// |
227 | |
228 | trait VecExt { |
229 | fn push_arg_str(&mut self, str: String, rawness: StrRawness); |
230 | } |
231 | |
232 | impl VecExt for Vec<FmtStrComponent> { |
233 | fn push_arg_str(&mut self, str: String, rawness: StrRawness) { |
234 | if !str.is_empty() { |
235 | self.push(FmtStrComponent::Str(str, rawness)); |
236 | } |
237 | } |
238 | } |
239 | |
240 | trait StrExt { |
241 | fn find_from(&self, c: char, from: usize) -> Option<usize>; |
242 | } |
243 | |
244 | impl StrExt for str { |
245 | fn find_from(&self, c: char, from: usize) -> Option<usize> { |
246 | self[from..].find(c).map(|p: usize| p + from) |
247 | } |
248 | } |
249 | |