]> git.lizzy.rs Git - rust.git/blobdiff - src/expr.rs
Merge pull request #128 from marcusklaas/subexpr
[rust.git] / src / expr.rs
index 70695f30c4f01b6687dc15755440362279c940d8..33a82da0f29ff4c9e8cfcfc7c535cd41cb8bd16c 100644 (file)
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use rewrite::{Rewrite, RewriteContext};
+use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
+use string::{StringFormat, rewrite_string};
+use utils::{span_after, make_indent};
 use visitor::FmtVisitor;
-use utils::*;
-use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
 
 use syntax::{ast, ptr};
-use syntax::codemap::{Pos, Span};
+use syntax::codemap::{Pos, Span, BytePos, mk_sp};
 use syntax::parse::token;
 use syntax::print::pprust;
+use syntax::visit::Visitor;
 
-use MIN_STRING;
-
-impl<'a> FmtVisitor<'a> {
-    // TODO NEEDS TESTS
-    fn rewrite_string_lit(&mut self, s: &str, span: Span, width: usize, offset: usize) -> String {
-        // FIXME I bet this stomps unicode escapes in the source string
-
-        // Check if there is anything to fix: we always try to fixup multi-line
-        // strings, or if the string is too long for the line.
-        let l_loc = self.codemap.lookup_char_pos(span.lo);
-        let r_loc = self.codemap.lookup_char_pos(span.hi);
-        if l_loc.line == r_loc.line && r_loc.col.to_usize() <= config!(max_width) {
-            return self.snippet(span);
+impl Rewrite for ast::Expr {
+    fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
+        match self.node {
+            ast::Expr_::ExprLit(ref l) => {
+                match l.node {
+                    ast::Lit_::LitStr(ref is, _) => {
+                        rewrite_string_lit(context, &is, l.span, width, offset)
+                    }
+                    _ => context.codemap.span_to_snippet(self.span).ok()
+                }
+            }
+            ast::Expr_::ExprCall(ref callee, ref args) => {
+                rewrite_call(context, callee, args, self.span, width, offset)
+            }
+            ast::Expr_::ExprParen(ref subexpr) => {
+                rewrite_paren(context, subexpr, width, offset)
+            }
+            ast::Expr_::ExprBinary(ref op, ref lhs, ref rhs) => {
+                rewrite_binary_op(context, op, lhs, rhs, width, offset)
+            }
+            ast::Expr_::ExprUnary(ref op, ref subexpr) => {
+                rewrite_unary_op(context, op, subexpr, width, offset)
+            }
+            ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
+                rewrite_struct_lit(context,
+                                   path,
+                                   fields,
+                                   base.as_ref().map(|e| &**e),
+                                   self.span,
+                                   width,
+                                   offset)
+            }
+            ast::Expr_::ExprTup(ref items) => {
+                rewrite_tuple_lit(context, items, self.span, width, offset)
+            }
+            ast::Expr_::ExprLoop(ref block, _) => {
+                // FIXME: this drops any comment between "loop" and the block.
+                // TODO: format label
+                block.rewrite(context, width, offset).map(|result| {
+                    format!("loop {}", result)
+                })
+            }
+            _ => context.codemap.span_to_snippet(self.span).ok()
         }
+    }
+}
 
-        // TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
-
-        let s = s.escape_default();
+impl Rewrite for ast::Block {
+    fn rewrite(&self, context: &RewriteContext, _: usize, _: usize) -> Option<String> {
+        let mut visitor = FmtVisitor::from_codemap(context.codemap, context.config);
+        visitor.last_pos = self.span.lo;
+        visitor.block_indent = context.block_indent;
 
-        let offset = offset + 1;
-        let indent = make_indent(offset);
-        let indent = &indent;
+        visitor.visit_block(self);
 
-        let max_chars = width - 1;
+        // Push text between last block item and end of block
+        let snippet = visitor.snippet(mk_sp(visitor.last_pos, self.span.hi));
+        visitor.changes.push_str_span(self.span, &snippet);
 
-        let mut cur_start = 0;
-        let mut result = String::new();
-        result.push('"');
-        loop {
-            let mut cur_end = cur_start + max_chars;
+        // Stringify visitor
+        let file_name = context.codemap.span_to_filename(self.span);
+        let string_buffer = visitor.changes.get(&file_name);
 
-            if cur_end >= s.len() {
-                result.push_str(&s[cur_start..]);
-                break;
-            }
+        Some(string_buffer.to_string())
+    }
+}
 
-            // Make sure we're on a char boundary.
-            cur_end = next_char(&s, cur_end);
+fn rewrite_string_lit(context: &RewriteContext,
+                      s: &str,
+                      span: Span,
+                      width: usize,
+                      offset: usize)
+                      -> Option<String> {
+    // Check if there is anything to fix: we always try to fixup multi-line
+    // strings, or if the string is too long for the line.
+    let l_loc = context.codemap.lookup_char_pos(span.lo);
+    let r_loc = context.codemap.lookup_char_pos(span.hi);
+    if l_loc.line == r_loc.line && r_loc.col.to_usize() <= context.config.max_width {
+        return context.codemap.span_to_snippet(span).ok();
+    }
+    let fmt = StringFormat { opener: "\"",
+                             closer: "\"",
+                             line_start: " ",
+                             line_end: "\\",
+                             width: width,
+                             offset: offset,
+                             trim_end: false, };
 
-            // Push cur_end left until we reach whitespace
-            while !s.char_at(cur_end-1).is_whitespace() {
-                cur_end = prev_char(&s, cur_end);
+    Some(rewrite_string(&s.escape_default(), &fmt))
+}
 
-                if cur_end - cur_start < MIN_STRING {
-                    // We can't break at whitespace, fall back to splitting
-                    // anywhere that doesn't break an escape sequence
-                    cur_end = next_char(&s, cur_start + max_chars);
-                    while s.char_at(cur_end) == '\\' {
-                        cur_end = prev_char(&s, cur_end);
-                    }
-                }
-            }
-            // Make sure there is no whitespace to the right of the break.
-            while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
-                cur_end = next_char(&s, cur_end+1);
-            }
-            result.push_str(&s[cur_start..cur_end]);
-            result.push_str("\\\n");
-            result.push_str(indent);
+fn rewrite_call(context: &RewriteContext,
+                callee: &ast::Expr,
+                args: &[ptr::P<ast::Expr>],
+                span: Span,
+                width: usize,
+                offset: usize)
+                -> Option<String> {
+    debug!("rewrite_call, width: {}, offset: {}", width, offset);
 
-            cur_start = cur_end;
-        }
-        result.push('"');
+    // TODO using byte lens instead of char lens (and probably all over the place too)
+    let callee_str = try_opt!(callee.rewrite(context, width, offset));
+    debug!("rewrite_call, callee_str: `{}`", callee_str);
 
-        result
+    if args.len() == 0 {
+        return Some(format!("{}()", callee_str));
     }
 
-    fn rewrite_call(&mut self,
-                    callee: &ast::Expr,
-                    args: &[ptr::P<ast::Expr>],
-                    width: usize,
-                    offset: usize)
-        -> String
-    {
-        debug!("rewrite_call, width: {}, offset: {}", width, offset);
-
-        // TODO using byte lens instead of char lens (and probably all over the place too)
-        let callee_str = self.rewrite_expr(callee, width, offset);
-        debug!("rewrite_call, callee_str: `{}`", callee_str);
-        // 2 is for parens.
-        let remaining_width = width - callee_str.len() - 2;
-        let offset = callee_str.len() + 1 + offset;
-        let arg_count = args.len();
-
-        let args_str = if arg_count > 0 {
-            let args: Vec<_> = args.iter().map(|e| (self.rewrite_expr(e,
-                                                                      remaining_width,
-                                                                      offset), String::new())).collect();
-            // TODO move this into write_list
-            let tactics = if args.iter().any(|&(ref s, _)| s.contains('\n')) {
-                ListTactic::Vertical
-            } else {
-                ListTactic::HorizontalVertical
-            };
-            let fmt = ListFormatting {
-                tactic: tactics,
-                separator: ",",
-                trailing_separator: SeparatorTactic::Never,
-                indent: offset,
-                h_width: remaining_width,
-                v_width: remaining_width,
-            };
-            write_list(&args, &fmt)
-        } else {
-            String::new()
-        };
+    // 2 is for parens.
+    let remaining_width = width - callee_str.len() - 2;
+    let offset = callee_str.len() + 1 + offset;
 
-        format!("{}({})", callee_str, args_str)
-    }
+    let items = itemize_list(context.codemap,
+                             Vec::new(),
+                             args.iter(),
+                             ",",
+                             ")",
+                             |item| item.span.lo,
+                             |item| item.span.hi,
+                             // Take old span when rewrite fails.
+                             |item| item.rewrite(context, remaining_width, offset)
+                                        .unwrap_or(context.codemap.span_to_snippet(item.span)
+                                                                  .unwrap()),
+                             callee.span.hi + BytePos(1),
+                             span.hi);
 
-    fn rewrite_paren(&mut self, subexpr: &ast::Expr, width: usize, offset: usize) -> String {
-        debug!("rewrite_paren, width: {}, offset: {}", width, offset);
-        // 1 is for opening paren, 2 is for opening+closing, we want to keep the closing
-        // paren on the same line as the subexpr
-        let subexpr_str = self.rewrite_expr(subexpr, width-2, offset+1);
-        debug!("rewrite_paren, subexpr_str: `{}`", subexpr_str);
-        format!("({})", subexpr_str)
-    }
+    let fmt = ListFormatting { tactic: ListTactic::HorizontalVertical,
+                               separator: ",",
+                               trailing_separator: SeparatorTactic::Never,
+                               indent: offset,
+                               h_width: remaining_width,
+                               v_width: remaining_width,
+                               ends_with_newline: true, };
+
+    Some(format!("{}({})", callee_str, write_list(&items, &fmt)))
+}
+
+fn rewrite_paren(context: &RewriteContext,
+                 subexpr: &ast::Expr,
+                 width: usize,
+                 offset: usize)
+                 -> Option<String> {
+    debug!("rewrite_paren, width: {}, offset: {}", width, offset);
+    // 1 is for opening paren, 2 is for opening+closing, we want to keep the closing
+    // paren on the same line as the subexpr
+    let subexpr_str = subexpr.rewrite(context, width-2, offset+1);
+    debug!("rewrite_paren, subexpr_str: `{:?}`", subexpr_str);
+    subexpr_str.map(|s| format!("({})", s))
+}
 
-    fn rewrite_struct_lit(&mut self,
+fn rewrite_struct_lit<'a>(context: &RewriteContext,
                           path: &ast::Path,
-                          fields: &[ast::Field],
-                          base: Option<&ast::Expr>,
+                          fields: &'a [ast::Field],
+                          base: Option<&'a ast::Expr>,
+                          span: Span,
                           width: usize,
                           offset: usize)
-        -> String
-    {
-        debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
-        assert!(fields.len() > 0 || base.is_some());
-
-        let path_str = pprust::path_to_string(path);
-        // Foo { a: Foo } - indent is +3, width is -5.
-        let indent = offset + path_str.len() + 3;
-        let budget = width - (path_str.len() + 5);
-
-        let mut field_strs: Vec<_> =
-            fields.iter().map(|f| self.rewrite_field(f, budget, indent)).collect();
-        if let Some(expr) = base {
-            // Another 2 on the width/indent for the ..
-            field_strs.push(format!("..{}", self.rewrite_expr(expr, budget - 2, indent + 2)))
-        }
+                          -> Option<String> {
+    debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
+    assert!(fields.len() > 0 || base.is_some());
 
-        // FIXME comments
-        let field_strs: Vec<_> = field_strs.into_iter().map(|s| (s, String::new())).collect();
-        let tactics = if field_strs.iter().any(|&(ref s, _)| s.contains('\n')) {
-            ListTactic::Vertical
-        } else {
-            ListTactic::HorizontalVertical
-        };
-        let fmt = ListFormatting {
-            tactic: tactics,
-            separator: ",",
-            trailing_separator: if base.is_some() {
-                    SeparatorTactic::Never
-                } else {
-                    config!(struct_lit_trailing_comma)
-                },
-            indent: indent,
-            h_width: budget,
-            v_width: budget,
-        };
-        let fields_str = write_list(&field_strs, &fmt);
-        format!("{} {{ {} }}", path_str, fields_str)
-
-        // FIXME if the usual multi-line layout is too wide, we should fall back to
-        // Foo {
-        //     a: ...,
-        // }
+    enum StructLitField<'a> {
+        Regular(&'a ast::Field),
+        Base(&'a ast::Expr),
     }
 
-    fn rewrite_field(&mut self, field: &ast::Field, width: usize, offset: usize) -> String {
-        let name = &token::get_ident(field.ident.node);
-        let overhead = name.len() + 2;
-        let expr = self.rewrite_expr(&field.expr, width - overhead, offset + overhead);
-        format!("{}: {}", name, expr)
-    }
+    let path_str = pprust::path_to_string(path);
+    // Foo { a: Foo } - indent is +3, width is -5.
+    let indent = offset + path_str.len() + 3;
+    let budget = width - (path_str.len() + 5);
 
-    fn rewrite_tuple_lit(&mut self, items: &[ptr::P<ast::Expr>], width: usize, offset: usize)
-        -> String {
-        // opening paren
-        let indent = offset + 1;
-        // In case of length 1, need a trailing comma
-        if items.len() == 1 {
-            return format!("({},)", self.rewrite_expr(&*items[0], width - 3, indent));
-        }
-        // Only last line has width-1 as budget, other may take max_width
-        let item_strs: Vec<_> =
-            items.iter()
-                 .enumerate()
-                 .map(|(i, item)| self.rewrite_expr(
-                    item,
-                    // last line : given width (minus "("+")"), other lines : max_width
-                    // (minus "("+","))
-                    if i == items.len() - 1 { width - 2 } else { config!(max_width) - indent - 2 },
-                    indent))
-                 .collect();
-        let tactics = if item_strs.iter().any(|s| s.contains('\n')) {
-            ListTactic::Vertical
+    let field_iter = fields.into_iter().map(StructLitField::Regular)
+                           .chain(base.into_iter().map(StructLitField::Base));
+
+    let items = itemize_list(context.codemap,
+                             Vec::new(),
+                             field_iter,
+                             ",",
+                             "}",
+                             |item| {
+                                 match *item {
+                                     StructLitField::Regular(ref field) => field.span.lo,
+                                     // 2 = ..
+                                     StructLitField::Base(ref expr) => expr.span.lo - BytePos(2)
+                                 }
+                             },
+                             |item| {
+                                 match *item {
+                                     StructLitField::Regular(ref field) => field.span.hi,
+                                     StructLitField::Base(ref expr) => expr.span.hi
+                                 }
+                             },
+                             |item| {
+                                 match *item {
+                                     StructLitField::Regular(ref field) => {
+                                         rewrite_field(context, &field, budget, indent)
+                                            .unwrap_or(context.codemap.span_to_snippet(field.span)
+                                                                      .unwrap())
+                                     },
+                                     StructLitField::Base(ref expr) => {
+                                         // 2 = ..
+                                         expr.rewrite(context, budget - 2, indent + 2)
+                                             .map(|s| format!("..{}", s))
+                                             .unwrap_or(context.codemap.span_to_snippet(expr.span)
+                                                                       .unwrap())
+                                     }
+                                 }
+                             },
+                             span_after(span, "{", context.codemap),
+                             span.hi);
+
+    let fmt = ListFormatting { tactic: ListTactic::HorizontalVertical,
+                               separator: ",",
+                               trailing_separator: if base.is_some() {
+            SeparatorTactic::Never
         } else {
-            ListTactic::HorizontalVertical
-        };
-        // FIXME handle comments
-        let item_strs: Vec<_> = item_strs.into_iter().map(|s| (s, String::new())).collect();
-        let fmt = ListFormatting {
-            tactic: tactics,
-            separator: ",",
-            trailing_separator: SeparatorTactic::Never,
-            indent: indent,
-            h_width: width - 2,
-            v_width: width - 2,
-        };
-        let item_str = write_list(&item_strs, &fmt);
-        format!("({})", item_str)
-    }
+            context.config.struct_lit_trailing_comma
+        },
+                               indent: indent,
+                               h_width: budget,
+                               v_width: budget,
+                               ends_with_newline: true, };
+    let fields_str = write_list(&items, &fmt);
+    Some(format!("{} {{ {} }}", path_str, fields_str))
 
+    // FIXME if the usual multi-line layout is too wide, we should fall back to
+    // Foo {
+    //     a: ...,
+    // }
+}
 
-    pub fn rewrite_expr(&mut self, expr: &ast::Expr, width: usize, offset: usize) -> String {
-        match expr.node {
-            ast::Expr_::ExprLit(ref l) => {
-                match l.node {
-                    ast::Lit_::LitStr(ref is, _) => {
-                        let result = self.rewrite_string_lit(&is, l.span, width, offset);
-                        debug!("string lit: `{}`", result);
-                        return result;
-                    }
-                    _ => {}
-                }
-            }
-            ast::Expr_::ExprCall(ref callee, ref args) => {
-                return self.rewrite_call(callee, args, width, offset);
-            }
-            ast::Expr_::ExprParen(ref subexpr) => {
-                return self.rewrite_paren(subexpr, width, offset);
-            }
-            ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
-                return self.rewrite_struct_lit(path,
-                                               fields,
-                                               base.as_ref().map(|e| &**e),
-                                               width,
-                                               offset);
-            }
-            ast::Expr_::ExprTup(ref items) => {
-                return self.rewrite_tuple_lit(items, width, offset);
-            }
-            _ => {}
-        }
+fn rewrite_field(context: &RewriteContext,
+                 field: &ast::Field,
+                 width: usize,
+                 offset: usize)
+                 -> Option<String> {
+    let name = &token::get_ident(field.ident.node);
+    let overhead = name.len() + 2;
+    let expr = field.expr.rewrite(context, width - overhead, offset + overhead);
+    expr.map(|s| format!("{}: {}", name, s))
+}
 
-        let result = self.snippet(expr.span);
-        result
+fn rewrite_tuple_lit(context: &RewriteContext,
+                     items: &[ptr::P<ast::Expr>],
+                     span: Span,
+                     width: usize,
+                     offset: usize)
+                     -> Option<String> {
+    debug!("rewrite_tuple_lit: width: {}, offset: {}", width, offset);
+    let indent = offset + 1;
+    // In case of length 1, need a trailing comma
+    if items.len() == 1 {
+        // 3 = "(" + ",)"
+        return items[0].rewrite(context, width - 3, indent).map(|s| format!("({},)", s));
     }
+
+    let items = itemize_list(context.codemap,
+                             Vec::new(),
+                             items.into_iter(),
+                             ",",
+                             ")",
+                             |item| item.span.lo,
+                             |item| item.span.hi,
+                             |item| item.rewrite(context,
+                                                 context.config.max_width - indent - 1,
+                                                 indent)
+                                        .unwrap_or(context.codemap.span_to_snippet(item.span)
+                                                                  .unwrap()),
+                             span.lo + BytePos(1), // Remove parens
+                             span.hi - BytePos(1));
+
+    let fmt = ListFormatting { tactic: ListTactic::HorizontalVertical,
+                               separator: ",",
+                               trailing_separator: SeparatorTactic::Never,
+                               indent: indent,
+                               h_width: width - 2,
+                               v_width: width - 2,
+                               ends_with_newline: true, };
+
+    Some(format!("({})", write_list(&items, &fmt)))
+}
+
+fn rewrite_binary_op(context: &RewriteContext,
+                     op: &ast::BinOp,
+                     lhs: &ast::Expr,
+                     rhs: &ast::Expr,
+                     width: usize,
+                     offset: usize)
+                     -> Option<String> {
+    // FIXME: format comments between operands and operator
+
+    let operator_str = context.codemap.span_to_snippet(op.span).unwrap();
+
+    // 1 = space between lhs expr and operator
+    let mut result =
+        try_opt!(lhs.rewrite(context, context.config.max_width - offset - 1 - operator_str.len(), offset));
+
+    result.push(' ');
+    result.push_str(&operator_str);
+
+    let remaining_width = match result.rfind('\n') {
+        Some(idx) => (offset + width + idx).checked_sub(result.len()).unwrap_or(0),
+        None => width.checked_sub(result.len()).unwrap_or(0)
+    };
+
+    // Get "full width" rhs and see if it fits on the current line. This
+    // usually works fairly well since it tends to place operands of
+    // operations with high precendence close together.
+    let rhs_result = try_opt!(rhs.rewrite(context, width, offset));
+
+    // Second condition is needed in case of line break not caused by a
+    // shortage of space, but by end-of-line comments, for example.
+    if rhs_result.len() > remaining_width || rhs_result.contains('\n') {
+        result.push('\n');
+        result.push_str(&make_indent(offset));
+    } else {
+        result.push(' ');
+    };
+
+    result.push_str(&rhs_result);
+    Some(result)
+}
+
+fn rewrite_unary_op(context: &RewriteContext,
+                    op: &ast::UnOp,
+                    expr: &ast::Expr,
+                    width: usize,
+                    offset: usize)
+                    -> Option<String> {
+    // For some reason, an UnOp is not spanned like BinOp!
+    let operator_str = match *op {
+        ast::UnOp::UnUniq => "&",
+        ast::UnOp::UnDeref => "*",
+        ast::UnOp::UnNot => "!",
+        ast::UnOp::UnNeg => "-"
+    };
+
+    Some(format!("{}{}", operator_str, try_opt!(expr.rewrite(context, width - 1, offset))))
 }