From c507571c5dc0376dd4aa66221d818f8229b0d6dc Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Fri, 10 Apr 2015 18:44:59 +1200
Subject: [PATCH] Coalesce adjacent writes

Fixes #5
---
 maud_macros/src/render.rs | 79 ++++++++++++++++++++++++---------------
 1 file changed, 48 insertions(+), 31 deletions(-)

diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs
index 8c74cc8..7da3961 100644
--- a/maud_macros/src/render.rs
+++ b/maud_macros/src/render.rs
@@ -14,8 +14,9 @@ pub enum Escape {
 
 pub struct Renderer<'cx> {
     pub cx: &'cx ExtCtxt<'cx>,
-    stmts: Vec<P<Stmt>>,
     w: Ident,
+    stmts: Vec<P<Stmt>>,
+    tail: String,
 }
 
 impl<'cx> Renderer<'cx> {
@@ -23,8 +24,9 @@ impl<'cx> Renderer<'cx> {
     pub fn new(cx: &'cx ExtCtxt<'cx>) -> Renderer<'cx> {
         Renderer {
             cx: cx,
-            stmts: vec![],
             w: Ident::new(token::intern("w")),
+            stmts: Vec::new(),
+            tail: String::new(),
         }
     }
 
@@ -32,14 +34,29 @@ impl<'cx> Renderer<'cx> {
     pub fn fork(&self) -> Renderer<'cx> {
         Renderer {
             cx: self.cx,
-            stmts: vec![],
             w: self.w,
+            stmts: Vec::new(),
+            tail: String::new(),
+        }
+    }
+
+    /// Flushes the tail buffer, emitting a single `.write_str()` call.
+    fn flush(&mut self) {
+        if !self.tail.is_empty() {
+            let expr = {
+                let w = self.w;
+                let s = &*self.tail;
+                quote_expr!(self.cx, $w.write_str($s))
+            };
+            let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
+            self.stmts.push(stmt);
+            self.tail.clear();
         }
     }
 
     /// Reifies the `Renderer` into a block of markup.
-    pub fn into_expr(self) -> P<Expr> {
-        let Renderer { cx, stmts, w } = self;
+    pub fn into_expr(mut self) -> P<Expr> {
+        let Renderer { cx, w, stmts, .. } = { self.flush(); self };
         quote_expr!(cx,
             ::maud::rt::make_markup(|$w: &mut ::std::fmt::Write| -> Result<(), ::std::fmt::Error> {
                 $stmts
@@ -48,27 +65,26 @@ impl<'cx> Renderer<'cx> {
     }
 
     /// Reifies the `Renderer` into a raw list of statements.
-    pub fn into_stmts(self) -> Vec<P<Stmt>> {
-        let Renderer { stmts, .. } = self;
+    pub fn into_stmts(mut self) -> Vec<P<Stmt>> {
+        let Renderer { stmts, .. } = { self.flush(); self };
         stmts
     }
 
     /// Appends the list of statements to the output.
     pub fn push_stmts(&mut self, mut stmts: Vec<P<Stmt>>) {
+        self.flush();
         self.stmts.append(&mut stmts);
     }
 
-    /// Pushes an expression statement, also wrapping it with `try!`.
-    fn push_try(&mut self, expr: P<Expr>) {
-        let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
+    /// Pushes an statement, flushing the tail buffer in the process.
+    fn push(&mut self, stmt: P<Stmt>) {
+        self.flush();
         self.stmts.push(stmt);
     }
 
-    /// Appends a literal pre-escaped string.
-    fn write(&mut self, s: &str) {
-        let w = self.w;
-        let expr = quote_expr!(self.cx, $w.write_str($s));
-        self.push_try(expr);
+    /// Pushes a literal string to the tail buffer.
+    fn push_str(&mut self, s: &str) {
+        self.tail.push_str(s);
     }
 
     /// Appends a literal string, with the specified escaping method.
@@ -78,7 +94,7 @@ impl<'cx> Renderer<'cx> {
             Escape::PassThru => s,
             Escape::Escape => { escaped = maud::escape(s); &*escaped },
         };
-        self.write(s);
+        self.push_str(s);
     }
 
     /// Appends the result of an expression, with the specified escaping method.
@@ -93,37 +109,38 @@ impl<'cx> Renderer<'cx> {
                         &mut ::maud::rt::Escaper { inner: $w },
                         $expr)),
         };
-        self.push_try(expr);
+        let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
+        self.push(stmt);
     }
 
     pub fn element_open_start(&mut self, name: &str) {
-        self.write("<");
-        self.write(name);
+        self.push_str("<");
+        self.push_str(name);
     }
 
     pub fn attribute_start(&mut self, name: &str) {
-        self.write(" ");
-        self.write(name);
-        self.write("=\"");
+        self.push_str(" ");
+        self.push_str(name);
+        self.push_str("=\"");
     }
 
     pub fn attribute_empty(&mut self, name: &str) {
-        self.write(" ");
-        self.write(name);
+        self.push_str(" ");
+        self.push_str(name);
     }
 
     pub fn attribute_end(&mut self) {
-        self.write("\"");
+        self.push_str("\"");
     }
 
     pub fn element_open_end(&mut self) {
-        self.write(">");
+        self.push_str(">");
     }
 
     pub fn element_close(&mut self, name: &str) {
-        self.write("</");
-        self.write(name);
-        self.write(">");
+        self.push_str("</");
+        self.push_str(name);
+        self.push_str(">");
     }
 
     /// Emits an `if` expression.
@@ -137,11 +154,11 @@ impl<'cx> Renderer<'cx> {
             Some(else_body) =>
                 quote_stmt!(self.cx, if $if_cond { $if_body } else { $else_body }),
         }.unwrap();
-        self.stmts.push(stmt);
+        self.push(stmt);
     }
 
     pub fn emit_for(&mut self, pattern: P<Pat>, iterable: P<Expr>, body: Vec<P<Stmt>>) {
         let stmt = quote_stmt!(self.cx, for $pattern in $iterable { $body }).unwrap();
-        self.stmts.push(stmt);
+        self.push(stmt);
     }
 }