From e12d3a581d97491b2445212613a9c978726390f5 Mon Sep 17 00:00:00 2001
From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com>
Date: Thu, 25 Jun 2020 00:27:24 -0400
Subject: [PATCH] reduce size of parse::Stmt

---
 src/atrule/media.rs    | 69 ++++++++++++++++++++++++++++++++++++++++++
 src/atrule/mod.rs      |  5 +++
 src/atrule/supports.rs |  7 +++++
 src/atrule/unknown.rs  |  9 ++++++
 src/output.rs          | 47 ++++++++++++++++++++--------
 src/parse/function.rs  |  6 ++--
 src/parse/media.rs     | 61 -------------------------------------
 src/parse/mod.rs       | 44 ++++++++++-----------------
 src/parse/style.rs     |  6 ++--
 src/style.rs           |  6 ++--
 10 files changed, 149 insertions(+), 111 deletions(-)
 create mode 100644 src/atrule/media.rs
 create mode 100644 src/atrule/supports.rs
 create mode 100644 src/atrule/unknown.rs

diff --git a/src/atrule/media.rs b/src/atrule/media.rs
new file mode 100644
index 0000000..ec6a93a
--- /dev/null
+++ b/src/atrule/media.rs
@@ -0,0 +1,69 @@
+use std::fmt;
+
+use crate::{parse::Stmt, selector::Selector};
+
+#[derive(Debug, Clone)]
+pub(crate) struct MediaRule {
+    pub super_selector: Selector,
+    pub query: String,
+    pub body: Vec<Stmt>,
+}
+
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub(crate) struct MediaQuery {
+    /// The modifier, probably either "not" or "only".
+    ///
+    /// This may be `None` if no modifier is in use.
+    pub modifier: Option<String>,
+
+    /// The media type, for example "screen" or "print".
+    ///
+    /// This may be `None`. If so, `self.features` will not be empty.
+    pub media_type: Option<String>,
+
+    /// Feature queries, including parentheses.
+    pub features: Vec<String>,
+}
+
+#[allow(dead_code)]
+impl MediaQuery {
+    pub fn is_condition(&self) -> bool {
+        self.modifier.is_none() && self.media_type.is_none()
+    }
+
+    pub fn matches_all_types(&self) -> bool {
+        self.media_type.is_none()
+            || self
+                .media_type
+                .as_ref()
+                .map_or(false, |v| v.to_ascii_lowercase() == "all")
+    }
+
+    pub fn condition(features: Vec<String>) -> Self {
+        Self {
+            modifier: None,
+            media_type: None,
+            features,
+        }
+    }
+
+    #[allow(dead_code, unused_variables)]
+    pub fn merge(other: &Self) -> Self {
+        todo!()
+    }
+}
+
+impl fmt::Display for MediaQuery {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(modifier) = &self.modifier {
+            f.write_str(modifier)?;
+        }
+        if let Some(media_type) = &self.media_type {
+            f.write_str(media_type)?;
+            if !&self.features.is_empty() {
+                f.write_str(" and ")?;
+            }
+        }
+        f.write_str(&self.features.join(" and "))
+    }
+}
diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs
index 5c42d66..e12a576 100644
--- a/src/atrule/mod.rs
+++ b/src/atrule/mod.rs
@@ -1,7 +1,12 @@
 pub(crate) use function::Function;
 pub(crate) use kind::AtRuleKind;
 pub(crate) use mixin::Mixin;
+pub(crate) use supports::SupportsRule;
+pub(crate) use unknown::UnknownAtRule;
 
 mod function;
 mod kind;
+pub mod media;
 mod mixin;
+mod supports;
+mod unknown;
diff --git a/src/atrule/supports.rs b/src/atrule/supports.rs
new file mode 100644
index 0000000..145c3dd
--- /dev/null
+++ b/src/atrule/supports.rs
@@ -0,0 +1,7 @@
+use crate::parse::Stmt;
+
+#[derive(Debug, Clone)]
+pub(crate) struct SupportsRule {
+    pub params: String,
+    pub body: Vec<Stmt>,
+}
diff --git a/src/atrule/unknown.rs b/src/atrule/unknown.rs
new file mode 100644
index 0000000..2d0df7d
--- /dev/null
+++ b/src/atrule/unknown.rs
@@ -0,0 +1,9 @@
+use crate::{parse::Stmt, selector::Selector};
+
+#[derive(Debug, Clone)]
+pub(crate) struct UnknownAtRule {
+    pub name: String,
+    pub super_selector: Selector,
+    pub params: String,
+    pub body: Vec<Stmt>,
+}
diff --git a/src/output.rs b/src/output.rs
index 5b1cdfa..9d17bd2 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -3,7 +3,14 @@ use std::io::Write;
 
 use codemap::CodeMap;
 
-use crate::{error::SassResult, parse::Stmt, selector::Extender, selector::Selector, style::Style};
+use crate::{
+    atrule::{media::MediaRule, SupportsRule, UnknownAtRule},
+    error::SassResult,
+    parse::Stmt,
+    selector::Extender,
+    selector::Selector,
+    style::Style,
+};
 
 #[derive(Debug, Clone)]
 enum Toplevel {
@@ -23,7 +30,7 @@ enum Toplevel {
         body: Vec<Stmt>,
     },
     Newline,
-    Style(Box<Style>),
+    Style(Style),
 }
 
 #[derive(Debug, Clone)]
@@ -92,17 +99,22 @@ impl Css {
                 for rule in body {
                     match rule {
                         Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule, extender)?),
-                        Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(*s)?,
+                        Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(s)?,
                         Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
-                        Stmt::Media { query, body, .. } => {
+                        Stmt::Media(m) => {
+                            let MediaRule { query, body, .. } = *m;
                             vals.push(Toplevel::Media { query, body })
                         }
-                        Stmt::Supports { params, body, .. } => {
+                        Stmt::Supports(s) => {
+                            let SupportsRule { params, body, .. } = *s;
                             vals.push(Toplevel::Supports { params, body })
                         }
-                        Stmt::UnknownAtRule {
-                            params, body, name, ..
-                        } => vals.push(Toplevel::UnknownAtRule { params, body, name }),
+                        Stmt::UnknownAtRule(u) => {
+                            let UnknownAtRule {
+                                params, body, name, ..
+                            } = *u;
+                            vals.push(Toplevel::UnknownAtRule { params, body, name })
+                        }
                         Stmt::Return(..) => unreachable!(),
                         Stmt::AtRoot { body } => body
                             .into_iter()
@@ -114,11 +126,20 @@ impl Css {
             }
             Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
             Stmt::Style(s) => vec![Toplevel::Style(s)],
-            Stmt::Media { query, body, .. } => vec![Toplevel::Media { query, body }],
-            Stmt::Supports { params, body, .. } => vec![Toplevel::Supports { params, body }],
-            Stmt::UnknownAtRule {
-                params, name, body, ..
-            } => vec![Toplevel::UnknownAtRule { params, name, body }],
+            Stmt::Media(m) => {
+                let MediaRule { query, body, .. } = *m;
+                vec![Toplevel::Media { query, body }]
+            }
+            Stmt::Supports(s) => {
+                let SupportsRule { params, body, .. } = *s;
+                vec![Toplevel::Supports { params, body }]
+            }
+            Stmt::UnknownAtRule(u) => {
+                let UnknownAtRule {
+                    params, body, name, ..
+                } = *u;
+                vec![Toplevel::UnknownAtRule { params, name, body }]
+            }
             Stmt::Return(..) => unreachable!("@return: {:?}", stmt),
             Stmt::AtRoot { .. } => unreachable!("@at-root: {:?}", stmt),
         })
diff --git a/src/parse/function.rs b/src/parse/function.rs
index 5b95614..651bd4b 100644
--- a/src/parse/function.rs
+++ b/src/parse/function.rs
@@ -82,13 +82,13 @@ impl<'a> Parser<'a> {
         Ok(())
     }
 
-    pub(super) fn parse_return(&mut self) -> SassResult<Value> {
+    pub(super) fn parse_return(&mut self) -> SassResult<Box<Value>> {
         let toks = read_until_semicolon_or_closing_curly_brace(self.toks)?;
         let v = self.parse_value_from_vec(toks)?;
         if let Some(Token { kind: ';', .. }) = self.toks.peek() {
             self.toks.next();
         }
-        Ok(v.node)
+        Ok(Box::new(v.node))
     }
 
     pub fn eval_function(&mut self, mut function: Function, args: CallArgs) -> SassResult<Value> {
@@ -117,7 +117,7 @@ impl<'a> Parser<'a> {
             .pop()
             .ok_or(("Function finished without @return.", self.span_before))?
         {
-            Stmt::Return(v) => Ok(v),
+            Stmt::Return(v) => Ok(*v),
             _ => todo!("should be unreachable"),
         }
     }
diff --git a/src/parse/media.rs b/src/parse/media.rs
index e71b7d6..2a5e761 100644
--- a/src/parse/media.rs
+++ b/src/parse/media.rs
@@ -1,5 +1,3 @@
-use std::fmt;
-
 use crate::{
     error::SassResult,
     utils::{is_name_start, peek_ident_no_interpolation, read_until_closing_paren},
@@ -8,65 +6,6 @@ use crate::{
 
 use super::Parser;
 
-#[derive(Debug, Eq, PartialEq, Clone)]
-pub(super) struct MediaQuery {
-    /// The modifier, probably either "not" or "only".
-    ///
-    /// This may be `None` if no modifier is in use.
-    modifier: Option<String>,
-
-    /// The media type, for example "screen" or "print".
-    ///
-    /// This may be `None`. If so, `self.features` will not be empty.
-    media_type: Option<String>,
-
-    /// Feature queries, including parentheses.
-    features: Vec<String>,
-}
-
-#[allow(dead_code)]
-impl MediaQuery {
-    pub fn is_condition(&self) -> bool {
-        self.modifier.is_none() && self.media_type.is_none()
-    }
-
-    pub fn matches_all_types(&self) -> bool {
-        self.media_type.is_none()
-            || self
-                .media_type
-                .as_ref()
-                .map_or(false, |v| v.to_ascii_lowercase() == "all")
-    }
-
-    pub fn condition(features: Vec<String>) -> Self {
-        Self {
-            modifier: None,
-            media_type: None,
-            features,
-        }
-    }
-
-    #[allow(dead_code, unused_variables)]
-    pub fn merge(other: &Self) -> Self {
-        todo!()
-    }
-}
-
-impl fmt::Display for MediaQuery {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if let Some(modifier) = &self.modifier {
-            f.write_str(modifier)?;
-        }
-        if let Some(media_type) = &self.media_type {
-            f.write_str(media_type)?;
-            if !&self.features.is_empty() {
-                f.write_str(" and ")?;
-            }
-        }
-        f.write_str(&self.features.join(" and "))
-    }
-}
-
 impl<'a> Parser<'a> {
     pub fn scan_identifier(&mut self, ident: &str) -> SassResult<bool> {
         let peeked_identifier =
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 01feb12..273da76 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -5,7 +5,7 @@ use num_traits::cast::ToPrimitive;
 use peekmore::{PeekMore, PeekMoreIterator};
 
 use crate::{
-    atrule::AtRuleKind,
+    atrule::{media::MediaRule, AtRuleKind, SupportsRule, UnknownAtRule},
     common::{Brackets, ListSeparator},
     error::SassResult,
     scope::Scope,
@@ -46,27 +46,15 @@ pub(crate) enum Stmt {
         selector: ExtendedSelector,
         body: Vec<Self>,
     },
-    Style(Box<Style>),
-    Media {
-        super_selector: Selector,
-        query: String,
-        body: Vec<Stmt>,
-    },
-    UnknownAtRule {
-        name: String,
-        super_selector: Selector,
-        params: String,
-        body: Vec<Stmt>,
-    },
-    Supports {
-        params: String,
-        body: Vec<Stmt>,
-    },
+    Style(Style),
+    Media(Box<MediaRule>),
+    UnknownAtRule(Box<UnknownAtRule>),
+    Supports(Box<SupportsRule>),
     AtRoot {
         body: Vec<Stmt>,
     },
     Comment(String),
-    Return(Value),
+    Return(Box<Value>),
 }
 
 /// We could use a generic for the toks, but it makes the API
@@ -252,12 +240,12 @@ impl<'a> Parser<'a> {
                         let styles = if let Some(value) = value {
                             vec![Style {
                                 property,
-                                value: *value,
+                                value: value,
                             }]
                         } else {
                             self.parse_style_group(property)?
                         };
-                        stmts.extend(styles.into_iter().map(Box::new).map(Stmt::Style));
+                        stmts.extend(styles.into_iter().map(Stmt::Style));
                     }
                     SelectorOrStyle::Selector(init) => {
                         let at_root = self.at_root;
@@ -1003,12 +991,12 @@ impl<'a> Parser<'a> {
         self.whitespace();
         if let Some(Token { kind: ';', .. }) | None = self.toks.peek() {
             self.toks.next();
-            return Ok(Stmt::UnknownAtRule {
+            return Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule {
                 name,
                 super_selector: Selector::new(self.span_before),
                 params: String::new(),
                 body: Vec::new(),
-            });
+            })));
         }
         while let Some(tok) = self.toks.next() {
             match tok.kind {
@@ -1053,12 +1041,12 @@ impl<'a> Parser<'a> {
 
         body.append(&mut rules);
 
-        Ok(Stmt::UnknownAtRule {
+        Ok(Stmt::UnknownAtRule(Box::new(UnknownAtRule {
             name,
             super_selector: Selector::new(self.span_before),
             params: params.trim().to_owned(),
             body,
-        })
+        })))
     }
 
     fn parse_media(&mut self) -> SassResult<Stmt> {
@@ -1107,11 +1095,11 @@ impl<'a> Parser<'a> {
 
         body.append(&mut rules);
 
-        Ok(Stmt::Media {
+        Ok(Stmt::Media(Box::new(MediaRule {
             super_selector: Selector::new(self.span_before),
             query,
             body,
-        })
+        })))
     }
 
     fn parse_at_root(&mut self) -> SassResult<Vec<Stmt>> {
@@ -1292,10 +1280,10 @@ impl<'a> Parser<'a> {
 
         body.append(&mut rules);
 
-        Ok(Stmt::Supports {
+        Ok(Stmt::Supports(Box::new(SupportsRule {
             params: params.trim().to_owned(),
             body,
-        })
+        })))
     }
 
     #[allow(dead_code, clippy::unused_self)]
diff --git a/src/parse/style.rs b/src/parse/style.rs
index 18dd5e1..85c28d1 100644
--- a/src/parse/style.rs
+++ b/src/parse/style.rs
@@ -198,7 +198,7 @@ impl<'a> Parser<'a> {
                                 continue;
                             }
                         }
-                        let value = self.parse_style_value()?;
+                        let value = Box::new(self.parse_style_value()?);
                         match self.toks.peek() {
                             Some(Token { kind: '}', .. }) => {
                                 styles.push(Style { property, value });
@@ -246,7 +246,7 @@ impl<'a> Parser<'a> {
                         '{' => {
                             let mut v = vec![Style {
                                 property: super_property.clone(),
-                                value,
+                                value: Box::new(value),
                             }];
                             v.append(&mut self.parse_style_group(super_property)?);
                             return Ok(v);
@@ -255,7 +255,7 @@ impl<'a> Parser<'a> {
                     }
                     return Ok(vec![Style {
                         property: super_property,
-                        value,
+                        value: Box::new(value),
                     }]);
                 }
             }
diff --git a/src/style.rs b/src/style.rs
index bda0e42..1191bf7 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -6,7 +6,7 @@ use crate::{error::SassResult, value::Value};
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub(crate) struct Style {
     pub property: String,
-    pub value: Spanned<Value>,
+    pub value: Box<Spanned<Value>>,
 }
 
 impl Style {
@@ -21,10 +21,10 @@ impl Style {
     pub(crate) fn eval(self) -> SassResult<Self> {
         Ok(Style {
             property: self.property,
-            value: Spanned {
+            value: Box::new(Spanned {
                 span: self.value.span,
                 node: self.value.node.eval(self.value.span)?.node,
-            },
+            }),
         })
     }
 }