1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use super::*;

impl AstVariant {
    /// `(not-)?(ALPHA)(-ALPHA)*`
    ///
    /// eg:
    /// - `not-focus`
    /// - `not-last-child`
    pub fn parse(input: &str) -> IResult<&str, Self> {
        let not = opt(tuple((tag("not"), tag("-"))));
        let vs = separated_list0(tag("-"), alphanumeric1);
        let (rest, (not, r)) = tuple((not, vs))(input)?;
        let names: Vec<_> = r.into_iter().map(|f| f.to_string()).collect();
        let pseudo = Self::check_pseudo(&names.iter().map(<_>::as_ref).collect::<Vec<_>>());
        Ok((rest, Self { not: not.is_some(), pseudo, names }))
    }
    pub fn parse_many(input: &str) -> IResult<&str, Vec<Self>> {
        let (rest, out) = many0(tuple((Self::parse, alt((tag("::"), tag(":"))))))(input)?;
        let mut v = vec![];
        for (a, s) in out {
            let mut a = a;
            if s == "::" {
                a.pseudo = true
            }
            v.push(a)
        }
        Ok((rest, v))
    }
    #[rustfmt::skip]
    /// https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements#index
    fn check_pseudo(names: &[&str]) -> bool {
        matches!(names
            , ["after"]
            | ["before"]
            | ["backdrop"]
            | ["marker"]
            | ["placeholder"]
            | ["selection"]
            | ["first", "line"]
            | ["first", "litter"]
            | ["first", "selector", "button"]
            | ["target", "text"]
        )
    }
}

impl AstArbitrary {
    pub fn parse(input: &str) -> IResult<&str, Self> {
        let (rest, r) = delimited(char('['), take_till1(|c| c != ']'), char(']'))(input)?;
        Ok((rest, Self(r.to_string())))
    }
    pub fn parse_maybe(input: &str) -> IResult<&str, Option<Self>> {
        let (rest, rhs) = opt(tuple((tag("-"), Self::parse)))(input)?;
        Ok((rest, rhs.map(|(_, e)| e)))
    }
}

impl AstElement {
    fn parse(input: &str) -> IResult<&str, Self> {
        let stop = |c: char| -> bool { matches!(c, ':' | '|' | '[' | ']' | '(' | ')' | ' ') };
        let (rest, s) = take_till(stop)(input)?;
        Ok((rest, Self(s.to_string())))
    }
}

impl AstElement {
    #[inline]
    pub fn is_self_reference(&self) -> bool {
        self.0 == "&"
    }

    pub fn as_usize(&self) -> Option<usize> {
        parse_integer(&self.0).ok().map(|(_, o)| o)
    }
    pub fn as_fraction(&self) -> Option<(usize, usize)> {
        parse_fraction(&self.0).ok().map(|(_, o)| o)
    }
}

impl AstStyle {
    /// `v:v::-a-a-a-[A]`
    pub fn parse(input: &str) -> IResult<&str, Self> {
        let es = separated_list0(tag("-"), AstElement::parse);
        let (rest, rhs) = tuple((AstVariant::parse_many, opt(tag("-")), es, AstArbitrary::parse_maybe))(input)?;
        let (variants, neg, atoms, arbitrary) = rhs;
        Ok((rest, Self { negative: neg.is_some(), variants, elements: atoms, arbitrary }))
    }
    pub fn parse_list(input: &str) -> IResult<&str, Vec<Self>> {
        separated_list0(multispace1, Self::parse)(input)
    }
}

impl AstStyle {
    #[inline]
    pub fn view_elements(&self) -> Vec<&str> {
        self.elements.iter().map(|s| s.0.as_str()).collect()
    }
    #[inline]
    pub fn view_arbitrary(&self) -> &str {
        match &self.arbitrary {
            None => "",
            Some(setter) => setter.0.as_str(),
        }
    }
    // TODO
    pub fn normalization(self) -> Self {
        self
    }
}

impl AstGroup {
    pub fn parse(input: &str) -> IResult<&str, Self> {
        alt((Self::parse_grouped, Self::parse_standalone))(input)
    }
    pub fn parse_list(input: &str) -> IResult<&str, Vec<Self>> {
        separated_list0(multispace1, Self::parse)(input)
    }
    #[inline]
    fn parse_standalone(input: &str) -> IResult<&str, Self> {
        let (rest, inner) = AstStyle::parse(input)?;
        Ok((rest, Self::Standalone { inner }))
    }
    #[inline]
    fn parse_grouped(input: &str) -> IResult<&str, Self> {
        let lhs = tuple((AstVariant::parse_many, opt(AstElement::parse)));
        let rhs = delimited(char('('), AstStyle::parse_list, char(')'));
        let (rest, ((variants, elements), inner)) = tuple((lhs, rhs))(input)?;
        Ok((rest, Self::Grouped { variants, elements, inner }))
    }

    pub fn expand(s: Self, buffer: &mut Vec<AstStyle>) {
        match s {
            Self::Standalone { inner } => buffer.push(inner),
            Self::Grouped { variants: vl, elements: el, inner } => {
                for AstStyle { negative, variants: vr, elements: er, arbitrary } in inner {
                    let mut variants = vl.clone();
                    variants.extend(vr.into_iter());
                    let mut elements = match el.clone() {
                        None => vec![],
                        Some(s) => vec![s],
                    };
                    elements.extend(er.into_iter());
                    let new = AstStyle { negative, variants, elements, arbitrary };
                    buffer.push(new);
                }
            }
        }
    }
    pub fn expand_list(v: Vec<Self>) -> Vec<AstStyle> {
        let mut out = vec![];
        for i in v {
            Self::expand(i, &mut out)
        }
        out
    }
}

impl TailwindBuilder {
    pub fn parse_styles(input: &str) -> Result<Vec<AstStyle>> {
        let g = AstGroup::parse_list(input.trim())?.1;
        Ok(AstGroup::expand_list(g))
    }
}

#[test]
fn test_style() {
    // w-full sm:w-auto text-lg uppercase text-gray-100 bg-purple-800 hover:bg-purple-700 focus:bg-purple-700 focus-visible:ring-4 ring-purple-400 px-6
    println!("{:#?}", AstStyle::parse("not-hover:sm:text-red-200").unwrap().1);
    println!("{:#?}", AstStyle::parse_list("w-full sm:w-auto").unwrap().1);
}

#[test]
fn test_group() {
    println!("{:#?}", AstGroup::parse_list("w(full sm:auto)").unwrap().1);
    println!("{:#?}", AstGroup::parse_list("not-hover:sm:text-red-200").unwrap().1);
}

#[test]
fn test_group_expand() {
    println!("{:#?}", TailwindBuilder::parse_styles("w(full sm:auto)"));
}