From f4278a29b7a90b2a6a5d6911fc9ff870a369f0d4 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 4 Jan 2020 22:55:04 -0500 Subject: [PATCH] Implement lexing, basic parsing, and pretty printing --- src/color.rs | 1 + src/common.rs | 1052 +++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 1 + src/format.rs | 159 +++++++ src/lexer.rs | 305 ++++++++++++++ src/main.rs | 303 ++++++++++++++ src/selector.rs | 247 +++++++++++ src/units.rs | 261 ++++++++++++ 8 files changed, 2329 insertions(+) create mode 100644 src/color.rs create mode 100644 src/common.rs create mode 100644 src/error.rs create mode 100644 src/format.rs create mode 100644 src/lexer.rs create mode 100644 src/main.rs create mode 100644 src/selector.rs create mode 100644 src/units.rs diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/color.rs @@ -0,0 +1 @@ + diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..6ba4399 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,1052 @@ +use std::convert::TryFrom; +use std::fmt::{self, Display}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Symbol { + /// . + Period, + /// # + Hash, + /// @ + At, + /// $ + Dollar, + /// ( + OpenParen, + /// ) + CloseParen, + /// { + OpenBrace, + /// } + CloseBrace, + /// [ + OpenBracket, + /// ] + CloseBracket, + /// , + Comma, + /// + + Plus, + /// - + Minus, + /// * + Mul, + /// / + Div, + /// : + Colon, + /// ; + SemiColon, + /// ~ + Tilde, + /// > + Gt, + /// < + Lt, + /// ^ + Xor, + /// = + Equal, + /// | + BitOr, + /// & + BitAnd, + /// % + Percent, + /// " + DoubleQuote, + /// ' + SingleQuote, +} + +impl Display for Symbol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Symbol::Period => write!(f, "."), + Symbol::Hash => write!(f, "#"), + Symbol::At => write!(f, "@"), + Symbol::Dollar => write!(f, "$"), + Symbol::OpenParen => write!(f, "("), + Symbol::CloseParen => write!(f, "),"), + Symbol::OpenBrace => write!(f, "{{"), + Symbol::CloseBrace => write!(f, "}}"), + Symbol::OpenBracket => write!(f, "["), + Symbol::CloseBracket => write!(f, "]"), + Symbol::Comma => write!(f, ","), + Symbol::Plus => write!(f, "+"), + Symbol::Minus => write!(f, "-"), + Symbol::Mul => write!(f, "*"), + Symbol::Div => write!(f, "/"), + Symbol::Colon => write!(f, ":"), + Symbol::SemiColon => write!(f, ";"), + Symbol::Tilde => write!(f, "~"), + Symbol::Gt => write!(f, ">"), + Symbol::Lt => write!(f, "<"), + Symbol::Xor => write!(f, "^"), + Symbol::Equal => write!(f, "="), + Symbol::BitOr => write!(f, "|"), + Symbol::BitAnd => write!(f, "&"), + Symbol::Percent => write!(f, "%"), + Symbol::DoubleQuote => write!(f, "\""), + Symbol::SingleQuote => write!(f, "'"), + } + } +} + +impl TryFrom for Symbol { + type Error = &'static str; + + fn try_from(c: char) -> Result { + match c { + '.' => Ok(Symbol::Period), + '#' => Ok(Symbol::Hash), + '@' => Ok(Symbol::At), + '$' => Ok(Symbol::Dollar), + '(' => Ok(Symbol::OpenParen), + ')' => Ok(Symbol::CloseParen), + '{' => Ok(Symbol::OpenBrace), + '}' => Ok(Symbol::CloseBrace), + '[' => Ok(Symbol::OpenBracket), + ']' => Ok(Symbol::CloseBracket), + ',' => Ok(Symbol::Comma), + '+' => Ok(Symbol::Plus), + '-' => Ok(Symbol::Minus), + '*' => Ok(Symbol::Mul), + '/' => Ok(Symbol::Div), + ':' => Ok(Symbol::Colon), + ';' => Ok(Symbol::SemiColon), + '~' => Ok(Symbol::Tilde), + '>' => Ok(Symbol::Gt), + '<' => Ok(Symbol::Lt), + '^' => Ok(Symbol::Xor), + '=' => Ok(Symbol::Equal), + '|' => Ok(Symbol::BitOr), + '&' => Ok(Symbol::BitAnd), + '%' => Ok(Symbol::Percent), + '"' => Ok(Symbol::DoubleQuote), + '\'' => Ok(Symbol::SingleQuote), + _ => Err("invalid symbol"), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MediaQuery {} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum AtRule { + // SASS specific @rules + /// Loads mixins, functions, and variables from other Sass stylesheets, and combines CSS from multiple stylesheets together + Use, + /// Loads a Sass stylesheet and makes its mixins, functions, and variables available when your stylesheet is loaded with the `@use` rule + Forward, + /// Extends the CSS at-rule to load styles, mixins, functions, and variables from other stylesheets + Import, + Mixin, + Include, + /// Defines custom functions that can be used in SassScript expressions + Function, + /// Allows selectors to inherit styles from one another + Extend, + /// Puts styles within it at the root of the CSS document + AtRoot, + /// Causes compilation to fail with an error message + Error, + /// Prints a warning without stopping compilation entirely + Warn, + /// Prints a message for debugging purposes + Debug, + If, + Each, + For, + While, + + // CSS @rules + /// Defines the character set used by the style sheet + Charset, + /// Tells the CSS engine that all its content must be considered prefixed with an XML namespace + Namespace, + /// A conditional group rule that will apply its content if the device meets the criteria of the condition defined using a media query + Media, + /// A conditional group rule that will apply its content if the browser meets the criteria of the given condition + Supports, + /// Describes the aspect of layout changes that will be applied when printing the document + Page, + /// Describes the aspect of an external font to be downloaded + FontFace, + /// Describes the aspect of intermediate steps in a CSS animation sequence + Keyframes, + + // @rules related to @font-feature-values + FontFeatureValues, + Swash, + Ornaments, + Annotation, + Stylistic, + Styleset, + CharacterVariant, + + // Experimental CSS @rules + /// Describes the aspects of the viewport for small screen devices + /// + /// Currently at the Working Draft stage + Viewport, + /// A conditional group rule that will apply its content if the document in which the style sheet is applied meets the criteria of the given condition + /// + /// Deferred to Level 4 of CSS Spec + Document, + /// Defines specific counter styles that are not part of the predefined set of styles + /// + /// At the Candidate Recommendation stage + CounterStyle, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Whitespace { + Space, + Tab, + Newline, + CarriageReturn, +} + +impl TryFrom for Whitespace { + type Error = &'static str; + + fn try_from(c: char) -> Result { + match c { + ' ' => Ok(Whitespace::Space), + '\t' => Ok(Whitespace::Tab), + '\n' => Ok(Whitespace::Newline), + '\r' => Ok(Whitespace::CarriageReturn), + _ => Err("invalid whitespace"), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Keyword { + Important, + Infinity, + NaN, + Auto, + Inherit, + Initial, + Unset, +} + +impl Display for Keyword { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Keyword::Important => write!(f, "!important"), + Keyword::Infinity => write!(f, "Infinity"), + Keyword::NaN => write!(f, "NaN"), + Keyword::Auto => write!(f, "auto"), + Keyword::Inherit => write!(f, "inherit"), + Keyword::Initial => write!(f, "initial"), + Keyword::Unset => write!(f, "unset"), + } + } +} + +impl Into<&'static str> for Keyword { + fn into(self) -> &'static str { + match self { + Keyword::Important => "!important", + Keyword::Infinity => "Infinity", + Keyword::NaN => "NaN", + Keyword::Auto => "auto", + Keyword::Inherit => "inherit", + Keyword::Initial => "initial", + Keyword::Unset => "unset", + } + } +} + +impl TryFrom<&str> for Keyword { + type Error = &'static str; + + fn try_from(kw: &str) -> Result { + // todo: case insensitive? + match kw { + "important" => Ok(Keyword::Important), + "infinity" => Ok(Keyword::Infinity), + "nan" => Ok(Keyword::NaN), + "auto" => Ok(Keyword::Auto), + "inherit" => Ok(Keyword::Inherit), + "initial" => Ok(Keyword::Initial), + "unset" => Ok(Keyword::Unset), + _ => Err("invalid keyword"), + } + } +} + +#[derive(Debug)] +pub enum Color { + AliceBlue, // = 0xF0F8FF, + AntiqueWhite, // = 0xFAEBD7, + Aqua, // = 0x00FFFF, + Aquamarine, // = 0x7FFFD4, + Azure, // = 0xF0FFFF, + Beige, // = 0xF5F5DC, + Bisque, // = 0xFFE4C4, + Black, // = 0x000000, + BlanchedAlmond, // = 0xFFEBCD, + Blue, // = 0x0000FF, + BlueViolet, // = 0x8A2BE2, + Brown, // = 0xA52A2A, + BurlyWood, // = 0xDEB887, + CadetBlue, // = 0x5F9EA0, + Chartreuse, // = 0x7FFF00, + Chocolate, // = 0xD2691E, + Coral, // = 0xFF7F50, + CornflowerBlue, // = 0x6495ED, + Cornsilk, // = 0xFFF8DC, + Crimson, // = 0xDC143C, + Cyan, //0x00FFFF + DarkBlue, // = 0x00008B, + DarkCyan, // = 0x008B8B, + DarkGoldenRod, // = 0xB8860B, + DarkGray, // = 0xA9A9A9, + DarkGrey, //0xA9A9A9 + DarkGreen, // = 0x006400, + DarkKhaki, // = 0xBDB76B, + DarkMagenta, // = 0x8B008B, + DarkOliveGreen, // = 0x556B2F, + DarkOrange, // = 0xFF8C00, + DarkOrchid, // = 0x9932CC, + DarkRed, // = 0x8B0000, + DarkSalmon, // = 0xE9967A, + DarkSeaGreen, // = 0x8FBC8F, + DarkSlateBlue, // = 0x483D8B, + DarkSlateGray, // = 0x2F4F4F, + DarkSlateGrey, //0x2F4F4F + DarkTurquoise, // = 0x00CED1, + DarkViolet, // = 0x9400D3, + DeepPink, // = 0xFF1493, + DeepSkyBlue, // = 0x00BFFF, + DimGray, // = 0x696969, + DimGrey, //0x696969 + DodgerBlue, // = 0x1E90FF, + FireBrick, // = 0xB22222, + FloralWhite, // = 0xFFFAF0, + ForestGreen, // = 0x228B22, + Fuchsia, // = 0xFF00FF, + Gainsboro, // = 0xDCDCDC, + GhostWhite, // = 0xF8F8FF, + Gold, // = 0xFFD700, + GoldenRod, // = 0xDAA520, + Gray, // = 0x808080, + Grey, //0x808080 + Green, // = 0x008000, + GreenYellow, // = 0xADFF2F, + HoneyDew, // = 0xF0FFF0, + HotPink, // = 0xFF69B4, + IndianRed, // = 0xCD5C5C, + Indigo, // = 0x4B0082, + Ivory, // = 0xFFFFF0, + Khaki, // = 0xF0E68C, + Lavender, // = 0xE6E6FA, + LavenderBlush, // = 0xFFF0F5, + LawnGreen, // = 0x7CFC00, + LemonChiffon, // = 0xFFFACD, + LightBlue, // = 0xADD8E6, + LightCoral, // = 0xF08080, + LightCyan, // = 0xE0FFFF, + LightGoldenRodYellow, // = 0xFAFAD2, + LightGray, // = 0xD3D3D3, + LightGrey, //0xD3D3D3 + LightGreen, // = 0x90EE90, + LightPink, // = 0xFFB6C1, + LightSalmon, // = 0xFFA07A, + LightSeaGreen, // = 0x20B2AA, + LightSkyBlue, // = 0x87CEFA, + LightSlateGray, // = 0x778899, + LightSlateGrey, //0x778899 + LightSteelBlue, // = 0xB0C4DE, + LightYellow, // = 0xFFFFE0, + Lime, // = 0x00FF00, + LimeGreen, // = 0x32CD32, + Linen, // = 0xFAF0E6, + Magenta, //0xFF00FF + Maroon, // = 0x800000, + MediumAquaMarine, // = 0x66CDAA, + MediumBlue, // = 0x0000CD, + MediumOrchid, // = 0xBA55D3, + MediumPurple, // = 0x9370DB, + MediumSeaGreen, // = 0x3CB371, + MediumSlateBlue, // = 0x7B68EE, + MediumSpringGreen, // = 0x00FA9A, + MediumTurquoise, // = 0x48D1CC, + MediumVioletRed, // = 0xC71585, + MidnightBlue, // = 0x191970, + MintCream, // = 0xF5FFFA, + MistyRose, // = 0xFFE4E1, + Moccasin, // = 0xFFE4B5, + NavajoWhite, // = 0xFFDEAD, + Navy, // = 0x000080, + OldLace, // = 0xFDF5E6, + Olive, // = 0x808000, + OliveDrab, // = 0x6B8E23, + Orange, // = 0xFFA500, + OrangeRed, // = 0xFF4500, + Orchid, // = 0xDA70D6, + PaleGoldenRod, // = 0xEEE8AA, + PaleGreen, // = 0x98FB98, + PaleTurquoise, // = 0xAFEEEE, + PaleVioletRed, // = 0xDB7093, + PapayaWhip, // = 0xFFEFD5, + PeachPuff, // = 0xFFDAB9, + Peru, // = 0xCD853F, + Pink, // = 0xFFC0CB, + Plum, // = 0xDDA0DD, + PowderBlue, // = 0xB0E0E6, + Purple, // = 0x800080, + RebeccaPurple, // = 0x663399, + Red, // = 0xFF0000, + RosyBrown, // = 0xBC8F8F, + RoyalBlue, // = 0x4169E1, + SaddleBrown, // = 0x8B4513, + Salmon, // = 0xFA8072, + SandyBrown, // = 0xF4A460, + SeaGreen, // = 0x2E8B57, + SeaShell, // = 0xFFF5EE, + Sienna, // = 0xA0522D, + Silver, // = 0xC0C0C0, + SkyBlue, // = 0x87CEEB, + SlateBlue, // = 0x6A5ACD, + SlateGray, // = 0x708090, + SlateGrey, //0x708090 + Snow, // = 0xFFFAFA, + SpringGreen, // = 0x00FF7F, + SteelBlue, // = 0x4682B4, + Tan, // = 0xD2B48C, + Teal, // = 0x008080, + Thistle, // = 0xD8BFD8, + Tomato, // = 0xFF6347, + Turquoise, // = 0x40E0D0, + Violet, // = 0xEE82EE, + Wheat, // = 0xF5DEB3, + White, // = 0xFFFFFF, + WhiteSmoke, // = 0xF5F5F5, + Yellow, // = 0xFFFF00, + YellowGreen, // = 0x9ACD32, + Other(u32), +} + +impl TryFrom<&str> for Color { + type Error = &'static str; + + fn try_from(string: &str) -> Result { + match string { + "aliceblue" => Ok(Color::AliceBlue), + "antiquewhite" => Ok(Color::AntiqueWhite), + "aqua" => Ok(Color::Aqua), + "aquamarine" => Ok(Color::Aquamarine), + "azure" => Ok(Color::Azure), + "beige" => Ok(Color::Beige), + "bisque" => Ok(Color::Bisque), + "black" => Ok(Color::Black), + "blanchedalmond" => Ok(Color::BlanchedAlmond), + "blue" => Ok(Color::Blue), + "blueviolet" => Ok(Color::BlueViolet), + "brown" => Ok(Color::Brown), + "burlywood" => Ok(Color::BurlyWood), + "cadetblue" => Ok(Color::CadetBlue), + "chartreuse" => Ok(Color::Chartreuse), + "chocolate" => Ok(Color::Chocolate), + "coral" => Ok(Color::Coral), + "cornflowerblue" => Ok(Color::CornflowerBlue), + "cornsilk" => Ok(Color::Cornsilk), + "crimson" => Ok(Color::Crimson), + "cyan" => Ok(Color::Cyan), + "darkblue" => Ok(Color::DarkBlue), + "darkcyan" => Ok(Color::DarkCyan), + "darkgoldenrod" => Ok(Color::DarkGoldenRod), + "darkgray" => Ok(Color::DarkGray), + "darkgrey" => Ok(Color::DarkGrey), + "darkgreen" => Ok(Color::DarkGreen), + "darkkhaki" => Ok(Color::DarkKhaki), + "darkmagenta" => Ok(Color::DarkMagenta), + "darkolivegreen" => Ok(Color::DarkOliveGreen), + "darkorange" => Ok(Color::DarkOrange), + "darkorchid" => Ok(Color::DarkOrchid), + "darkred" => Ok(Color::DarkRed), + "darksalmon" => Ok(Color::DarkSalmon), + "darkseagreen" => Ok(Color::DarkSeaGreen), + "darkslateblue" => Ok(Color::DarkSlateBlue), + "darkslategray" => Ok(Color::DarkSlateGray), + "darkslategrey" => Ok(Color::DarkSlateGrey), + "darkturquoise" => Ok(Color::DarkTurquoise), + "darkviolet" => Ok(Color::DarkViolet), + "deeppink" => Ok(Color::DeepPink), + "deepskyblue" => Ok(Color::DeepSkyBlue), + "dimgray" => Ok(Color::DimGray), + "dimgrey" => Ok(Color::DimGrey), + "dodgerblue" => Ok(Color::DodgerBlue), + "firebrick" => Ok(Color::FireBrick), + "floralwhite" => Ok(Color::FloralWhite), + "forestgreen" => Ok(Color::ForestGreen), + "fuchsia" => Ok(Color::Fuchsia), + "gainsboro" => Ok(Color::Gainsboro), + "ghostwhite" => Ok(Color::GhostWhite), + "gold" => Ok(Color::Gold), + "goldenrod" => Ok(Color::GoldenRod), + "gray" => Ok(Color::Gray), + "grey" => Ok(Color::Grey), + "green" => Ok(Color::Green), + "greenyellow" => Ok(Color::GreenYellow), + "honeydew" => Ok(Color::HoneyDew), + "hotpink" => Ok(Color::HotPink), + "indianred" => Ok(Color::IndianRed), + "indigo" => Ok(Color::Indigo), + "ivory" => Ok(Color::Ivory), + "khaki" => Ok(Color::Khaki), + "lavender" => Ok(Color::Lavender), + "lavenderblush" => Ok(Color::LavenderBlush), + "lawngreen" => Ok(Color::LawnGreen), + "lemonchiffon" => Ok(Color::LemonChiffon), + "lightblue" => Ok(Color::LightBlue), + "lightcoral" => Ok(Color::LightCoral), + "lightcyan" => Ok(Color::LightCyan), + "lightgoldenrodyellow" => Ok(Color::LightGoldenRodYellow), + "lightgray" => Ok(Color::LightGray), + "lightgrey" => Ok(Color::LightGrey), + "lightgreen" => Ok(Color::LightGreen), + "lightpink" => Ok(Color::LightPink), + "lightsalmon" => Ok(Color::LightSalmon), + "lightseagreen" => Ok(Color::LightSeaGreen), + "lightskyblue" => Ok(Color::LightSkyBlue), + "lightslategray" => Ok(Color::LightSlateGray), + "lightslategrey" => Ok(Color::LightSlateGrey), + "lightsteelblue" => Ok(Color::LightSteelBlue), + "lightyellow" => Ok(Color::LightYellow), + "lime" => Ok(Color::Lime), + "limegreen" => Ok(Color::LimeGreen), + "linen" => Ok(Color::Linen), + "magenta" => Ok(Color::Magenta), + "maroon" => Ok(Color::Maroon), + "mediumaquamarine" => Ok(Color::MediumAquaMarine), + "mediumblue" => Ok(Color::MediumBlue), + "mediumorchid" => Ok(Color::MediumOrchid), + "mediumpurple" => Ok(Color::MediumPurple), + "mediumseagreen" => Ok(Color::MediumSeaGreen), + "mediumslateblue" => Ok(Color::MediumSlateBlue), + "mediumspringgreen" => Ok(Color::MediumSpringGreen), + "mediumturquoise" => Ok(Color::MediumTurquoise), + "mediumvioletred" => Ok(Color::MediumVioletRed), + "midnightblue" => Ok(Color::MidnightBlue), + "mintcream" => Ok(Color::MintCream), + "mistyrose" => Ok(Color::MistyRose), + "moccasin" => Ok(Color::Moccasin), + "navajowhite" => Ok(Color::NavajoWhite), + "navy" => Ok(Color::Navy), + "oldlace" => Ok(Color::OldLace), + "olive" => Ok(Color::Olive), + "olivedrab" => Ok(Color::OliveDrab), + "orange" => Ok(Color::Orange), + "orangered" => Ok(Color::OrangeRed), + "orchid" => Ok(Color::Orchid), + "palegoldenrod" => Ok(Color::PaleGoldenRod), + "palegreen" => Ok(Color::PaleGreen), + "paleturquoise" => Ok(Color::PaleTurquoise), + "palevioletred" => Ok(Color::PaleVioletRed), + "papayawhip" => Ok(Color::PapayaWhip), + "peachpuff" => Ok(Color::PeachPuff), + "peru" => Ok(Color::Peru), + "pink" => Ok(Color::Pink), + "plum" => Ok(Color::Plum), + "powderblue" => Ok(Color::PowderBlue), + "purple" => Ok(Color::Purple), + "rebeccapurple" => Ok(Color::RebeccaPurple), + "red" => Ok(Color::Red), + "rosybrown" => Ok(Color::RosyBrown), + "royalblue" => Ok(Color::RoyalBlue), + "saddlebrown" => Ok(Color::SaddleBrown), + "salmon" => Ok(Color::Salmon), + "sandybrown" => Ok(Color::SandyBrown), + "seagreen" => Ok(Color::SeaGreen), + "seashell" => Ok(Color::SeaShell), + "sienna" => Ok(Color::Sienna), + "silver" => Ok(Color::Silver), + "skyblue" => Ok(Color::SkyBlue), + "slateblue" => Ok(Color::SlateBlue), + "slategray" => Ok(Color::SlateGray), + "slategrey" => Ok(Color::SlateGrey), + "snow" => Ok(Color::Snow), + "springgreen" => Ok(Color::SpringGreen), + "steelblue" => Ok(Color::SteelBlue), + "tan" => Ok(Color::Tan), + "teal" => Ok(Color::Teal), + "thistle" => Ok(Color::Thistle), + "tomato" => Ok(Color::Tomato), + "turquoise" => Ok(Color::Turquoise), + "violet" => Ok(Color::Violet), + "wheat" => Ok(Color::Wheat), + "white" => Ok(Color::White), + "whitesmoke" => Ok(Color::WhiteSmoke), + "yellow" => Ok(Color::Yellow), + "yellowgreen" => Ok(Color::YellowGreen), + _ => Err("invalid color"), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Pos { + line: u32, + column: u32, +} + +impl Pos { + pub const fn new() -> Self { + Pos { line: 0, column: 0 } + } + + pub const fn line(self) -> u32 { + self.line + } + + pub const fn column(self) -> u32 { + self.column + } + + pub fn newline(&mut self) { + self.line += 1; + self.column = 0; + } + + pub fn next_char(&mut self) { + self.column += 1; + } + + pub fn chars(&mut self, num: u32) { + self.column += num; + } +} + +#[derive(Debug)] +pub enum Property { + AlignContent, + AlignItems, + AlignSelf, + All, + Animation, + AnimationDelay, + AnimationDirection, + AnimationDuration, + AnimationFillMode, + AnimationIterationCount, + AnimationName, + AnimationPlayState, + AnimationTimingFunction, + BackfaceVisibility, + Background, + BackgroundAttachment, + BackgroundBlendMode, + BackgroundClip, + BackgroundColor, + BackgroundImage, + BackgroundOrigin, + BackgroundPosition, + BackgroundRepeat, + BackgroundSize, + Border, + BorderBottom, + BorderBottomColor, + BorderBottomLeftRadius, + BorderBottomRightRadius, + BorderBottomStyle, + BorderBottomWidth, + BorderCollapse, + BorderColor, + BorderImage, + BorderImageOutset, + BorderImageRepeat, + BorderImageSlice, + BorderImageSource, + BorderImageWidth, + BorderLeft, + BorderLeftColor, + BorderLeftStyle, + BorderLeftWidth, + BorderRadius, + BorderRight, + BorderRightColor, + BorderRightStyle, + BorderRightWidth, + BorderSpacing, + BorderStyle, + BorderTop, + BorderTopColor, + BorderTopLeftRadius, + BorderTopRightRadius, + BorderTopStyle, + BorderTopWidth, + BorderWidth, + Bottom, + BoxDecorationBreak, + BoxShadow, + BoxSizing, + CaptionSide, + CaretColor, + Charset, + Clear, + Clip, + Color, + ColumnCount, + ColumnFill, + ColumnGap, + ColumnRule, + ColumnRuleColor, + ColumnRuleStyle, + ColumnRuleWidth, + ColumnSpan, + ColumnWidth, + Columns, + Content, + CounterIncrement, + CounterReset, + Cursor, + Direction, + Display, + EmptyCells, + Filter, + Flex, + FlexBasis, + FlexDirection, + FlexFlow, + FlexGrow, + FlexShrink, + FlexWrap, + Float, + Font, + FontFace, + FontFamily, + FontKerning, + FontSize, + FontSizeAdjust, + FontStretch, + FontStyle, + FontVariant, + FontWeight, + Grid, + GridArea, + GridAutoColumns, + GridAutoFlow, + GridAutoRows, + GridColumn, + GridColumnEnd, + GridColumnGap, + GridColumnStart, + GridGap, + GridRow, + GridRowEnd, + GridRowGap, + GridRowStart, + GridTemplate, + GridTemplateAreas, + GridTemplateColumns, + GridTemplateRows, + HangingPunctuation, + Height, + Hyphens, + Import, + Isolation, + JustifyContent, + Keyframes, + Left, + LetterSpacing, + LineHeight, + ListStyle, + ListStyleImage, + ListStylePosition, + ListStyleType, + Margin, + MarginBottom, + MarginLeft, + MarginRight, + MarginTop, + MaxHeight, + MaxWidth, + Media, + MinHeight, + MinWidth, + MixBlendMode, + ObjectFit, + ObjectPosition, + Opacity, + Order, + Outline, + OutlineColor, + OutlineOffset, + OutlineStyle, + OutlineWidth, + Overflow, + OverflowX, + OverflowY, + Padding, + PaddingBottom, + PaddingLeft, + PaddingRight, + PaddingTop, + PageBreakAfter, + PageBreakBefore, + PageBreakInside, + Perspective, + PerspectiveOrigin, + PointerEvents, + Position, + Quotes, + Resize, + Right, + ScrollBehavior, + TabSize, + TableLayout, + TextAlign, + TextAlignLast, + TextDecoration, + TextDecorationColor, + TextDecorationLine, + TextDecorationStyle, + TextIndent, + TextJustify, + TextOverflow, + TextShadow, + TextTransform, + Top, + Transform, + TransformOrigin, + TransformStyle, + Transition, + TransitionDelay, + TransitionDuration, + TransitionProperty, + TransitionTimingFunction, + UnicodeBidi, + UserSelect, + VerticalAlign, + Visibility, + WhiteSpace, + Width, + WordBreak, + WordSpacing, + WordWrap, + WritingMode, + ZIndex, +} + +impl TryFrom<&str> for Property { + type Error = &'static str; + fn try_from(string: &str) -> Result { + match string { + "align-content" => Ok(Property::AlignContent), + "align-items" => Ok(Property::AlignItems), + "align-self" => Ok(Property::AlignSelf), + "all" => Ok(Property::All), + "animation" => Ok(Property::Animation), + "animation-delay" => Ok(Property::AnimationDelay), + "animation-direction" => Ok(Property::AnimationDirection), + "animation-duration" => Ok(Property::AnimationDuration), + "animation-fill-mode" => Ok(Property::AnimationFillMode), + "animation-iteration-count" => Ok(Property::AnimationIterationCount), + "animation-name" => Ok(Property::AnimationName), + "animation-play-state" => Ok(Property::AnimationPlayState), + "animation-timing-function" => Ok(Property::AnimationTimingFunction), + "backface-visibility" => Ok(Property::BackfaceVisibility), + "background" => Ok(Property::Background), + "background-attachment" => Ok(Property::BackgroundAttachment), + "background-blend-mode" => Ok(Property::BackgroundBlendMode), + "background-clip" => Ok(Property::BackgroundClip), + "background-color" => Ok(Property::BackgroundColor), + "background-image" => Ok(Property::BackgroundImage), + "background-origin" => Ok(Property::BackgroundOrigin), + "background-position" => Ok(Property::BackgroundPosition), + "background-repeat" => Ok(Property::BackgroundRepeat), + "background-size" => Ok(Property::BackgroundSize), + "border" => Ok(Property::Border), + "border-bottom" => Ok(Property::BorderBottom), + "border-bottom-color" => Ok(Property::BorderBottomColor), + "border-bottom-left-radius" => Ok(Property::BorderBottomLeftRadius), + "border-bottom-right-radius" => Ok(Property::BorderBottomRightRadius), + "border-bottom-style" => Ok(Property::BorderBottomStyle), + "border-bottom-width" => Ok(Property::BorderBottomWidth), + "border-collapse" => Ok(Property::BorderCollapse), + "border-color" => Ok(Property::BorderColor), + "border-image" => Ok(Property::BorderImage), + "border-image-outset" => Ok(Property::BorderImageOutset), + "border-image-repeat" => Ok(Property::BorderImageRepeat), + "border-image-slice" => Ok(Property::BorderImageSlice), + "border-image-source" => Ok(Property::BorderImageSource), + "border-image-width" => Ok(Property::BorderImageWidth), + "border-left" => Ok(Property::BorderLeft), + "border-left-color" => Ok(Property::BorderLeftColor), + "border-left-style" => Ok(Property::BorderLeftStyle), + "border-left-width" => Ok(Property::BorderLeftWidth), + "border-radius" => Ok(Property::BorderRadius), + "border-right" => Ok(Property::BorderRight), + "border-right-color" => Ok(Property::BorderRightColor), + "border-right-style" => Ok(Property::BorderRightStyle), + "border-right-width" => Ok(Property::BorderRightWidth), + "border-spacing" => Ok(Property::BorderSpacing), + "border-style" => Ok(Property::BorderStyle), + "border-top" => Ok(Property::BorderTop), + "border-top-color" => Ok(Property::BorderTopColor), + "border-top-left-radius" => Ok(Property::BorderTopLeftRadius), + "border-top-right-radius" => Ok(Property::BorderTopRightRadius), + "border-top-style" => Ok(Property::BorderTopStyle), + "border-top-width" => Ok(Property::BorderTopWidth), + "border-width" => Ok(Property::BorderWidth), + "bottom" => Ok(Property::Bottom), + "box-decoration-break" => Ok(Property::BoxDecorationBreak), + "box-shadow" => Ok(Property::BoxShadow), + "box-sizing" => Ok(Property::BoxSizing), + "caption-side" => Ok(Property::CaptionSide), + "caret-color" => Ok(Property::CaretColor), + "@charset" => Ok(Property::Charset), + "clear" => Ok(Property::Clear), + "clip" => Ok(Property::Clip), + "color" => Ok(Property::Color), + "column-count" => Ok(Property::ColumnCount), + "column-fill" => Ok(Property::ColumnFill), + "column-gap" => Ok(Property::ColumnGap), + "column-rule" => Ok(Property::ColumnRule), + "column-rule-color" => Ok(Property::ColumnRuleColor), + "column-rule-style" => Ok(Property::ColumnRuleStyle), + "column-rule-width" => Ok(Property::ColumnRuleWidth), + "column-span" => Ok(Property::ColumnSpan), + "column-width" => Ok(Property::ColumnWidth), + "columns" => Ok(Property::Columns), + "content" => Ok(Property::Content), + "counter-increment" => Ok(Property::CounterIncrement), + "counter-reset" => Ok(Property::CounterReset), + "cursor" => Ok(Property::Cursor), + "direction" => Ok(Property::Direction), + "display" => Ok(Property::Display), + "empty-cells" => Ok(Property::EmptyCells), + "filter" => Ok(Property::Filter), + "flex" => Ok(Property::Flex), + "flex-basis" => Ok(Property::FlexBasis), + "flex-direction" => Ok(Property::FlexDirection), + "flex-flow" => Ok(Property::FlexFlow), + "flex-grow" => Ok(Property::FlexGrow), + "flex-shrink" => Ok(Property::FlexShrink), + "flex-wrap" => Ok(Property::FlexWrap), + "float" => Ok(Property::Float), + "font" => Ok(Property::Font), + "@font-face" => Ok(Property::FontFace), + "font-family" => Ok(Property::FontFamily), + "font-kerning" => Ok(Property::FontKerning), + "font-size" => Ok(Property::FontSize), + "font-size-adjust" => Ok(Property::FontSizeAdjust), + "font-stretch" => Ok(Property::FontStretch), + "font-style" => Ok(Property::FontStyle), + "font-variant" => Ok(Property::FontVariant), + "font-weight" => Ok(Property::FontWeight), + "grid" => Ok(Property::Grid), + "grid-area" => Ok(Property::GridArea), + "grid-auto-columns" => Ok(Property::GridAutoColumns), + "grid-auto-flow" => Ok(Property::GridAutoFlow), + "grid-auto-rows" => Ok(Property::GridAutoRows), + "grid-column" => Ok(Property::GridColumn), + "grid-column-end" => Ok(Property::GridColumnEnd), + "grid-column-gap" => Ok(Property::GridColumnGap), + "grid-column-start" => Ok(Property::GridColumnStart), + "grid-gap" => Ok(Property::GridGap), + "grid-row" => Ok(Property::GridRow), + "grid-row-end" => Ok(Property::GridRowEnd), + "grid-row-gap" => Ok(Property::GridRowGap), + "grid-row-start" => Ok(Property::GridRowStart), + "grid-template" => Ok(Property::GridTemplate), + "grid-template-areas" => Ok(Property::GridTemplateAreas), + "grid-template-columns" => Ok(Property::GridTemplateColumns), + "grid-template-rows" => Ok(Property::GridTemplateRows), + "hanging-punctuation" => Ok(Property::HangingPunctuation), + "height" => Ok(Property::Height), + "hyphens" => Ok(Property::Hyphens), + "@import" => Ok(Property::Import), + "isolation" => Ok(Property::Isolation), + "justify-content" => Ok(Property::JustifyContent), + "@keyframes" => Ok(Property::Keyframes), + "left" => Ok(Property::Left), + "letter-spacing" => Ok(Property::LetterSpacing), + "line-height" => Ok(Property::LineHeight), + "list-style" => Ok(Property::ListStyle), + "list-style-image" => Ok(Property::ListStyleImage), + "list-style-position" => Ok(Property::ListStylePosition), + "list-style-type" => Ok(Property::ListStyleType), + "margin" => Ok(Property::Margin), + "margin-bottom" => Ok(Property::MarginBottom), + "margin-left" => Ok(Property::MarginLeft), + "margin-right" => Ok(Property::MarginRight), + "margin-top" => Ok(Property::MarginTop), + "max-height" => Ok(Property::MaxHeight), + "max-width" => Ok(Property::MaxWidth), + "@media" => Ok(Property::Media), + "min-height" => Ok(Property::MinHeight), + "min-width" => Ok(Property::MinWidth), + "mix-blend-mode" => Ok(Property::MixBlendMode), + "object-fit" => Ok(Property::ObjectFit), + "object-position" => Ok(Property::ObjectPosition), + "opacity" => Ok(Property::Opacity), + "order" => Ok(Property::Order), + "outline" => Ok(Property::Outline), + "outline-color" => Ok(Property::OutlineColor), + "outline-offset" => Ok(Property::OutlineOffset), + "outline-style" => Ok(Property::OutlineStyle), + "outline-width" => Ok(Property::OutlineWidth), + "overflow" => Ok(Property::Overflow), + "overflow-x" => Ok(Property::OverflowX), + "overflow-y" => Ok(Property::OverflowY), + "padding" => Ok(Property::Padding), + "padding-bottom" => Ok(Property::PaddingBottom), + "padding-left" => Ok(Property::PaddingLeft), + "padding-right" => Ok(Property::PaddingRight), + "padding-top" => Ok(Property::PaddingTop), + "page-break-after" => Ok(Property::PageBreakAfter), + "page-break-before" => Ok(Property::PageBreakBefore), + "page-break-inside" => Ok(Property::PageBreakInside), + "perspective" => Ok(Property::Perspective), + "perspective-origin" => Ok(Property::PerspectiveOrigin), + "pointer-events" => Ok(Property::PointerEvents), + "position" => Ok(Property::Position), + "quotes" => Ok(Property::Quotes), + "resize" => Ok(Property::Resize), + "right" => Ok(Property::Right), + "scroll-behavior" => Ok(Property::ScrollBehavior), + "tab-size" => Ok(Property::TabSize), + "table-layout" => Ok(Property::TableLayout), + "text-align" => Ok(Property::TextAlign), + "text-align-last" => Ok(Property::TextAlignLast), + "text-decoration" => Ok(Property::TextDecoration), + "text-decoration-color" => Ok(Property::TextDecorationColor), + "text-decoration-line" => Ok(Property::TextDecorationLine), + "text-decoration-style" => Ok(Property::TextDecorationStyle), + "text-indent" => Ok(Property::TextIndent), + "text-justify" => Ok(Property::TextJustify), + "text-overflow" => Ok(Property::TextOverflow), + "text-shadow" => Ok(Property::TextShadow), + "text-transform" => Ok(Property::TextTransform), + "top" => Ok(Property::Top), + "transform" => Ok(Property::Transform), + "transform-origin" => Ok(Property::TransformOrigin), + "transform-style" => Ok(Property::TransformStyle), + "transition" => Ok(Property::Transition), + "transition-delay" => Ok(Property::TransitionDelay), + "transition-duration" => Ok(Property::TransitionDuration), + "transition-property" => Ok(Property::TransitionProperty), + "transition-timing-function" => Ok(Property::TransitionTimingFunction), + "unicode-bidi" => Ok(Property::UnicodeBidi), + "user-select" => Ok(Property::UserSelect), + "vertical-align" => Ok(Property::VerticalAlign), + "visibility" => Ok(Property::Visibility), + "white-space" => Ok(Property::WhiteSpace), + "width" => Ok(Property::Width), + "word-break" => Ok(Property::WordBreak), + "word-spacing" => Ok(Property::WordSpacing), + "word-wrap" => Ok(Property::WordWrap), + "writing-mode" => Ok(Property::WritingMode), + "z-index" => Ok(Property::ZIndex), + _ => Err("invalid property"), + } + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/error.rs @@ -0,0 +1 @@ + diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..432037c --- /dev/null +++ b/src/format.rs @@ -0,0 +1,159 @@ +use std::io::{self, Write}; + +use crate::{RuleSet, Stmt, Style, StyleSheet}; + +pub(crate) struct PrettyPrinter { + buf: W, + scope: usize, +} + +impl PrettyPrinter { + pub(crate) fn new(buf: W) -> Self { + PrettyPrinter { buf, scope: 0 } + } + + fn pretty_print_stmt(&mut self, stmt: &Stmt) -> io::Result<()> { + let padding = vec![' '; self.scope * 2].iter().collect::(); + match stmt { + Stmt::RuleSet(RuleSet { + selector, rules, .. + }) => { + writeln!(self.buf, "{}{} {{", padding, selector)?; + self.scope += 1; + for rule in rules { + self.pretty_print_stmt(rule)?; + } + writeln!(self.buf, "{}}}", padding)?; + self.scope -= 1; + } + Stmt::Style(Style { property, value }) => { + writeln!( + self.buf, + "{}{}: {};", + padding, + property, + value + .iter() + .map(ToString::to_string) + .collect::>() + .join(" ") + )?; + } + } + Ok(()) + } + + pub(crate) fn pretty_print(&mut self, s: &StyleSheet) -> io::Result<()> { + for rule in &s.rules { + self.pretty_print_stmt(rule)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::StyleSheet; + macro_rules! test { + ($func:ident, $input:literal) => { + #[test] + fn $func() { + let mut buf = Vec::new(); + StyleSheet::new($input) + .pretty_print(&mut buf) + .expect(concat!("failed to pretty print on ", $input)); + assert_eq!( + String::from($input), + String::from_utf8(buf).expect("produced invalid utf8") + ); + } + }; + ($func:ident, $input:literal, $output:literal) => { + #[test] + fn $func() { + let mut buf = Vec::new(); + StyleSheet::new($input) + .pretty_print(&mut buf) + .expect(concat!("failed to pretty print on ", $input)); + assert_eq!( + String::from($output), + String::from_utf8(buf).expect("produced invalid utf8") + ); + } + }; + } + + test!(empty, ""); + test!(basic_nesting, "a {\n b {\n }\n}\n"); + test!(ident_with_num, "el1 {\n}\n"); + + test!(selector_element, "a {\n}\n"); + test!(selector_id, "#id {\n}\n"); + test!(selector_class, ".class {\n}\n"); + test!(selector_el_descendant, "a a {\n}\n"); + test!(selector_universal, "* {\n}\n"); + test!(selector_el_class_and, "a.class {\n}\n"); + test!(selector_el_id_and, "a#class {\n}\n"); + test!(selector_el_class_descendant, "a .class {\n}\n"); + test!(selector_el_id_descendant, "a #class {\n}\n"); + test!(selector_el_universal_descendant, "a * {\n}\n"); + test!(selector_universal_el_descendant, "* a {\n}\n"); + test!(selector_attribute_any, "[attr] {\n}\n"); + test!(selector_attribute_equals, "[attr=val] {\n}\n"); + test!(selector_attribute_in, "[attr~=val] {\n}\n"); + test!( + selector_attribute_begins_hyphen_or_exact, + "[attr|=val] {\n}\n" + ); + test!(selector_attribute_starts_with, "[attr^=val] {\n}\n"); + test!(selector_attribute_ends_with, "[attr$=val] {\n}\n"); + test!(selector_attribute_contains, "[attr*=val] {\n}\n"); + test!(selector_el_attribute_and, "a[attr] {\n}\n"); + test!(selector_el_attribute_descendant, "a [attr] {\n}\n"); + test!(selector_el_mul_el, "a, b {\n}\n"); + test!(selector_el_immediate_child_el, "a > b {\n}\n"); + test!(selector_el_following_el, "a + b {\n}\n"); + test!(selector_el_preceding_el, "a ~ b {\n}\n"); + test!(selector_pseudo, ":pseudo {\n}\n"); + test!(selector_el_pseudo_and, "a:pseudo {\n}\n"); + test!(selector_el_pseudo_descendant, "a :pseudo {\n}\n"); + + test!(basic_style, "a {\n color: red;\n}\n"); + test!(two_styles, "a {\n color: red;\n color: blue;\n}\n"); + test!(hyphenated_style_property, "a {\n font-family: Arial;\n}\n"); + test!(hyphenated_style_value, "a {\n color: Open-Sans;\n}\n"); + test!( + space_separated_style_value, + "a {\n border: solid red;\n}\n" + ); + test!( + single_quoted_style_value, + "a {\n font: 'Open-Sans';\n}\n", + "a {\n font: Open-Sans;\n}\n" + ); + test!( + double_quoted_style_value, + "a {\n font: \"Open-Sans\";\n}\n", + "a {\n font: Open-Sans;\n}\n" + ); + // test!(comma_style_value, "a {\n font: Open-Sans, sans-serif;\n}\n"); + test!( + nested_style_in_parent, + "a {\n color: red;\n b {\n }\n}\n" + ); + test!( + nested_style_in_child, + "a {\n b {\n color: red;\n }\n}\n" + ); + test!( + nested_style_in_both, + "a {\n color: red;\n b {\n color: red;\n }\n}\n" + ); + + test!(unit_none, "a {\n height: 1;\n}\n"); + test!(unit_not_attached, "a {\n height: 1 px;\n}\n"); + test!(unit_px, "a {\n height: 1px;\n}\n"); + test!(unit_em, "a {\n height: 1em;\n}\n"); + test!(unit_rem, "a {\n height: 1rem;\n}\n"); + test!(unit_percent, "a {\n height: 1%;\n}\n"); +} diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..90cc1ed --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,305 @@ +use std::convert::TryFrom; +use std::iter::Peekable; +use std::str::Chars; + +use crate::common::{Keyword, Pos, Symbol}; +use crate::selector::{Attribute, AttributeKind, Selector}; +use crate::units::Unit; +use crate::{Token, TokenKind, Whitespace}; + +#[derive(Debug, Clone)] +pub struct Lexer<'a> { + tokens: Vec, + buf: Peekable>, + pos: Pos, +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Token; + fn next(&mut self) -> Option { + macro_rules! symbol { + ($self:ident, $symbol:ident) => {{ + $self.buf.next(); + $self.pos.next_char(); + TokenKind::Symbol(Symbol::$symbol) + }}; + } + macro_rules! whitespace { + ($self:ident, $whitespace:ident) => {{ + $self.buf.next(); + $self.pos.next_char(); + TokenKind::Whitespace(Whitespace::$whitespace) + }}; + } + let kind: TokenKind = match self.buf.peek().unwrap_or(&'\0') { + 'a'..='z' | 'A'..='Z' => self.lex_ident(), + '@' => self.lex_at_rule(), + '0'..='9' => self.lex_num(), + '$' => self.lex_variable(), + ':' => symbol!(self, Colon), + ',' => symbol!(self, Comma), + '.' => symbol!(self, Period), + ';' => symbol!(self, SemiColon), + '+' => symbol!(self, Plus), + '~' => symbol!(self, Tilde), + '\'' => symbol!(self, SingleQuote), + '"' => symbol!(self, DoubleQuote), + ' ' => whitespace!(self, Space), + '\t' => whitespace!(self, Tab), + '\n' => whitespace!(self, Newline), + '\r' => whitespace!(self, CarriageReturn), + '#' => symbol!(self, Hash), + '{' => symbol!(self, OpenBrace), + '*' => symbol!(self, Mul), + '}' => symbol!(self, CloseBrace), + '%' => { + self.buf.next(); + self.pos.next_char(); + TokenKind::Unit(Unit::Percent) + } + '[' => { + self.buf.next(); + self.pos.next_char(); + self.lex_attr() + } + '<' => symbol!(self, Lt), + '>' => symbol!(self, Gt), + '\0' => return None, + _ => todo!("unknown char"), + }; + self.pos.next_char(); + Some(Token { + kind, + pos: self.pos, + }) + } +} + +fn is_whitespace(c: char) -> bool { + c == ' ' || c == '\n' || c == '\r' +} + +impl<'a> Lexer<'a> { + pub fn new(buf: &'a str) -> Lexer<'a> { + Lexer { + tokens: Vec::with_capacity(buf.len()), + buf: buf.chars().peekable(), + pos: Pos::new(), + } + } + + fn devour_whitespace(&mut self) { + while let Some(c) = self.buf.peek() { + if !is_whitespace(*c) { + break; + } + } + } + + fn lex_at_rule(&mut self) -> TokenKind { + let mut string = String::with_capacity(99); + while let Some(c) = self.buf.peek() { + if !c.is_alphabetic() && c != &'-' { + break; + } + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + string.push(tok); + } + + if let Ok(kw) = Unit::try_from(string.as_ref()) { + TokenKind::Unit(kw) + } else { + panic!("expected ident after `@`") + } + } + + fn lex_num(&mut self) -> TokenKind { + let mut string = String::with_capacity(99); + while let Some(c) = self.buf.peek() { + if !c.is_numeric() && c != &'.' { + break; + } + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + string.push(tok); + } + + TokenKind::Number(string) + } + + fn lex_hash(&mut self) -> TokenKind { + todo!() + } + + fn lex_attr(&mut self) -> TokenKind { + let mut attr = String::with_capacity(99); + while let Some(c) = self.buf.peek() { + if !c.is_alphabetic() && c != &'-' { + break; + } + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + attr.push(tok); + } + + self.devour_whitespace(); + + let kind = match self + .buf + .next() + .expect("todo! expected kind (should be error)") + { + ']' => { + return TokenKind::Selector(Selector::Attribute(Attribute { + kind: AttributeKind::Any, + attr, + value: String::new(), + case_sensitive: true, + })) + } + 'i' => { + self.devour_whitespace(); + assert!(self.buf.next() == Some(']')); + return TokenKind::Selector(Selector::Attribute(Attribute { + kind: AttributeKind::Any, + attr, + value: String::new(), + case_sensitive: false, + })); + } + '=' => AttributeKind::Equals, + '~' => AttributeKind::InList, + '|' => AttributeKind::BeginsWithHyphenOrExact, + '^' => AttributeKind::StartsWith, + '$' => AttributeKind::EndsWith, + '*' => AttributeKind::Contains, + _ => todo!("expected kind (should be error)"), + }; + + if kind != AttributeKind::Equals { + assert!(self.buf.next() == Some('=')); + } + + self.devour_whitespace(); + + match self + .buf + .peek() + .expect("todo! expected either value or quote") + { + '\'' | '"' => { + self.buf.next(); + } + _ => {} + } + + let mut value = String::with_capacity(99); + let mut case_sensitive = true; + + while let Some(c) = self.buf.peek() { + if !c.is_alphabetic() && c != &'-' { + break; + } + + if c == &'i' { + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + self.devour_whitespace(); + match self.buf.next() { + Some(']') => case_sensitive = false, + Some(val) => { + self.pos.next_char(); + value.push(tok); + value.push(val); + } + None => todo!("expected something to come after "), + } + continue; + } + + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + value.push(tok); + } + + match self + .buf + .peek() + .expect("todo! expected either value or quote") + { + '\'' | '"' => { + self.buf.next(); + } + _ => {} + } + + self.devour_whitespace(); + + assert!(self.buf.next() == Some(']')); + + TokenKind::Selector(Selector::Attribute(Attribute { + kind, + attr, + value, + case_sensitive, + })) + } + + fn lex_variable(&mut self) -> TokenKind { + let mut string = String::with_capacity(99); + while let Some(c) = self.buf.peek() { + if !c.is_alphabetic() && c != &'-' { + break; + } + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + string.push(tok); + } + TokenKind::Variable(string) + } + + fn lex_ident(&mut self) -> TokenKind { + let mut string = String::with_capacity(99); + while let Some(c) = self.buf.peek() { + // we know that the first char is alphabetic from peeking + if !c.is_alphanumeric() && c != &'-' { + break; + } + let tok = self + .buf + .next() + .expect("this is impossible because we have already peeked"); + self.pos.next_char(); + string.push(tok); + } + + if let Ok(kw) = Keyword::try_from(string.as_ref()) { + return TokenKind::Keyword(kw); + } + + if let Ok(kw) = Unit::try_from(string.as_ref()) { + return TokenKind::Unit(kw); + } + + TokenKind::Ident(string) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6ba8419 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,303 @@ +#![warn( + clippy::all, + clippy::restriction, + clippy::pedantic, + clippy::nursery, + // clippy::cargo +)] +#![deny(missing_debug_implementations)] +#![allow( + dead_code, + clippy::pub_enum_variant_names, + clippy::implicit_return, + clippy::use_self, + clippy::missing_docs_in_private_items, + clippy::todo, + clippy::dbg_macro, + clippy::unreachable, + clippy::wildcard_enum_match_arm, + clippy::option_expect_used, + clippy::panic, + clippy::unused_self, + clippy::too_many_lines, + clippy::integer_arithmetic, + clippy::missing_errors_doc +)] +use std::collections::HashMap; +use std::fs; +use std::io; +use std::iter::Iterator; +use std::{ + fmt::{self, Display}, + iter::Peekable, +}; + +use crate::common::{Keyword, Pos, Symbol, Whitespace}; +use crate::format::PrettyPrinter; +use crate::lexer::Lexer; +use crate::selector::Selector; +use crate::units::Unit; + +mod color; +mod common; +mod error; +mod format; +mod lexer; +mod selector; +mod units; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TokenKind { + Ident(String), + Symbol(Symbol), + Function(String, Vec), + AtRule(String), + Keyword(Keyword), + Number(String), + Unit(Unit), + Whitespace(Whitespace), + Variable(String), + Selector(Selector), + Style(Vec), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum StyleToken { + Ident(String), + Function(String, Vec), + Keyword(Keyword), + Symbol(Symbol), + Dimension(String, Unit), +} + +impl Display for StyleToken { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StyleToken::Ident(s) => write!(f, "{}", s), + StyleToken::Symbol(s) => write!(f, "{}", s), + StyleToken::Function(name, args) => write!(f, "{}({})", name, args.join(", ")), + StyleToken::Keyword(kw) => write!(f, "{}", kw), + StyleToken::Dimension(val, unit) => write!(f, "{}{}", val, unit), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Token { + pos: Pos, + pub kind: TokenKind, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct StyleSheet { + rules: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Style { + property: String, + value: Vec, +} + +impl Display for Style { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}: {};", + self.property, + self.value + .iter() + .map(|x| format!("{}", x)) + .collect::>() + .join(" ") + ) + } +} + +impl Style { + fn from_tokens(raw: &[Token]) -> Result { + let mut iter = raw.iter(); + let property: String; + loop { + if let Some(tok) = iter.next() { + match tok.kind { + TokenKind::Whitespace(_) => continue, + TokenKind::Ident(ref s) => { + property = s.clone(); + break; + } + _ => todo!(), + }; + } else { + return Err(()); + } + } + + while let Some(tok) = iter.next() { + match tok.kind { + TokenKind::Whitespace(_) => continue, + TokenKind::Symbol(Symbol::Colon) => break, + _ => todo!("found tokens before style value"), + } + } + + let mut value = Vec::new(); + + while let Some(tok) = iter.next() { + match tok.kind { + TokenKind::Whitespace(_) + | TokenKind::Symbol(Symbol::SingleQuote) + | TokenKind::Symbol(Symbol::DoubleQuote) => continue, + TokenKind::Ident(ref s) => value.push(StyleToken::Ident(s.clone())), + TokenKind::Symbol(s) => value.push(StyleToken::Symbol(s)), + TokenKind::Unit(u) => value.push(StyleToken::Ident(u.into())), + TokenKind::Number(ref num) => { + if let Some(t) = iter.next() { + match &t.kind { + &TokenKind::Unit(unit) => { + value.push(StyleToken::Dimension(num.clone(), unit)) + } + TokenKind::Ident(ref s) => { + value.push(StyleToken::Dimension(num.clone(), Unit::None)); + value.push(StyleToken::Ident(s.clone())); + } + TokenKind::Whitespace(_) => { + value.push(StyleToken::Dimension(num.clone(), Unit::None)) + } + _ => todo!(), + } + } else { + value.push(StyleToken::Dimension(num.clone(), Unit::None)) + } + } + _ => todo!("style value not ident or dimension"), + } + } + Ok(Style { property, value }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Stmt { + Style(Style), + RuleSet(RuleSet), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RuleSet { + selector: Selector, + rules: Vec, + // potential optimization: we don't *need* to own the selector + super_selector: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum Expr { + Style(Style), + Selector(Selector), +} + +impl StyleSheet { + #[must_use] + pub fn new(input: &str) -> StyleSheet { + StyleSheetParser { + variables: HashMap::new(), + lexer: Lexer::new(input).peekable(), + rules: Vec::new(), + } + .parse_toplevel() + } + + pub fn pretty_print(&self, buf: W) -> io::Result<()> { + PrettyPrinter::new(buf).pretty_print(self) + } +} + +#[derive(Debug, Clone)] +struct StyleSheetParser<'a> { + variables: HashMap, + lexer: Peekable>, + rules: Vec, +} + +impl<'a> StyleSheetParser<'a> { + fn parse_toplevel(&'a mut self) -> StyleSheet { + let mut rules = Vec::new(); + while let Some(tok) = self.lexer.peek() { + match tok.kind { + TokenKind::Ident(_) + | TokenKind::Selector(_) + | TokenKind::Symbol(Symbol::Hash) + | TokenKind::Symbol(Symbol::Colon) + | TokenKind::Symbol(Symbol::Mul) + | TokenKind::Symbol(Symbol::Period) => rules.extend(self.eat_rules(&None)), + TokenKind::Whitespace(_) | TokenKind::Symbol(_) => { + self.lexer.next(); + continue; + } + _ => todo!("unexpected toplevel token"), + }; + } + StyleSheet { rules } + } + + fn eat_rules(&mut self, super_selector: &Option) -> Vec { + let mut stmts = Vec::new(); + while let Ok(tok) = self.eat_expr() { + match tok { + Expr::Style(s) => stmts.push(Stmt::Style(s)), + Expr::Selector(s) => { + let rules = self.eat_rules(&Some(s.clone())); + stmts.push(Stmt::RuleSet(RuleSet { + super_selector: super_selector.clone(), + selector: s, + rules, + })); + } + } + } + stmts + } + + fn eat_expr(&mut self) -> Result { + let mut values = Vec::with_capacity(5); + while let Some(tok) = self.lexer.next() { + match tok.kind { + TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => { + self.devour_whitespace(); + return Ok(Expr::Style(Style::from_tokens(&values)?)); + } + TokenKind::Symbol(Symbol::OpenBrace) => { + self.devour_whitespace(); + return Ok(Expr::Selector(Selector::from_tokens( + values.iter().peekable(), + ))); + } + _ => values.push(tok.clone()), + }; + } + Err(()) + } + + fn devour_whitespace(&mut self) { + while let Some(tok) = self.lexer.peek() { + match tok.kind { + TokenKind::Whitespace(_) => { + self.lexer.next(); + } + _ => break, + } + } + } +} + +fn main() -> io::Result<()> { + let input = fs::read_to_string("input.scss")?; + let mut stdout = std::io::stdout(); + let s = StyleSheet::new(&input); + // dbg!(s); + s.pretty_print(&mut stdout)?; + // println!("{}", s); + // drop(input); + Ok(()) +} diff --git a/src/selector.rs b/src/selector.rs new file mode 100644 index 0000000..161ba5c --- /dev/null +++ b/src/selector.rs @@ -0,0 +1,247 @@ +use crate::common::Symbol; +use crate::{Token, TokenKind}; +use std::iter::Peekable; +use std::{ + fmt::{self, Display}, + slice::Iter, +}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Selector { + /// An element selector: `button` + Element(String), + /// An id selector: `#footer` + Id(String), + /// A single class selector: `.button-active` + Class(String), + /// A universal selector: `*` + Universal, + /// A simple child selector: `ul li` + Descendant(Box, Box), + /// And selector: `button.active` + And(Box, Box), + /// Multiple unrelated selectors: `button, .active` + Multiple(Box, Box), + /// Select all immediate children: `ul > li` + ImmediateChild(Box, Box), + /// Select all elements immediately following: `div + p` + Following(Box, Box), + /// Select elements preceeded by: `p ~ ul` + Preceding(Box, Box), + /// Select elements with attribute: `html[lang|=en]` + Attribute(Attribute), + /// Pseudo selector: `:hover` + Pseudo(String), +} + +impl Display for Selector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Selector::Element(s) => write!(f, "{}", s), + Selector::Id(s) => write!(f, "#{}", s), + Selector::Class(s) => write!(f, ".{}", s), + Selector::Universal => write!(f, "*"), + Selector::Descendant(lhs, rhs) => write!(f, "{} {}", lhs, rhs), + Selector::And(lhs, rhs) => write!(f, "{}{}", lhs, rhs), + Selector::Multiple(lhs, rhs) => write!(f, "{}, {}", lhs, rhs), + Selector::ImmediateChild(lhs, rhs) => write!(f, "{} > {}", lhs, rhs), + Selector::Following(lhs, rhs) => write!(f, "{} + {}", lhs, rhs), + Selector::Preceding(lhs, rhs) => write!(f, "{} ~ {}", lhs, rhs), + Selector::Attribute(attr) => write!(f, "{}", attr), + Selector::Pseudo(s) => write!(f, ":{}", s), + } + } +} + +struct SelectorParser<'a> { + tokens: Peekable>, +} + +impl<'a> SelectorParser<'a> { + const fn new(tokens: Peekable>) -> SelectorParser<'a> { + SelectorParser { tokens } + } + + fn all_selectors(&mut self) -> Selector { + self.devour_whitespace(); + let left = self + .consume_selector() + .expect("expected left handed selector"); + let whitespace: bool = self.devour_whitespace(); + match self.tokens.peek() { + Some(tok) => match tok.kind { + TokenKind::Ident(_) => { + return Selector::Descendant(Box::new(left), Box::new(self.all_selectors())) + } + TokenKind::Symbol(Symbol::Plus) => { + self.tokens.next(); + self.devour_whitespace(); + return Selector::Following(Box::new(left), Box::new(self.all_selectors())); + } + TokenKind::Symbol(Symbol::Tilde) => { + self.tokens.next(); + self.devour_whitespace(); + return Selector::Preceding(Box::new(left), Box::new(self.all_selectors())); + } + TokenKind::Symbol(Symbol::Comma) => { + self.tokens.next(); + self.devour_whitespace(); + return Selector::Multiple(Box::new(left), Box::new(self.all_selectors())); + } + TokenKind::Symbol(Symbol::Gt) => { + self.tokens.next(); + self.devour_whitespace(); + return Selector::ImmediateChild( + Box::new(left), + Box::new(self.all_selectors()), + ); + } + TokenKind::Symbol(Symbol::Colon) + | TokenKind::Symbol(Symbol::Period) + | TokenKind::Symbol(Symbol::Mul) + | TokenKind::Selector(_) + | TokenKind::Symbol(Symbol::Hash) => { + if whitespace { + return Selector::Descendant( + Box::new(left), + Box::new(self.all_selectors()), + ); + } else { + return Selector::And(Box::new(left), Box::new(self.all_selectors())); + } + } + TokenKind::Symbol(Symbol::Lt) => {} + _ => todo!(), + }, + None => return left, + } + todo!() + } + + fn devour_whitespace(&mut self) -> bool { + let mut found_whitespace = false; + while let Some(tok) = self.tokens.peek() { + match tok.kind { + TokenKind::Whitespace(_) => { + self.tokens.next(); + found_whitespace = true; + } + _ => break, + } + } + found_whitespace + } + + fn consume_selector(&mut self) -> Option { + if let Some(tok) = self.tokens.next() { + let selector = match tok.kind { + TokenKind::Symbol(Symbol::Period) => match self + .tokens + .next() + .expect("expected ident after `.`") + .clone() + .kind + { + TokenKind::Ident(tok) => Selector::Class(tok), + _ => todo!("there should normally be an ident after `.`"), + }, + TokenKind::Symbol(Symbol::Mul) => Selector::Universal, + TokenKind::Symbol(Symbol::Hash) => match self + .tokens + .next() + .expect("expected ident after `#`") + .clone() + .kind + { + TokenKind::Ident(tok) => Selector::Id(tok), + _ => todo!("there should normally be an ident after `#`"), + }, + TokenKind::Symbol(Symbol::Colon) => { + match self + .tokens + .next() + .expect("expected ident after `:`") + .clone() + .kind + { + TokenKind::Ident(tok) => Selector::Pseudo(tok), + _ => todo!("there should normally be an ident after `:`"), + } + } + TokenKind::Ident(ref tok) => Selector::Element(tok.clone()), + TokenKind::Selector(ref sel) => sel.clone(), + _ => todo!(), + }; + Some(selector) + } else { + None + } + } +} + +impl Selector { + pub fn from_tokens(tokens: Peekable>) -> Selector { + SelectorParser::new(tokens).all_selectors() + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Attribute { + pub attr: String, + pub value: String, + pub case_sensitive: bool, + pub kind: AttributeKind, +} + +impl Display for Attribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.case_sensitive { + match self.kind { + AttributeKind::Any => write!(f, "[{}]", self.attr), + AttributeKind::Equals => write!(f, "[{}={}]", self.attr, self.value), + AttributeKind::InList => write!(f, "[{}~={}]", self.attr, self.value), + AttributeKind::BeginsWithHyphenOrExact => { + write!(f, "[{}|={}]", self.attr, self.value) + } + AttributeKind::StartsWith => write!(f, "[{}^={}]", self.attr, self.value), + AttributeKind::EndsWith => write!(f, "[{}$={}]", self.attr, self.value), + AttributeKind::Contains => write!(f, "[{}*={}]", self.attr, self.value), + } + } else { + match self.kind { + AttributeKind::Any => write!(f, "[{} i]", self.attr), + AttributeKind::Equals => write!(f, "[{}={} i]", self.attr, self.value), + AttributeKind::InList => write!(f, "[{}~={} i]", self.attr, self.value), + AttributeKind::BeginsWithHyphenOrExact => { + write!(f, "[{}|={} i]", self.attr, self.value) + } + AttributeKind::StartsWith => write!(f, "[{}^={} i]", self.attr, self.value), + AttributeKind::EndsWith => write!(f, "[{}$={} i]", self.attr, self.value), + AttributeKind::Contains => write!(f, "[{}*={} i]", self.attr, self.value), + } + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum AttributeKind { + /// [attr] + /// Represents elements with an attribute name of `attr` + Any, + /// [attr=value] + /// Represents elements with an attribute name of `attr` whose value is exactly `value` + Equals, + /// [attr~=value] + /// Represents elements with an attribute name of `attr` whose value is a whitespace-separated list of words, one of which is exactly `value` + InList, + /// [attr|=value] + /// Represents elements with an attribute name of `attr` whose value can be exactly value or can begin with `value` immediately followed by a hyphen (`-`) + BeginsWithHyphenOrExact, + /// [attr^=value] + StartsWith, + /// [attr$=value] + EndsWith, + /// [attr*=value] + /// Represents elements with an attribute name of `attr` whose value contains at least one occurrence of `value` within the string + Contains, +} diff --git a/src/units.rs b/src/units.rs new file mode 100644 index 0000000..fec43de --- /dev/null +++ b/src/units.rs @@ -0,0 +1,261 @@ +use std::convert::TryFrom; +use std::fmt; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Unit { + // Absolute units + /// Pixels + Px, + /// Millimeters + Mm, + /// Inches + In, + /// Centimeters + Cm, + /// Quarter-millimeters + Q, + /// Points + Pt, + /// Picas + Pc, + // Font relative units + /// Font size of the parent element + Em, + /// Font size of the root element + Rem, + /// Line height of the element + Lh, + Percent, + /// x-height of the element's font + Ex, + /// The advance measure (width) of the glyph "0" of the element's font + Ch, + /// Represents the "cap height" (nominal height of capital letters) of the element’s font + Cap, + /// Equal to the used advance measure of the "水" (CJK water ideograph, U+6C34) glyph found in the font used to render it + Ic, + /// Equal to the computed value of the line-height property on the root element (typically ), converted to an absolute length + Rlh, + + // Viewport relative units + /// 1% of the viewport's width + Vw, + /// 1% of the viewport's height + Vh, + /// 1% of the viewport's smaller dimension + Vmin, + /// 1% of the viewport's larger dimension + Vmax, + /// Equal to 1% of the size of the initial containing block, in the direction of the root element’s inline axis + Vi, + /// Equal to 1% of the size of the initial containing block, in the direction of the root element’s block axis + Vb, + + // Angle units + /// Represents an angle in degrees. One full circle is 360deg + Deg, + /// Represents an angle in gradians. One full circle is 400grad + Grad, + /// Represents an angle in radians. One full circle is 2π radians which approximates to 6.2832rad + Rad, + /// Represents an angle in a number of turns. One full circle is 1turn + Turn, + + // Time units + /// Represents a time in seconds + S, + /// Represents a time in milliseconds + Ms, + + // Frequency units + /// Represents a frequency in hertz + Hz, + /// Represents a frequency in kilohertz + Khz, + + // Resolution units + /// Represents the number of dots per inch + Dpi, + /// Represents the number of dots per centimeter + Dpcm, + /// Represents the number of dots per px unit + Dppx, + /// Alias for dppx + X, + + /// Represents a fraction of the available space in the grid container + Fr, + /// Unspecified unit + None, +} + +impl TryFrom<&str> for Unit { + type Error = &'static str; + + fn try_from(unit: &str) -> Result { + match unit.to_ascii_lowercase().as_bytes() { + b"px" => Ok(Unit::Px), + b"mm" => Ok(Unit::Mm), + b"in" => Ok(Unit::In), + b"cm" => Ok(Unit::Cm), + b"Q" => Ok(Unit::Q), + b"pt" => Ok(Unit::Pt), + b"pc" => Ok(Unit::Pc), + b"em" => Ok(Unit::Em), + b"rem" => Ok(Unit::Rem), + b"lh" => Ok(Unit::Lh), + b"%" => Ok(Unit::Percent), + b"ex" => Ok(Unit::Ex), + b"ch" => Ok(Unit::Ch), + b"cap" => Ok(Unit::Cap), + b"ic" => Ok(Unit::Ic), + b"rlh" => Ok(Unit::Rlh), + b"vw" => Ok(Unit::Vw), + b"vh" => Ok(Unit::Vh), + b"vmin" => Ok(Unit::Vmin), + b"vmax" => Ok(Unit::Vmax), + b"vi" => Ok(Unit::Vi), + b"vb" => Ok(Unit::Vb), + b"deg" => Ok(Unit::Deg), + b"grad" => Ok(Unit::Grad), + b"rad" => Ok(Unit::Rad), + b"turn" => Ok(Unit::Turn), + b"s" => Ok(Unit::S), + b"ms" => Ok(Unit::Ms), + b"Hz" => Ok(Unit::Hz), + b"kHz" => Ok(Unit::Khz), + b"dpi" => Ok(Unit::Dpi), + b"dpcm" => Ok(Unit::Dpcm), + b"dppx" | b"x" => Ok(Unit::Dppx), + b"fr" => Ok(Unit::Fr), + _ => Err("invalid unit"), + } + } +} + +impl Into for Unit { + fn into(self) -> String { + match self { + Unit::Px => "px", + Unit::Mm => "mm", + Unit::In => "in", + Unit::Cm => "cm", + Unit::Q => "Q", + Unit::Pt => "pt", + Unit::Pc => "pc", + Unit::Em => "em", + Unit::Rem => "rem", + Unit::Lh => "lh", + Unit::Percent => "%", + Unit::Ex => "ex", + Unit::Ch => "ch", + Unit::Cap => "cap", + Unit::Ic => "ic", + Unit::Rlh => "rlh", + Unit::Vw => "vw", + Unit::Vh => "vh", + Unit::Vmin => "vmin", + Unit::Vmax => "vmax", + Unit::Vi => "vi", + Unit::Vb => "vb", + Unit::Deg => "deg", + Unit::Grad => "grad", + Unit::Rad => "rad", + Unit::Turn => "turn", + Unit::S => "s", + Unit::Ms => "ms", + Unit::Hz => "Hz", + Unit::Khz => "kHz", + Unit::Dpi => "dpi", + Unit::Dpcm => "dpcm", + Unit::Dppx | Unit::X => "dppx", + Unit::Fr => "fr", + Unit::None => "", + } + .into() + } +} + +impl Into<&'static str> for Unit { + fn into(self) -> &'static str { + match self { + Unit::Px => "px", + Unit::Mm => "mm", + Unit::In => "in", + Unit::Cm => "cm", + Unit::Q => "Q", + Unit::Pt => "pt", + Unit::Pc => "pc", + Unit::Em => "em", + Unit::Rem => "rem", + Unit::Lh => "lh", + Unit::Percent => "%", + Unit::Ex => "ex", + Unit::Ch => "ch", + Unit::Cap => "cap", + Unit::Ic => "ic", + Unit::Rlh => "rlh", + Unit::Vw => "vw", + Unit::Vh => "vh", + Unit::Vmin => "vmin", + Unit::Vmax => "vmax", + Unit::Vi => "vi", + Unit::Vb => "vb", + Unit::Deg => "deg", + Unit::Grad => "grad", + Unit::Rad => "rad", + Unit::Turn => "turn", + Unit::S => "s", + Unit::Ms => "ms", + Unit::Hz => "Hz", + Unit::Khz => "kHz", + Unit::Dpi => "dpi", + Unit::Dpcm => "dpcm", + Unit::Dppx | Unit::X => "dppx", + Unit::Fr => "fr", + Unit::None => "", + } + } +} + +impl fmt::Display for Unit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Unit::Px => write!(f, "px"), + Unit::Mm => write!(f, "mm"), + Unit::In => write!(f, "in"), + Unit::Cm => write!(f, "cm"), + Unit::Q => write!(f, "Q"), + Unit::Pt => write!(f, "pt"), + Unit::Pc => write!(f, "pc"), + Unit::Em => write!(f, "em"), + Unit::Rem => write!(f, "rem"), + Unit::Lh => write!(f, "lh"), + Unit::Percent => write!(f, "%"), + Unit::Ex => write!(f, "ex"), + Unit::Ch => write!(f, "ch"), + Unit::Cap => write!(f, "cap"), + Unit::Ic => write!(f, "ic"), + Unit::Rlh => write!(f, "rlh"), + Unit::Vw => write!(f, "vw"), + Unit::Vh => write!(f, "vh"), + Unit::Vmin => write!(f, "vmin"), + Unit::Vmax => write!(f, "vmax"), + Unit::Vi => write!(f, "vi"), + Unit::Vb => write!(f, "vb"), + Unit::Deg => write!(f, "deg"), + Unit::Grad => write!(f, "grad"), + Unit::Rad => write!(f, "rad"), + Unit::Turn => write!(f, "turn"), + Unit::S => write!(f, "s"), + Unit::Ms => write!(f, "ms"), + Unit::Hz => write!(f, "Hz"), + Unit::Khz => write!(f, "kHz"), + Unit::Dpi => write!(f, "dpi"), + Unit::Dpcm => write!(f, "dpcm"), + Unit::Dppx | Unit::X => write!(f, "dppx"), + Unit::Fr => write!(f, "fr"), + Unit::None => write!(f, ""), + } + } +}