From 441b137307ba5fbc22d0d32dca6e5c0f9559dbf5 Mon Sep 17 00:00:00 2001
From: Melody Horn <melody@boringcactus.com>
Date: Fri, 2 Apr 2021 20:37:54 -0600
Subject: implement define blocks

---
 src/makefile/mod.rs   | 64 ++++++++++++++++++++++++++++++++++++++++++++++-----
 src/makefile/token.rs |  5 +---
 2 files changed, 59 insertions(+), 10 deletions(-)

diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index f4f0853..fb92a01 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -42,6 +42,10 @@ enum LineType {
 
 impl LineType {
     fn of(line_tokens: &TokenString) -> Self {
+        #[cfg(feature = "full")]
+        if line_tokens.starts_with("define ") {
+            return Self::Macro;
+        }
         for token in line_tokens.tokens() {
             if let Token::Text(text) = token {
                 let colon_idx = text.find(':');
@@ -242,7 +246,9 @@ impl<'a> Makefile<'a> {
 
                 match line_type {
                     LineType::Rule => self.read_rule(&line_tokens, line_number, &mut lines_iter)?,
-                    LineType::Macro => self.read_macro(&line_tokens, line_number)?,
+                    LineType::Macro => {
+                        self.read_macro(line_tokens, line_number, &mut lines_iter)?
+                    }
                     LineType::Unknown => {
                         if !line_tokens.is_empty() {
                             bail!(
@@ -377,10 +383,37 @@ impl<'a> Makefile<'a> {
         Ok(())
     }
 
-    fn read_macro(&mut self, line_tokens: &TokenString, line_number: usize) -> Result<()> {
-        let (name, mut value) = line_tokens
-            .split_once('=')
-            .ok_or_else(|| eyre!("read_rule couldn't find a ':' on line {}", line_number))?;
+    fn read_macro(
+        &mut self,
+        mut line_tokens: TokenString,
+        line_number: usize,
+        lines_iter: &mut Peekable<impl Iterator<Item = (usize, Result<String>)>>,
+    ) -> Result<()> {
+        let (name, mut value) = if cfg!(feature = "full") && line_tokens.starts_with("define ") {
+            line_tokens.strip_prefix("define ");
+            if line_tokens.ends_with("=") {
+                line_tokens.strip_suffix("=");
+                line_tokens.trim_end();
+            }
+            let mut value = TokenString::from(vec![]);
+            for (_, line) in lines_iter {
+                let line = line?;
+                if line == "endef" {
+                    break;
+                }
+                dbg!(&value, &line);
+                if !value.is_empty() {
+                    value.extend(TokenString::text("\n"));
+                }
+                value.extend(line.parse()?);
+                dbg!(&value);
+            }
+            (line_tokens, value)
+        } else {
+            line_tokens
+                .split_once('=')
+                .ok_or_else(|| eyre!("read_rule couldn't find a ':' on line {}", line_number))?
+        };
         let name = self.expand_macros(&name, None)?;
         // GNUisms are annoying, but popular
         let mut expand_value = false;
@@ -762,8 +795,27 @@ endif
         Ok(())
     }
 
+    #[cfg(feature = "full")]
+    #[test]
+    fn define_syntax() -> R {
+        let file = "
+define foo =
+bar
+baz
+endef
+        ";
+        let args = Args::empty();
+        let mut makefile = empty_makefile(&args);
+        makefile.and_read(Cursor::new(file))?;
+        assert_eq!(
+            makefile.expand_macros(&TokenString::r#macro("foo"), None)?,
+            "bar\nbaz"
+        );
+        Ok(())
+    }
+
     #[test]
-    #[ignore = "I still haven't implemented `eval` or `define` or %-based macro substitution."]
+    #[ignore = "I still haven't implemented `eval` or %-based macro substitution."]
     fn eval() -> R {
         // This, for the record, is a terrible misfeature.
         // If you need this, you probably shouldn't be using Make.
diff --git a/src/makefile/token.rs b/src/makefile/token.rs
index 271580a..3bb9f4e 100644
--- a/src/makefile/token.rs
+++ b/src/makefile/token.rs
@@ -63,7 +63,6 @@ impl TokenString {
         None
     }
 
-    #[cfg(feature = "full")]
     pub fn starts_with(&self, pattern: &str) -> bool {
         match self.0.first() {
             Some(Token::Text(t)) => t.starts_with(pattern),
@@ -78,7 +77,6 @@ impl TokenString {
         }
     }
 
-    #[cfg(feature = "full")]
     pub fn strip_prefix(&mut self, suffix: &str) {
         if let Some(Token::Text(t)) = self.0.first_mut() {
             if let Some(x) = t.strip_prefix(suffix) {
@@ -105,7 +103,6 @@ impl TokenString {
         }
     }
 
-    #[cfg(feature = "full")]
     pub fn trim_end(&mut self) {
         if let Some(Token::Text(t)) = self.0.last_mut() {
             *t = t.trim_end().into();
@@ -115,7 +112,7 @@ impl TokenString {
     pub fn is_empty(&self) -> bool {
         match self.0.get(0) {
             None => true,
-            Some(Token::Text(t)) if t.is_empty() => true,
+            Some(Token::Text(t)) if t.is_empty() && self.0.len() == 1 => true,
             _ => false,
         }
     }
-- 
cgit v1.2.3