From 9503b3288bd60ff478092e2f301c1e37f14ac3cc Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 25 Jan 2020 09:58:53 -0500 Subject: [PATCH] Initial implementation of Value --- src/lib.rs | 22 ++++ src/style.rs | 54 +--------- src/value.rs | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 324 insertions(+), 52 deletions(-) create mode 100644 src/value.rs diff --git a/src/lib.rs b/src/lib.rs index c2fdce7..6f5afcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,7 @@ mod selector; mod style; mod units; mod utils; +mod value; pub type SassResult = Result; @@ -1291,3 +1292,24 @@ mod test_imports { test_import!(finds_name_scss, "@import \"finds_name_scss\";\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "finds_name_scss.scss"("$a: red;")); test_import!(finds_underscore_name_scss, "@import \"finds_underscore_name_scss\";\na {\n color: $a;\n}" => "a {\n color: red;\n}\n" | "_finds_underscore_name_scss.scss"("$a: red;")); } + +#[cfg(test)] +mod test_values { + use super::*; + test!(comma_list_ident, "a {\n color: red, white, blue;\n}\n"); + test!(space_list_ident, "a {\n color: red white blue;\n}\n"); + test!(comma_list_number, "a {\n color: 1, 2, 3;\n}\n"); + test!(space_list_number, "a {\n color: 1 2 3;\n}\n"); + test!(comma_space_list_number, "a {\n color: 1 1, 2 2, 3 3;\n}\n"); + test!( + whitespace_space_list_number, + "a {\n color: 1 2 3 ;\n}\n", + "a {\n color: 1 2 3;\n}\n" + ); + test!( + whitespace_comma_list_number, + "a {\n color: 1 , 2 , 3 ;\n}\n", + "a {\n color: 1, 2, 3;\n}\n" + ); + test!(number, "a {\n color: 1;\n}\n"); +} diff --git a/src/style.rs b/src/style.rs index eb0968d..6ac8ee5 100644 --- a/src/style.rs +++ b/src/style.rs @@ -10,7 +10,7 @@ use std::vec::IntoIter; #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Style { property: String, - value: String, + value: Value, } impl Display for Style { @@ -59,58 +59,8 @@ impl<'a> StyleParser<'a> { devour_whitespace_or_comment(&mut self.tokens); - let mut value = String::new(); + let value = Value::from_tokens(&mut self.tokens, self.scope).unwrap(); - // read styles - while let Some(tok) = self.tokens.next() { - match &tok.kind { - TokenKind::Whitespace(_) => { - while let Some(Token { kind, .. }) = self.tokens.peek() { - match kind { - TokenKind::Whitespace(_) | TokenKind::MultilineComment(_) => { - self.tokens.next(); - continue; - } - TokenKind::Ident(ref s) => { - if s == &String::from("-") { - self.tokens.next(); - value.push('-'); - self.devour_whitespace_or_comment(); - break; - } - } - TokenKind::Interpolation => { - self.tokens.next(); - value.push_str( - &eat_interpolation(&mut self.tokens, self.scope) - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ); - break; - } - _ => {} - } - value.push(' '); - break; - } - } - TokenKind::Variable(ref v) => value.push_str( - &deref_variable(v, self.scope) - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ), - TokenKind::MultilineComment(_) => continue, - TokenKind::Interpolation => value.push_str( - &eat_interpolation(&mut self.tokens, self.scope) - .iter() - .map(|x| x.kind.to_string()) - .collect::(), - ), - _ => value.push_str(&tok.kind.to_string()), - } - } Style { property, value } } } diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..74534ec --- /dev/null +++ b/src/value.rs @@ -0,0 +1,300 @@ +#![allow(dead_code, unused_variables)] +use std::fmt::{self, Display}; +use std::iter::{Iterator, Peekable}; +use std::ops::{Add, Div, Mul, Rem, Sub}; + +use crate::color::Color; +use crate::common::{Keyword, Op, Scope, Symbol}; +use crate::units::Unit; +use crate::utils::{deref_variable, devour_whitespace_or_comment, eat_interpolation}; +use crate::{Token, TokenKind}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Dimension { + val: u64, +} + +impl Add for Dimension { + type Output = Self; + + fn add(self, other: Self) -> Self { + Dimension { + val: self.val + other.val, + } + } +} + +impl Display for Dimension { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.val) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ListSeparator { + Space, + Comma, +} + +impl ListSeparator { + pub fn as_str(self) -> &'static str { + match self { + Self::Space => " ", + Self::Comma => ", ", + } + } + + pub fn name(self) -> &'static str { + match self { + Self::Space => "space", + Self::Comma => "comma", + } + } +} + +impl Display for ListSeparator { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Space => write!(f, " "), + Self::Comma => write!(f, ", "), + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum QuoteKind { + Single, + Double, + None, +} + +impl QuoteKind { + pub fn as_str(self) -> &'static str { + match self { + Self::Single => "'", + Self::Double => "\"", + Self::None => "", + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) enum Value { + Important, + True, + False, + Null, + Dimension(Dimension, Unit), + List(Vec, ListSeparator), + Color(Color), + Paren(Box), + Ident(String, QuoteKind), +} + +impl Add for Value { + type Output = Self; + + fn add(self, other: Self) -> Self { + match self { + Self::Important => todo!(), + Self::True => todo!(), + Self::False => todo!(), + Self::Null => todo!(), + Self::Dimension(num, unit) => match other { + Self::Dimension(num2, unit2) => Value::Dimension(num + num2, unit), + _ => todo!(), + }, + Self::List(..) => todo!(), + Self::Color(..) => todo!(), + Self::Paren(..) => todo!(), + Self::Ident(..) => todo!(), + } + } +} + +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Important => write!(f, "!important"), + Self::Dimension(num, unit) => write!(f, "{}{}", num, unit), + Self::List(vals, sep) => write!( + f, + "{}", + vals.iter() + .map(|x| x.to_string()) + .collect::>() + .join(sep.as_str()) + ), + Self::Color(c) => write!(f, "{}", c), + Self::Paren(val) => write!(f, "({})", val), + Self::Ident(val, kind) => write!(f, "{}{}{}", kind.as_str(), val, kind.as_str()), + Self::True => write!(f, "true"), + Self::False => write!(f, "false"), + Self::Null => write!(f, "null"), + } + } +} + +impl Value { + pub fn is_true(&self) -> bool { + todo!() + } + + pub fn unquote(&mut self) -> &mut Self { + todo!() + } + + pub fn from_tokens>( + toks: &mut Peekable, + scope: &Scope, + ) -> Option { + let left = Self::_from_tokens(toks, scope)?; + let whitespace = devour_whitespace_or_comment(toks); + let next = match toks.peek() { + Some(x) => x, + None => return Some(left), + }; + match next.kind { + TokenKind::Symbol(Symbol::Comma) => { + toks.next(); + devour_whitespace_or_comment(toks); + let right = match Self::from_tokens(toks, scope) { + Some(x) => x, + None => return Some(left), + }; + Some(Value::List(vec![left, right], ListSeparator::Comma)) + } + TokenKind::Symbol(Symbol::Plus) => { + toks.next(); + devour_whitespace_or_comment(toks); + let right = match Self::from_tokens(toks, scope) { + Some(x) => x, + None => return Some(left), + }; + Some(left + right) + } + _ if whitespace => { + devour_whitespace_or_comment(toks); + let right = match Self::from_tokens(toks, scope) { + Some(x) => x, + None => return Some(left), + }; + Some(Value::List(vec![left, right], ListSeparator::Space)) + } + _ => { + dbg!(&next.kind); + todo!("unimplemented token in value") + } + } + } + + fn _from_tokens>( + toks: &mut Peekable, + scope: &Scope, + ) -> Option { + let kind = if let Some(tok) = toks.next() { + tok.kind + } else { + return None; + }; + match kind { + TokenKind::Number(val) => { + let unit = if let Some(tok) = toks.peek() { + match tok.kind.clone() { + TokenKind::Ident(i) => { + toks.next(); + Unit::from(&i) + } + TokenKind::Symbol(Symbol::Percent) => { + toks.next(); + Unit::Percent + } + _ => Unit::None, + } + } else { + Unit::None + }; + Some(Value::Dimension( + Dimension { + val: val.parse().unwrap(), + }, + unit, + )) + } + TokenKind::Ident(mut s) => { + while let Some(tok) = toks.peek() { + match tok.kind.clone() { + TokenKind::Interpolation => { + toks.next(); + s.push_str( + &eat_interpolation(toks, scope) + .iter() + .map(|x| x.kind.to_string()) + .collect::(), + ) + } + TokenKind::Ident(ref i) => { + toks.next(); + s.push_str(i) + } + _ => break, + } + } + Some(Value::Ident(s, QuoteKind::None)) + } + TokenKind::Symbol(Symbol::DoubleQuote) => { + let mut s = String::new(); + while let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Symbol(Symbol::DoubleQuote) => break, + _ => {} + } + s.push_str(&tok.kind.to_string()); + } + Some(Value::Ident(s, QuoteKind::Double)) + } + TokenKind::Symbol(Symbol::SingleQuote) => { + let mut s = String::new(); + while let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Symbol(Symbol::SingleQuote) => break, + _ => {} + } + s.push_str(&tok.kind.to_string()); + } + Some(Value::Ident(s, QuoteKind::Single)) + } + TokenKind::Variable(ref v) => { + Value::from_tokens(&mut deref_variable(v, scope).into_iter().peekable(), scope) + } + TokenKind::Interpolation => { + let mut s = eat_interpolation(toks, scope) + .iter() + .map(|x| x.kind.to_string()) + .collect::(); + while let Some(tok) = toks.peek() { + match tok.kind.clone() { + TokenKind::Interpolation => { + toks.next(); + s.push_str( + &eat_interpolation(toks, scope) + .iter() + .map(|x| x.kind.to_string()) + .collect::(), + ) + } + TokenKind::Ident(ref i) => { + toks.next(); + s.push_str(i) + } + _ => break, + } + } + Some(Value::Ident(s, QuoteKind::None)) + } + TokenKind::Keyword(Keyword::Important) => Some(Value::Important), + _ => None, + } + } +}