|
1 | 1 | extern crate lalrpop_util; |
2 | 2 |
|
3 | 3 | use std::iter; |
4 | | -use std::mem; |
5 | 4 |
|
6 | 5 | use super::ast; |
7 | 6 | use super::error::ParseError; |
@@ -86,74 +85,111 @@ impl From<FStringError> |
86 | 85 | } |
87 | 86 | } |
88 | 87 |
|
| 88 | +enum ParseState { |
| 89 | + Text { content: String }, |
| 90 | + FormattedValue { expression: String, depth: usize }, |
| 91 | +} |
| 92 | + |
89 | 93 | pub fn parse_fstring(source: &str) -> Result<ast::StringGroup, FStringError> { |
| 94 | + use self::ParseState::*; |
| 95 | + |
90 | 96 | let mut values = vec![]; |
91 | | - let mut start = 0; |
92 | | - let mut depth = 0; |
93 | | - let mut escaped = false; |
94 | | - let mut content = String::new(); |
95 | | - |
96 | | - let mut chars = source.char_indices().peekable(); |
97 | | - while let Some((pos, ch)) = chars.next() { |
98 | | - match ch { |
99 | | - '{' | '}' if escaped => { |
100 | | - if depth == 0 { |
| 97 | + let mut state = ParseState::Text { |
| 98 | + content: String::new(), |
| 99 | + }; |
| 100 | + |
| 101 | + let mut chars = source.chars().peekable(); |
| 102 | + while let Some(ch) = chars.next() { |
| 103 | + state = match state { |
| 104 | + Text { mut content } => match ch { |
| 105 | + '{' => { |
| 106 | + if let Some('{') = chars.peek() { |
| 107 | + chars.next(); |
| 108 | + content.push('{'); |
| 109 | + Text { content } |
| 110 | + } else { |
| 111 | + if !content.is_empty() { |
| 112 | + values.push(ast::StringGroup::Constant { value: content }); |
| 113 | + } |
| 114 | + |
| 115 | + FormattedValue { |
| 116 | + expression: String::new(), |
| 117 | + depth: 0, |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + '}' => { |
| 122 | + if let Some('}') = chars.peek() { |
| 123 | + chars.next(); |
| 124 | + content.push('}'); |
| 125 | + Text { content } |
| 126 | + } else { |
| 127 | + return Err(FStringError::UnopenedRbrace); |
| 128 | + } |
| 129 | + } |
| 130 | + _ => { |
101 | 131 | content.push(ch); |
| 132 | + Text { content } |
102 | 133 | } |
103 | | - escaped = false; |
104 | | - } |
105 | | - '{' => { |
106 | | - if let Some((_, '{')) = chars.peek() { |
107 | | - escaped = true; |
108 | | - continue; |
| 134 | + }, |
| 135 | + |
| 136 | + FormattedValue { |
| 137 | + mut expression, |
| 138 | + depth, |
| 139 | + } => match ch { |
| 140 | + '{' => { |
| 141 | + if let Some('{') = chars.peek() { |
| 142 | + expression.push_str("{{"); |
| 143 | + chars.next(); |
| 144 | + FormattedValue { expression, depth } |
| 145 | + } else { |
| 146 | + expression.push('{'); |
| 147 | + FormattedValue { |
| 148 | + expression, |
| 149 | + depth: depth + 1, |
| 150 | + } |
| 151 | + } |
109 | 152 | } |
110 | | - |
111 | | - if depth == 0 { |
112 | | - if !content.is_empty() { |
113 | | - values.push(ast::StringGroup::Constant { |
114 | | - value: mem::replace(&mut content, String::new()), |
| 153 | + '}' => { |
| 154 | + if let Some('}') = chars.peek() { |
| 155 | + expression.push_str("}}"); |
| 156 | + chars.next(); |
| 157 | + FormattedValue { expression, depth } |
| 158 | + } else if depth > 0 { |
| 159 | + expression.push('}'); |
| 160 | + FormattedValue { |
| 161 | + expression, |
| 162 | + depth: depth - 1, |
| 163 | + } |
| 164 | + } else { |
| 165 | + values.push(ast::StringGroup::FormattedValue { |
| 166 | + value: Box::new(match parse_expression(dbg!(expression.trim())) { |
| 167 | + Ok(expr) => expr, |
| 168 | + Err(_) => return Err(FStringError::InvalidExpression), |
| 169 | + }), |
115 | 170 | }); |
| 171 | + Text { |
| 172 | + content: String::new(), |
| 173 | + } |
116 | 174 | } |
117 | | - |
118 | | - start = pos + 1; |
119 | | - } |
120 | | - |
121 | | - depth += 1; |
122 | | - } |
123 | | - '}' => { |
124 | | - if let Some((_, '}')) = chars.peek() { |
125 | | - escaped = true; |
126 | | - continue; |
127 | 175 | } |
128 | | - |
129 | | - if depth == 0 { |
130 | | - return Err(FStringError::UnopenedRbrace); |
| 176 | + _ => { |
| 177 | + expression.push(ch); |
| 178 | + FormattedValue { expression, depth } |
131 | 179 | } |
| 180 | + }, |
| 181 | + }; |
| 182 | + } |
132 | 183 |
|
133 | | - depth -= 1; |
134 | | - if depth == 0 { |
135 | | - values.push(ast::StringGroup::FormattedValue { |
136 | | - value: Box::new(match parse_expression(source[start..pos].trim()) { |
137 | | - Ok(expr) => expr, |
138 | | - Err(_) => return Err(FStringError::InvalidExpression), |
139 | | - }), |
140 | | - }); |
141 | | - } |
142 | | - } |
143 | | - ch => { |
144 | | - if depth == 0 { |
145 | | - content.push(ch); |
146 | | - } |
| 184 | + match state { |
| 185 | + Text { content } => { |
| 186 | + if !content.is_empty() { |
| 187 | + values.push(ast::StringGroup::Constant { value: content }) |
147 | 188 | } |
148 | 189 | } |
149 | | - } |
150 | | - |
151 | | - if depth != 0 { |
152 | | - return Err(FStringError::UnclosedLbrace); |
153 | | - } |
154 | | - |
155 | | - if !content.is_empty() { |
156 | | - values.push(ast::StringGroup::Constant { value: content }) |
| 190 | + FormattedValue { .. } => { |
| 191 | + return Err(FStringError::UnclosedLbrace); |
| 192 | + } |
157 | 193 | } |
158 | 194 |
|
159 | 195 | Ok(match values.len() { |
|
0 commit comments