]> git.lizzy.rs Git - rust.git/blob - src/expr.rs
Struct literals
[rust.git] / src / expr.rs
1 // Copyright 2015 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 use visitor::FmtVisitor;
12 use utils::*;
13 use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
14
15 use syntax::{ast, ptr};
16 use syntax::codemap::{Pos, Span};
17 use syntax::parse::token;
18 use syntax::print::pprust;
19
20 use MIN_STRING;
21
22 impl<'a> FmtVisitor<'a> {
23     // TODO NEEDS TESTS
24     fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
25         // FIXME I bet this stomps unicode escapes in the source string
26
27         // Check if there is anything to fix: we always try to fixup multi-line
28         // strings, or if the string is too long for the line.
29         let l_loc = self.codemap.lookup_char_pos(span.lo);
30         let r_loc = self.codemap.lookup_char_pos(span.hi);
31         if l_loc.line == r_loc.line && r_loc.col.to_usize() <= config!(max_width) {
32             return self.snippet(span);
33         }
34
35         // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
36
37         let s = s.escape_default();
38
39         let offset = offset + 1;
40         let indent = make_indent(offset);
41         let indent = &indent;
42
43         let max_chars = width - 1;
44
45         let mut cur_start = 0;
46         let mut result = String::new();
47         result.push('"');
48         loop {
49             let mut cur_end = cur_start + max_chars;
50
51             if cur_end >= s.len() {
52                 result.push_str(&s[cur_start..]);
53                 break;
54             }
55
56             // Make sure we're on a char boundary.
57             cur_end = next_char(&s, cur_end);
58
59             // Push cur_end left until we reach whitespace
60             while !s.char_at(cur_end-1).is_whitespace() {
61                 cur_end = prev_char(&s, cur_end);
62
63                 if cur_end - cur_start < MIN_STRING {
64                     // We can't break at whitespace, fall back to splitting
65                     // anywhere that doesn't break an escape sequence
66                     cur_end = next_char(&s, cur_start + max_chars);
67                     while s.char_at(cur_end) == '\\' {
68                         cur_end = prev_char(&s, cur_end);
69                     }
70                 }
71             }
72             // Make sure there is no whitespace to the right of the break.
73             while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
74                 cur_end = next_char(&s, cur_end+1);
75             }
76             result.push_str(&s[cur_start..cur_end]);
77             result.push_str("\\\n");
78             result.push_str(indent);
79
80             cur_start = cur_end;
81         }
82         result.push('"');
83
84         result
85     }
86
87     fn rewrite_call(&mut self,
88                     callee: &ast::Expr,
89                     args: &[ptr::P<ast::Expr>],
90                     width: usize,
91                     offset: usize)
92         -> String
93     {
94         debug!("rewrite_call, width: {}, offset: {}", width, offset);
95
96         // TODO using byte lens instead of char lens (and probably all over the place too)
97         let callee_str = self.rewrite_expr(callee, width, offset);
98         debug!("rewrite_call, callee_str: `{}`", callee_str);
99         // 2 is for parens.
100         let remaining_width = width - callee_str.len() - 2;
101         let offset = callee_str.len() + 1 + offset;
102         let arg_count = args.len();
103
104         let args_str = if arg_count > 0 {
105             let args: Vec<_> = args.iter().map(|e| (self.rewrite_expr(e,
106                                                                       remaining_width,
107                                                                       offset), String::new())).collect();
108             // TODO move this into write_list
109             let tactics = if args.iter().any(|&(ref s, _)| s.contains('\n')) {
110                 ListTactic::Vertical
111             } else {
112                 ListTactic::HorizontalVertical
113             };
114             let fmt = ListFormatting {
115                 tactic: tactics,
116                 separator: ",",
117                 trailing_separator: SeparatorTactic::Never,
118                 indent: offset,
119                 h_width: remaining_width,
120                 v_width: remaining_width,
121             };
122             write_list(&args, &fmt)
123         } else {
124             String::new()
125         };
126
127         format!("{}({})", callee_str, args_str)
128     }
129
130     fn rewrite_paren(&mut self, subexpr: &ast::Expr, width: usize, offset: usize) -> String {
131         debug!("rewrite_paren, width: {}, offset: {}", width, offset);
132         // 1 is for opening paren, 2 is for opening+closing, we want to keep the closing
133         // paren on the same line as the subexpr
134         let subexpr_str = self.rewrite_expr(subexpr, width-2, offset+1);
135         debug!("rewrite_paren, subexpr_str: `{}`", subexpr_str);
136         format!("({})", subexpr_str)
137     }
138
139     fn rewrite_struct_lit(&mut self,
140                           path: &ast::Path,
141                           fields: &[ast::Field],
142                           base: Option<&ast::Expr>,
143                           width: usize,
144                           offset: usize)
145         -> String
146     {
147         debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
148         assert!(fields.len() > 0 || base.is_some());
149
150         let path_str = pprust::path_to_string(path);
151         // Foo { a: Foo } - indent is +3, width is -5.
152         let indent = offset + path_str.len() + 3;
153         let budget = width - (path_str.len() + 5);
154
155         let mut field_strs: Vec<_> =
156             fields.iter().map(|f| self.rewrite_field(f, budget, indent)).collect();
157         if let Some(expr) = base {
158             // Another 2 on the width/indent for the ..
159             field_strs.push(format!("..{}", self.rewrite_expr(expr, budget - 2, indent + 2)))
160         }
161
162         // FIXME comments
163         let field_strs: Vec<_> = field_strs.into_iter().map(|s| (s, String::new())).collect();
164         let tactics = if field_strs.iter().any(|&(ref s, _)| s.contains('\n')) {
165             ListTactic::Vertical
166         } else {
167             ListTactic::HorizontalVertical
168         };
169         let fmt = ListFormatting {
170             tactic: tactics,
171             separator: ",",
172             trailing_separator: if base.is_some() {
173                     SeparatorTactic::Never
174                 } else {
175                     config!(struct_lit_trailing_comma)
176                 },
177             indent: indent,
178             h_width: budget,
179             v_width: budget,
180         };
181         let fields_str = write_list(&field_strs, &fmt);
182         format!("{} {{ {} }}", path_str, fields_str)
183
184         // FIXME if the usual multi-line layout is too wide, we should fall back to
185         // Foo {
186         //     a: ...,
187         // }
188     }
189
190     fn rewrite_field(&mut self, field: &ast::Field, width: usize, offset: usize) -> String {
191         let name = &token::get_ident(field.ident.node);
192         let overhead = name.len() + 2;
193         let expr = self.rewrite_expr(&field.expr, width - overhead, offset + overhead);
194         format!("{}: {}", name, expr)
195     }
196
197     pub fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
198         match expr.node {
199             ast::Expr_::ExprLit(ref l) => {
200                 match l.node {
201                     ast::Lit_::LitStr(ref is, _) => {
202                         let result = self.rewrite_string_lit(&is, l.span, width, offset);
203                         debug!("string lit: `{}`", result);
204                         return result;
205                     }
206                     _ => {}
207                 }
208             }
209             ast::Expr_::ExprCall(ref callee, ref args) => {
210                 return self.rewrite_call(callee, args, width, offset);
211             }
212             ast::Expr_::ExprParen(ref subexpr) => {
213                 return self.rewrite_paren(subexpr, width, offset);
214             }
215             ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
216                 return self.rewrite_struct_lit(path,
217                                                fields,
218                                                base.as_ref().map(|e| &**e),
219                                                width,
220                                                offset);
221             }
222             _ => {}
223         }
224
225         let result = self.snippet(expr.span);
226         result
227     }
228 }