Skip to content

Commit

Permalink
vaev-style: Add CSS variables, allow nested variables and add default…
Browse files Browse the repository at this point in the history
… values.
  • Loading branch information
Louciole authored and sleepy-monax committed Nov 18, 2024
1 parent 43ba29d commit 810d84a
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/vaev-css/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct Token {
using enum Type;

Type type;
Str data;
String data;

#define ITER(ID, NAME) \
static Token NAME(Str data = "") { return {ID, data}; }
Expand All @@ -62,7 +62,7 @@ struct Token {

Token() : type(NIL) {}

Token(Type type, Str data = "")
Token(Type type, String data = ""s)
: type(type), data(data) {}

explicit operator bool() const {
Expand Down
5 changes: 5 additions & 0 deletions src/vaev-style/computed.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <vaev-base/text.h>
#include <vaev-base/visibility.h>
#include <vaev-base/z-index.h>
#include <vaev-css/parser.h>

namespace Vaev::Style {

Expand Down Expand Up @@ -64,6 +65,8 @@ struct Computed {
Cow<FlexProps> flex;
Cow<BreakProps> break_;

Cow<Map<String, Css::Content>> variables;

Float float_ = Float::NONE;
Clear clear = Clear::NONE;

Expand All @@ -74,6 +77,7 @@ struct Computed {
color = parent.color;
font = parent.font;
text = parent.text;
variables = parent.variables;
}

void repr(Io::Emit &e) const {
Expand Down Expand Up @@ -103,6 +107,7 @@ struct Computed {
e(" float: {}", float_);
e(" clear: {}", clear);
e(" zIndex: {}", zIndex);
e(" variables: {}", variables);
e(")");
}
};
Expand Down
69 changes: 67 additions & 2 deletions src/vaev-style/computer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Computed const &Computed::initial() {
static Computed computed = [] {
Computed res{};
StyleProp::any([&]<typename T>(Meta::Type<T>) {
T{}.apply(res);
if constexpr (requires { T::initial(); })
T{}.apply(res);
});
return res;
}();
Expand All @@ -31,6 +32,61 @@ void Computer::_evalRule(Rule const &rule, Markup::Element const &el, MatchingRu
});
}

static Css::Content expandVariables(Cursor<Css::Sst> &cursor, Map<String, Css::Content> computedVars) {
// replacing the var with its value in the cascade
Css::Content computedDecl = {};
while (not cursor.ended()) {
if (cursor.peek() == Css::Sst::FUNC and cursor.peek().prefix == Css::Token::function("var(")) {

auto const varName = cursor->content[0].token.data;
// if the variable is defined in the cascade
if (computedVars.has(varName)) {
computedDecl.pushBack(computedVars.get(varName));
} else {
if (cursor->content.len() > 2) {
// using the default value
Cursor<Css::Sst> cur = cursor->content;
cur.next(2);
eatWhitespace(cur);
auto defaultValue = expandVariables(cur, computedVars);
computedDecl.pushBack(defaultValue);
} else {
// invalid property
logWarn("variable not found: {} {}", varName, cursor->content);
}
}
cursor.next();
} else if (cursor.peek() == Css::Sst::FUNC) {
Cursor<Css::Sst> cur = cursor->content;
computedDecl.pushBack(cursor.peek());
computedDecl[computedDecl.len() - 1].content = expandVariables(cur, computedVars);
cursor.next();
} else {
computedDecl.pushBack(cursor.peek());
cursor.next();
}
}
return computedDecl;
}

static Res<StyleProp> resolve(DeferredProp const &prop, Map<String, Css::Content> computedVars) {
Cursor<Css::Sst> cursor = prop.value;

auto computedDecl = expandVariables(cursor, computedVars);

// building the declaration
Css::Sst decl{Css::Sst::DECL};
decl.token = Css::Token::ident(prop.propName);
decl.content = computedDecl;

// parsing the declaration
Res<StyleProp> computed = parseDeclaration<StyleProp>(decl, false);
if (not computed) {
logWarn("failed to parse declaration: {}", computed.none());
}
return computed;
}

Strong<Computed> Computer::computeFor(Computed const &parent, Markup::Element const &el) {
MatchingRules matchingRules;

Expand All @@ -51,6 +107,7 @@ Strong<Computed> Computer::computeFor(Computed const &parent, Markup::Element co

// Get the style attribute if any
auto styleAttr = el.getAttribute(Html::STYLE_ATTR);

StyleRule styleRule{
.selector = UNIVERSAL,
.props = parseDeclarations<StyleProp>(styleAttr ? *styleAttr : ""),
Expand All @@ -62,7 +119,15 @@ Strong<Computed> Computer::computeFor(Computed const &parent, Markup::Element co
computed->inherit(parent);

for (auto const &styleRule : matchingRules) {
for (auto const &prop : styleRule->props) {
for (auto &prop : styleRule->props) {
if (auto deferred = prop.is<DeferredProp>()) {
auto resolved = resolve(prop.unwrap<DeferredProp>(), computed->variables.cow());

if (not resolved) {
continue;
}
resolved.unwrap().apply(*computed);
}
if (prop.important == Important::NO)
prop.apply(*computed);
}
Expand Down
88 changes: 54 additions & 34 deletions src/vaev-style/decls.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ Res<T> parseDeclarationValue(Cursor<Css::Sst> &c) {

return Ok(std::move(t));
} else {
logDebug("missing parser for declaration: {}", T::name());
logError("missing parser for declaration: {}", T::name());
return Error::notImplemented("missing parser for declaration");
}
}

template <typename P>
Res<P> parseDeclaration(Css::Sst const &sst) {
Res<P> parseDeclaration(Css::Sst const &sst, bool allowDeferred = true) {
if (sst != Css::Sst::DECL)
panic("expected declaration");

Expand All @@ -30,32 +30,52 @@ Res<P> parseDeclaration(Css::Sst const &sst) {

Res<P> resDecl = Error::invalidData("unknown declaration");

P::any([&]<typename T>(Meta::Type<T>) -> bool {
if (sst.token != Css::Token::ident(T::name()))
return false;

Cursor<Css::Sst> cursor = sst.content;

auto res = parseDeclarationValue<T>(cursor);
if (not res) {
resDecl = res.none();
return true;
}

resDecl = Ok(res.take());

if constexpr (requires { P::important; }) {
if (cursor.skip(Css::Token::delim("!")) and cursor.skip(Css::Token::ident("important")))
resDecl.unwrap().important = Important::YES;
}

eatWhitespace(cursor);
if (not cursor.ended()) {
resDecl = Error::invalidData("unknown tokens in content");
}

return true;
});
P::any(
Visitor{
[&](Meta::Type<CustomProp>) -> bool {
if constexpr (requires(P &p) { p.template unwrap<CustomProp>(); }) {
if (startWith(sst.token.data, "--"s) == Match::NO) {
return false;
}

resDecl = Ok(CustomProp(sst.token.data, sst.content));
return true;
}
return false;
},
[&]<typename T>(Meta::Type<T>) -> bool {
if (sst.token != Css::Token::ident(T::name())) {
return false;
}
Cursor<Css::Sst> cursor = sst.content;

auto res = parseDeclarationValue<T>(cursor);

if (not res and allowDeferred) {
if constexpr (Meta::Constructible<P, DeferredProp>) {
resDecl = Ok(DeferredProp{T::name(), sst.content});
return true;
}
} else if (not res) {
resDecl = res.none();
return true;
} else {
resDecl = Ok(res.take());
}

if constexpr (requires { P::important; }) {
if (cursor.skip(Css::Token::delim("!")) and cursor.skip(Css::Token::ident("important")))
resDecl.unwrap().important = Important::YES;
}

eatWhitespace(cursor);
if (not cursor.ended())
resDecl = Error::invalidData("unknown tokens in content");

return true;
}
} // namespace Vaev::Style
);

if (not resDecl)
logWarn("failed to parse declaration: {} - {}", sst, resDecl);
Expand All @@ -64,7 +84,7 @@ Res<P> parseDeclaration(Css::Sst const &sst) {
}

template <typename P>
Vec<P> parseDeclarations(Css::Content const &sst) {
Vec<P> parseDeclarations(Css::Content const &sst, bool allowDeferred = true) {
Vec<P> res;

for (auto const &item : sst) {
Expand All @@ -73,7 +93,7 @@ Vec<P> parseDeclarations(Css::Content const &sst) {
continue;
}

auto prop = parseDeclaration<P>(item);
auto prop = parseDeclaration<P>(item, allowDeferred);

if (not prop) {
logWarn("failed to parse declaration: {}", prop.none());
Expand All @@ -86,15 +106,15 @@ Vec<P> parseDeclarations(Css::Content const &sst) {
}

template <typename P>
Vec<P> parseDeclarations(Css::Sst const &sst) {
return parseDeclarations<P>(sst.content);
Vec<P> parseDeclarations(Css::Sst const &sst, bool allowDeferred = true) {
return parseDeclarations<P>(sst.content, allowDeferred);
}

template <typename P>
Vec<P> parseDeclarations(Str style) {
Vec<P> parseDeclarations(Str style, bool allowDeferred = true) {
Css::Lexer lex{style};
auto sst = Css::consumeDeclarationList(lex, true);
return parseDeclarations<P>(sst);
return parseDeclarations<P>(sst, allowDeferred);
}

} // namespace Vaev::Style
2 changes: 1 addition & 1 deletion src/vaev-style/media.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ static Style::Feature _parseMediaFeature(Cursor<Css::Sst> &c) {
}

auto unexplodedName = c.next().token.data;
auto [prefix, name] = _explodeFeatureName(unexplodedName);
auto [prefix, name] = _explodeFeatureName(unexplodedName.str());

Opt<Style::Feature> prop;

Expand Down
2 changes: 1 addition & 1 deletion src/vaev-style/rules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ void FontFaceRule::repr(Io::Emit &e) const {
}

FontFaceRule FontFaceRule::parse(Css::Sst const &sst) {
return {parseDeclarations<FontDesc>(sst)};
return {parseDeclarations<FontDesc>(sst, false)};
}

// MARK: Rule ------------------------------------------------------------------
Expand Down
45 changes: 44 additions & 1 deletion src/vaev-style/styles.h
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,45 @@ struct ZIndexProp {
}
};

// MARK: OTHER -----------------------------------------------------------------
// These are no specs or behave differently than the others, you can find more details for each one in the comments.

// https://drafts.csswg.org/css-variables/#defining-variables
// this symbolizes a custom property, it starts with `--` and can be used to store a value that can be reused in the stylesheet
struct CustomProp {
String varName;
Css::Content value;

CustomProp(String varName, Css::Content value)
: varName(varName), value(value) {
}

static constexpr Str name() { return "custom prop"; }

void apply(Computed &c) const {
c.variables.cow().put(varName, value);
}

void repr(Io::Emit &e) const {
e("(var {#} = {})", varName, value);
}
};

// NOSPEC: This is a property that could not be parsed, it's used to store the value as is and apply it with the cascade and custom properties
struct DeferredProp {
String propName;
Css::Content value;

static constexpr Str name() { return "deferred prop"; }

void apply(Computed &) const {
}

void repr(Io::Emit &e) const {
e("(Deffered {#} = {})", propName, value);
}
};

// MARK: Style Property -------------------------------------------------------

using _StyleProp = Union<
Expand Down Expand Up @@ -2293,7 +2332,11 @@ using _StyleProp = Union<
WhiteSpaceProp,

// ZIndex
ZIndexProp
ZIndexProp,

// Other
CustomProp, // this is generally a variable declaration
DeferredProp

/**/
>;
Expand Down
Loading

0 comments on commit 810d84a

Please sign in to comment.