1use crate::kinds::SyntaxKind::*;
2use rowan::{ast::AstNode as OtherAstNode, NodeOrToken};
3
4use crate::ast;
5
6use super::{support::children_tokens_u, AstToken, InterpolPart, StrContent};
7
8impl ast::Str {
9 pub fn parts(&self) -> impl Iterator<Item = InterpolPart<StrContent>> {
10 self.syntax().children_with_tokens().filter_map(|child| match child {
11 NodeOrToken::Token(token) if token.kind() == TOKEN_STRING_CONTENT => {
12 Some(InterpolPart::Literal(StrContent::cast(token).unwrap()))
13 }
14 NodeOrToken::Token(token) => {
15 assert!(token.kind() == TOKEN_STRING_START || token.kind() == TOKEN_STRING_END);
16 None
17 }
18 NodeOrToken::Node(node) => {
19 assert_eq!(node.kind(), NODE_INTERPOL);
20 Some(InterpolPart::Interpolation(ast::Interpol::cast(node.clone()).unwrap()))
21 }
22 })
23 }
24
25 pub fn normalized_parts(&self) -> Vec<InterpolPart<String>> {
26 let multiline = children_tokens_u(self).next().map_or(false, |t| t.text() == "''");
27 let mut is_first_literal = true;
28 let mut at_start_of_line = true;
29 let mut min_indent = 1000000;
30 let mut cur_indent = 0;
31 let mut n = 0;
32 let mut first_is_literal = false;
33
34 let parts: Vec<InterpolPart<StrContent>> = self.parts().collect();
35
36 if multiline {
37 for part in &parts {
38 match part {
39 InterpolPart::Interpolation(_) => {
40 if at_start_of_line {
41 at_start_of_line = false;
42 min_indent = min_indent.min(cur_indent);
43 }
44 n += 1;
45 }
46 InterpolPart::Literal(literal) => {
47 let mut token_text = literal.syntax().text();
48
49 if n == 0 {
50 first_is_literal = true;
51 }
52
53 if is_first_literal && first_is_literal {
54 is_first_literal = false;
55 if let Some(p) = token_text.find('\n') {
56 if token_text[0..p].chars().all(|c| c == ' ') {
57 token_text = &token_text[p + 1..]
58 }
59 }
60 }
61
62 for c in token_text.chars() {
63 if at_start_of_line {
64 if c == ' ' {
65 cur_indent += 1;
66 } else if c == '\n' {
67 cur_indent = 0;
68 } else {
69 at_start_of_line = false;
70 min_indent = min_indent.min(cur_indent);
71 }
72 } else if c == '\n' {
73 at_start_of_line = true;
74 cur_indent = 0;
75 }
76 }
77
78 n += 1;
79 }
80 }
81 }
82 }
83
84 let mut normalized_parts = Vec::new();
85 let mut cur_dropped = 0;
86 let mut i = 0;
87 is_first_literal = true;
88 at_start_of_line = true;
89
90 for part in parts {
91 match part {
92 InterpolPart::Interpolation(interpol) => {
93 at_start_of_line = false;
94 cur_dropped = 0;
95 normalized_parts.push(InterpolPart::Interpolation(interpol));
96 i += 1;
97 }
98 InterpolPart::Literal(literal) => {
99 let mut token_text = literal.syntax().text();
100
101 if multiline {
102 if is_first_literal && first_is_literal {
103 is_first_literal = false;
104 if let Some(p) = token_text.find('\n') {
105 if token_text[0..p].chars().all(|c| c == ' ') {
106 token_text = &token_text[p + 1..];
107 if token_text.is_empty() {
108 i += 1;
109 continue;
110 }
111 }
112 }
113 }
114
115 let mut str = String::new();
116 for c in token_text.chars() {
117 if at_start_of_line {
118 if c == ' ' {
119 if cur_dropped >= min_indent {
120 str.push(c);
121 }
122 cur_dropped += 1;
123 } else if c == '\n' {
124 cur_dropped = 0;
125 str.push(c);
126 } else {
127 at_start_of_line = false;
128 cur_dropped = 0;
129 str.push(c);
130 }
131 } else {
132 str.push(c);
133 if c == '\n' {
134 at_start_of_line = true;
135 }
136 }
137 }
138
139 if i == n - 1 {
140 if let Some(p) = str.rfind('\n') {
141 if str[p + 1..].chars().all(|c| c == ' ') {
142 str.truncate(p + 1);
143 }
144 }
145 }
146
147 normalized_parts.push(InterpolPart::Literal(unescape(&str, multiline)));
148 i += 1;
149 } else {
150 normalized_parts
151 .push(InterpolPart::Literal(unescape(token_text, multiline)));
152 }
153 }
154 }
155 }
156
157 normalized_parts
158 }
159}
160
161pub fn unescape(input: &str, multiline: bool) -> String {
163 let mut output = String::new();
164 let mut input = input.chars().peekable();
165 loop {
166 match input.next() {
167 None => break,
168 Some('"') if !multiline => break,
169 Some('\\') if !multiline => match input.next() {
170 None => break,
171 Some('n') => output.push('\n'),
172 Some('r') => output.push('\r'),
173 Some('t') => output.push('\t'),
174 Some(c) => output.push(c),
175 },
176 Some('\'') if multiline => match input.next() {
177 None => {
178 output.push('\'');
179 }
180 Some('\'') => match input.peek() {
181 Some('\'') => {
182 input.next().unwrap();
183 output.push_str("''");
184 }
185 Some('$') => {
186 input.next().unwrap();
187 output.push('$');
188 }
189 Some('\\') => {
190 input.next().unwrap();
191 match input.next() {
192 None => break,
193 Some('n') => output.push('\n'),
194 Some('r') => output.push('\r'),
195 Some('t') => output.push('\t'),
196 Some(c) => output.push(c),
197 }
198 }
199 _ => break,
200 },
201 Some(c) => {
202 output.push('\'');
203 output.push(c);
204 }
205 },
206 Some(c) => output.push(c),
207 }
208 }
209 output
210}
211
212#[cfg(test)]
213mod tests {
214 use crate::Root;
215
216 use super::*;
217
218 #[test]
219 fn string_unescapes() {
220 assert_eq!(unescape(r#"Hello\n\"World\" :D"#, false), "Hello\n\"World\" :D");
221 assert_eq!(unescape(r#"\"Hello\""#, false), "\"Hello\"");
222
223 assert_eq!(unescape(r#"Hello''\n'''World''' :D"#, true), "Hello\n''World'' :D");
224 assert_eq!(unescape(r#""Hello""#, true), "\"Hello\"");
225 }
226 #[test]
227 fn parts_leading_ws() {
228 let inp = "''\n hello\n world''";
229 let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
230 match expr {
231 ast::Expr::Str(str) => {
232 assert_eq!(
233 str.normalized_parts(),
234 vec![InterpolPart::Literal("hello\nworld".to_string())]
235 )
236 }
237 _ => unreachable!(),
238 }
239 }
240 #[test]
241 fn parts_trailing_ws_single_line() {
242 let inp = "''hello ''";
243 let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
244 match expr {
245 ast::Expr::Str(str) => {
246 assert_eq!(
247 str.normalized_parts(),
248 vec![InterpolPart::Literal("hello ".to_string())]
249 )
250 }
251 _ => unreachable!(),
252 }
253 }
254 #[test]
255 fn parts_trailing_ws_multiline() {
256 let inp = "''hello\n ''";
257 let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
258 match expr {
259 ast::Expr::Str(str) => {
260 assert_eq!(
261 str.normalized_parts(),
262 vec![InterpolPart::Literal("hello\n".to_string())]
263 )
264 }
265 _ => unreachable!(),
266 }
267 }
268 #[test]
269 fn parts() {
270 use crate::{NixLanguage, SyntaxNode};
271 use rowan::{GreenNodeBuilder, Language};
272
273 fn string_node(content: &str) -> ast::Str {
274 let mut builder = GreenNodeBuilder::new();
275 builder.start_node(NixLanguage::kind_to_raw(NODE_STRING));
276 builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_START), "''");
277 builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_CONTENT), content);
278 builder.token(NixLanguage::kind_to_raw(TOKEN_STRING_END), "''");
279 builder.finish_node();
280
281 ast::Str::cast(SyntaxNode::new_root(builder.finish())).unwrap()
282 }
283
284 let txtin = r#"
285 |trailing-whitespace
286 |trailing-whitespace
287 This is a multiline string :D
288 indented by two
289 \'\'\'\'\
290 ''${ interpolation was escaped }
291 two single quotes: '''
292 three single quotes: ''''
293 "#
294 .replace("|trailing-whitespace", "");
295
296 if let [InterpolPart::Literal(lit)] =
297 &ast::Str::normalized_parts(&string_node(txtin.as_str()))[..]
298 {
299 assert_eq!(lit,
300 " \n \nThis is a multiline string :D\n indented by two\n\\'\\'\\'\\'\\\n${ interpolation was escaped }\ntwo single quotes: ''\nthree single quotes: '''\n"
302 );
303 } else {
304 unreachable!();
305 }
306 }
307
308 #[test]
309 fn parts_ast() {
310 fn assert_eq_ast_ctn(it: &mut dyn Iterator<Item = InterpolPart<String>>, x: &str) {
311 let tmp = it.next().expect("unexpected EOF");
312 if let InterpolPart::Interpolation(astn) = tmp {
313 assert_eq!(astn.expr().unwrap().syntax().to_string(), x);
314 } else {
315 unreachable!("unexpected literal {:?}", tmp);
316 }
317 }
318
319 let inp = r#"''
320
321 This version of Nixpkgs requires Nix >= ${requiredVersion}, please upgrade:
322
323 - If you are running NixOS, `nixos-rebuild' can be used to upgrade your system.
324
325 - Alternatively, with Nix > 2.0 `nix upgrade-nix' can be used to imperatively
326 upgrade Nix. You may use `nix-env --version' to check which version you have.
327
328 - If you installed Nix using the install script (https://nixos.org/nix/install),
329 it is safe to upgrade by running it again:
330
331 curl -L https://nixos.org/nix/install | sh
332
333 For more information, please see the NixOS release notes at
334 https://nixos.org/nixos/manual or locally at
335 ${toString ./nixos/doc/manual/release-notes}.
336
337 If you need further help, see https://nixos.org/nixos/support.html
338 ''"#;
339 let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
340 match expr {
341 ast::Expr::Str(s) => {
342 let mut it = s.normalized_parts().into_iter();
343 assert_eq!(
344 it.next().unwrap(),
345 InterpolPart::Literal("\nThis version of Nixpkgs requires Nix >= ".to_string())
346 );
347 assert_eq_ast_ctn(&mut it, "requiredVersion");
348 assert_eq!(it.next().unwrap(), InterpolPart::Literal(
349 ", please upgrade:\n\n- If you are running NixOS, `nixos-rebuild' can be used to upgrade your system.\n\n- Alternatively, with Nix > 2.0 `nix upgrade-nix' can be used to imperatively\n upgrade Nix. You may use `nix-env --version' to check which version you have.\n\n- If you installed Nix using the install script (https://nixos.org/nix/install),\n it is safe to upgrade by running it again:\n\n curl -L https://nixos.org/nix/install | sh\n\nFor more information, please see the NixOS release notes at\nhttps://nixos.org/nixos/manual or locally at\n".to_string()
350 ));
351 assert_eq_ast_ctn(&mut it, "toString ./nixos/doc/manual/release-notes");
352 assert_eq!(
353 it.next().unwrap(),
354 InterpolPart::Literal(
355 ".\n\nIf you need further help, see https://nixos.org/nixos/support.html\n"
356 .to_string()
357 )
358 );
359 }
360 _ => unreachable!(),
361 }
362 }
363}