From 2250c2e9616a5615f0792176f98a442ab5c9650a Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Sun, 18 Jan 2015 20:18:21 +1300
Subject: [PATCH] Make splices a bit nicer to use

---
 maud_macros/src/parse.rs   | 42 ++++++++++++++++++++++++++++----------
 maud_macros/tests/tests.rs | 27 ++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 7555afc..4d89bbc 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -11,6 +11,9 @@ use super::render::{Escape, Renderer};
 macro_rules! dollar {
     () => (TtToken(_, token::Dollar))
 }
+macro_rules! dot {
+    () => (TtToken(_, token::Dot))
+}
 macro_rules! eq {
     () => (TtToken(_, token::Eq))
 }
@@ -125,17 +128,34 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
     }
 
     fn splice(&mut self, escape: Escape, sp: Span) {
-        let tt = match self.input {
-            [ref tt, ..] => {
-                self.shift(1);
-                self.new_rust_parser(vec![tt.clone()]).parse_expr()
-            },
-            _ => {
-                self.render.cx.span_err(sp, "expected expression for this splice");
-                return;
-            },
-        };
-        self.render.splice(tt, escape);
+        let mut tts = vec![];
+        // First, munch a single token tree
+        if let [ref tt, ..] = self.input {
+            self.shift(1);
+            tts.push(tt.clone());
+        }
+        loop {
+            match self.input {
+                // Munch attribute lookups e.g. `$person.address.street`
+                [ref dot @ dot!(), ref ident @ ident!(_), ..] => {
+                    self.shift(2);
+                    tts.push(dot.clone());
+                    tts.push(ident.clone());
+                },
+                // Munch function calls `()` and indexing operations `[]`
+                [TtDelimited(sp, ref d), ..] if d.delim != token::DelimToken::Brace => {
+                    self.shift(1);
+                    tts.push(TtDelimited(sp, d.clone()));
+                },
+                _ => break,
+            }
+        }
+        if tts.is_empty() {
+            self.render.cx.span_err(sp, "expected expression for this splice");
+        } else {
+            let expr = self.new_rust_parser(tts).parse_expr();
+            self.render.splice(expr, escape);
+        }
     }
 
     fn element(&mut self, name: &str, sp: Span) {
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index 2d94c2f..25a936b 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -123,6 +123,33 @@ mod splices {
         assert_eq!(s, "Pinkie Pie");
     }
 
+    /// An example struct, for testing purposes only
+    struct Creature {
+        name: &'static str,
+        /// Rating out of 10, where:
+        /// * 0 is a naked mole rat with dysentery
+        /// * 10 is Sweetie Belle in a milkshake
+        adorableness: u8,
+    }
+
+    impl Creature {
+        fn repugnance(&self) -> u8 {
+            10 - self.adorableness
+        }
+    }
+
+    #[test]
+    fn structs() {
+        let pinkie = Creature {
+            name: "Pinkie Pie",
+            adorableness: 9,
+        };
+        let s = html! {
+            "Name: " $pinkie.name ". Rating: " $pinkie.repugnance()
+        }.render();
+        assert_eq!(s, "Name: Pinkie Pie. Rating: 1");
+    }
+
     // FIXME: See <https://github.com/rust-lang/rust/issues/16617>
     // for why this is commented out
     /*