Skip to content

Commit

Permalink
Merge pull request #167 from cztomsik/css-tokenize
Browse files Browse the repository at this point in the history
Weekend 2021-05-09
  • Loading branch information
cztomsik authored May 9, 2021
2 parents d8604f2 + dcadb8d commit 4760ea5
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 151 deletions.
2 changes: 1 addition & 1 deletion examples/calculator.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

.display {
display: flex;
justify-content: flex-end;
/*justify-content: flex-end;*/
align-items: center;
height: 70px;
padding-left: 10px;
Expand Down
17 changes: 4 additions & 13 deletions libgraffiti/src/css/css_engine.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
use super::*;

// just a fn for now
pub(crate) fn matching_style<'a, E: Copy>(ctx: &MatchingContext<'a, E>, sheets: &'a [StyleSheet], el: E) -> Style {
let mut matching_rules: Vec<_> = sheets
pub(crate) fn matching_rules<'a, E: Copy>(ctx: &MatchingContext<'a, E>, sheets: &'a [StyleSheet], el: E) -> impl Iterator<Item = &'a Rule> + 'a {
let mut rules: Vec<_> = sheets
.iter()
.flat_map(|s| &s.rules)
.filter_map(|r| ctx.match_selector(&r.selector, el).map(move |spec| (spec, r)))
.collect();

matching_rules.sort_by(|(a, _), (b, _)| a.cmp(b));
rules.sort_by(|(a, _), (b, _)| a.cmp(b));

let mut style = Style::new();

for (_, r) in &matching_rules {
// TODO: style.merge?
for p in &r.style.props {
style.add_prop(p.clone());
}
}

style
rules.into_iter().map(|(_, r)| r)
}

#[derive(Debug, PartialEq)]
Expand Down
110 changes: 92 additions & 18 deletions libgraffiti/src/css/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
// - repeat() for skip/discard() should be alloc-free because of zero-sized types
// - collect() creates slice from start to end regardless of the results "inside"
// (which means (a + b).collect() only takes "super-slice" of both matches)
// - we are only parsing known/valid props, which means tokenizer can be simpler
// and we also get correct overriding for free (only valid prop should override prev one)

use super::{
prop_parser, Combinator, Component, CssBoxShadow, CssColor, CssDimension, Rule, Selector, SelectorPart, Style,
StyleProp, StyleSheet,
prop_parser, Combinator, Component, CssBoxShadow, CssColor, CssDimension, CssOverflow, Rule, Selector,
SelectorPart, Style, StyleProp, StyleSheet,
};
use crate::util::Atom;
use pom::char_class::alphanum;
use pom::parser::{any, empty, is_a, none_of, one_of, seq, skip, sym};
use pom::parser::{any, empty, is_a, list, none_of, one_of, seq, skip, sym};
use std::convert::TryFrom;
use std::fmt::Debug;

Expand Down Expand Up @@ -85,14 +87,26 @@ pub(super) fn style<'a>() -> Parser<'a, Style> {
let prop_value = (!sym(";") * !sym("}") * skip(1)).repeat(1..).collect();
let prop = any() - sym(":") + prop_value - sym(";").discard().repeat(0..);

prop.repeat(0..).map(|props| Style {
// skip unknown
props: props.iter().filter_map(|(p, v)| parse_style_prop(p, v).ok()).collect(),
prop.repeat(0..).map(|props| {
let mut style = Style::new();

for (p, v) in props {
// skip unknown
parse_prop_into(p, v, &mut style);
}

style
})
}

pub(super) fn parse_style_prop<'a>(prop: Token, value: &[Token]) -> Result<StyleProp, ParseError> {
prop_parser(prop).parse(value)
pub(super) fn parse_prop_into<'a>(prop: &str, value: &[&str], style: &mut Style) {
if let Ok(p) = super::prop_parser(prop).parse(value) {
style.add_prop(p);
} else if let Ok(props) = super::shorthand_parser(prop).parse(value) {
for p in props {
style.add_prop(p);
}
}
}

pub(super) fn try_from<'a, T: 'static + TryFrom<&'a str>>() -> Parser<'a, T>
Expand All @@ -111,6 +125,31 @@ pub(super) fn dimension<'a>() -> Parser<'a, CssDimension> {
px | percent | auto | zero
}

pub(super) fn sides_of<'a, V: Copy + 'a>(parser: Parser<'a, V>) -> Parser<'a, (V, V, V, V)> {
list(parser, sym(" ")).convert(|sides| {
Ok(match &sides[..] {
&[a, b, c, d] => (a, b, c, d),
&[a, b, c] => (a, b, c, b),
&[a, b] => (a, b, a, b),
&[a] => (a, a, a, a),
_ => return Err("expected 1-4 values"),
})
})
}

pub(super) fn flex<'a>() -> Parser<'a, (f32, f32, CssDimension)> {
(float() + (sym(" ") * float()).opt() + (sym(" ") * dimension()).opt())
.map(|((grow, shrink), basis)| (grow, shrink.unwrap_or(1.), basis.unwrap_or(CssDimension::Auto)))
}

pub(super) fn overflow<'a>() -> Parser<'a, (CssOverflow, CssOverflow)> {
(try_from() + (sym(" ") * try_from()).opt()).map(|(x, y)| (x, y.unwrap_or(x)))
}

pub(super) fn background<'a>() -> Parser<'a, CssColor> {
sym("none").map(|_| CssColor::TRANSPARENT) | color()
}

pub(super) fn color<'a>() -> Parser<'a, CssColor> {
fn hex_val(byte: u8) -> u8 {
(byte as char).to_digit(16).unwrap() as u8
Expand Down Expand Up @@ -170,7 +209,7 @@ pub(super) fn font_family<'a>() -> Parser<'a, Atom<String>> {
is_a(|t: &str| alphanum_dash(t.as_bytes()[0])).map(Atom::from)
}

pub(super) fn box_shadow<'a>() -> Parser<'a, CssBoxShadow> {
pub(super) fn box_shadow<'a>() -> Parser<'a, Box<CssBoxShadow>> {
fail("TODO: parse box-shadow")
}

Expand Down Expand Up @@ -316,6 +355,26 @@ mod tests {
fn shorthands() {
use StyleProp::*;

assert_eq!(
&Style::from("overflow: hidden").props,
&[OverflowX(CssOverflow::Hidden), OverflowY(CssOverflow::Hidden)]
);

assert_eq!(
&Style::from("overflow: visible hidden").props,
&[OverflowX(CssOverflow::Visible), OverflowY(CssOverflow::Hidden)]
);

assert_eq!(
&Style::from("flex: 1").props,
&[FlexGrow(1.), FlexShrink(1.), FlexBasis(CssDimension::Auto)]
);

assert_eq!(
&Style::from("flex: 2 3 10px").props,
&[FlexGrow(2.), FlexShrink(3.), FlexBasis(CssDimension::Px(10.))]
);

assert_eq!(
&Style::from("padding: 0").props,
&[
Expand All @@ -336,7 +395,10 @@ mod tests {
]
);

assert_eq!(&Style::from("background: none").props, &[]);
assert_eq!(
&Style::from("background: none").props,
&[StyleProp::BackgroundColor(CssColor::TRANSPARENT)]
);
assert_eq!(
&Style::from("background: #000").props,
&[StyleProp::BackgroundColor(CssColor::BLACK)]
Expand All @@ -350,7 +412,7 @@ mod tests {
// remove
let mut s = Style::from("background-color: #fff");
s.set_property("background", "none");
assert_eq!(s, Style::EMPTY);
assert_eq!(s.props, &[StyleProp::BackgroundColor(CssColor::TRANSPARENT)]);
}

#[test]
Expand Down Expand Up @@ -463,33 +525,45 @@ mod tests {
#[test]
fn parse_prop() {
assert_eq!(
parse_style_prop("padding-left", &["10", "px"]),
prop_parser("padding-left").parse(&["10", "px"]),
Ok(StyleProp::PaddingLeft(CssDimension::Px(10.)))
);
assert_eq!(
parse_style_prop("margin-top", &["5", "%"]),
prop_parser("margin-top").parse(&["5", "%"]),
Ok(StyleProp::MarginTop(CssDimension::Percent(5.)))
);
assert_eq!(parse_style_prop("opacity", &["1"]), Ok(StyleProp::Opacity(1.)));
assert_eq!(prop_parser("opacity").parse(&["1"]), Ok(StyleProp::Opacity(1.)));
assert_eq!(
parse_style_prop("color", &["#", "000000"]),
prop_parser("color").parse(&["#", "000000"]),
Ok(StyleProp::Color(CssColor::BLACK))
);
}

#[test]
fn parse_align() {
assert_eq!(try_from().parse(&["auto"]), Ok(CssAlign::Auto));
assert_eq!(try_from().parse(&["start"]), Ok(CssAlign::Start));
//assert_eq!(try_from().parse(&["start"]), Ok(CssAlign::Start));
assert_eq!(try_from().parse(&["flex-start"]), Ok(CssAlign::FlexStart));
assert_eq!(try_from().parse(&["center"]), Ok(CssAlign::Center));
assert_eq!(try_from().parse(&["end"]), Ok(CssAlign::End));
//assert_eq!(try_from().parse(&["end"]), Ok(CssAlign::End));
assert_eq!(try_from().parse(&["flex-end"]), Ok(CssAlign::FlexEnd));
assert_eq!(try_from().parse(&["stretch"]), Ok(CssAlign::Stretch));
assert_eq!(try_from().parse(&["baseline"]), Ok(CssAlign::Baseline));
assert_eq!(try_from().parse(&["space-between"]), Ok(CssAlign::SpaceBetween));
assert_eq!(try_from().parse(&["space-around"]), Ok(CssAlign::SpaceAround));
assert_eq!(try_from().parse(&["space-evenly"]), Ok(CssAlign::SpaceEvenly));
//assert_eq!(try_from().parse(&["space-evenly"]), Ok(CssAlign::SpaceEvenly));
}

#[test]
fn parse_justify() {
//assert_eq!(try_from().parse(&["start"]), Ok(CssJustify::Start));
assert_eq!(try_from().parse(&["flex-start"]), Ok(CssJustify::FlexStart));
assert_eq!(try_from().parse(&["center"]), Ok(CssJustify::Center));
//assert_eq!(try_from().parse(&["end"]), Ok(CssJustify::End));
assert_eq!(try_from().parse(&["flex-end"]), Ok(CssJustify::FlexEnd));
assert_eq!(try_from().parse(&["space-between"]), Ok(CssJustify::SpaceBetween));
assert_eq!(try_from().parse(&["space-around"]), Ok(CssJustify::SpaceAround));
assert_eq!(try_from().parse(&["space-evenly"]), Ok(CssJustify::SpaceEvenly));
}

#[test]
Expand Down
Loading

0 comments on commit 4760ea5

Please sign in to comment.