Skip to content

Commit

Permalink
web-css: cssom builder and parser improvements 🔨
Browse files Browse the repository at this point in the history
  • Loading branch information
Louciole committed May 8, 2024
1 parent 581019c commit f81a201
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 57 deletions.
35 changes: 35 additions & 0 deletions src/web/web-css/ast.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <karm-io/funcs.h>

#include "lexer.h"

namespace Web::Css {

struct Ast;

using Content = Vec<Ast>;

#define FOREACH_AST(AST) \
AST(QUALIFIED_RULE) \
AST(FUNC) \
AST(DECL) \
AST(LIST) \
AST(TOKEN) \
AST(BLOCK)

struct Ast {
enum struct _Type {
#define ITER(NAME) NAME,
FOREACH_AST(ITER)
#undef ITER
};

using enum _Type;

_Type type;
Opt<Token> token{};
Opt<Box<Ast>> prefix{};
Content content{};

Ast(_Type type) : type(type) {}
};
} // namespace Web::Css
79 changes: 79 additions & 0 deletions src/web/web-css/builder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#pragma once

#include <web-cssom/stylesheet.h>

#include "ast.h"

namespace Web::Css {

static CSSOM::CSSStyleRule parseQualifiedRule(Ast rule) {
CSSOM::CSSStyleRule parsed;

auto &prefix = rule.prefix.unwrap()->content;
for (usize i = 0; i < prefix.len(); i++) {
switch (prefix[i].type) {
case Ast::QUALIFIED_RULE:
case Ast::FUNC:
case Ast::DECL:
case Ast::LIST:
case Ast::BLOCK:
break;
case Ast::TOKEN:
parsed.selector.add(prefix[i].token.unwrap());
break;
}
}

auto block = rule.content[0].content;
bool parsingContent = false;
for (usize i = 0; i < block.len(); i++) {
switch (block[i].type) {
case Ast::QUALIFIED_RULE:
case Ast::FUNC:
case Ast::DECL:
case Ast::LIST:
case Ast::BLOCK:
break;
case Ast::TOKEN:
if (!parsingContent) {
if (block[i].token->type != Token::WHITESPACE) {
if (block[i].token->type != Token::COLON) {
parsed.declarations.add(CSSOM::CSSStyleDeclaration(block[i].token.unwrap()));
} else {
parsingContent = true;
}
}
} else {
if (block[i].token->type != Token::SEMICOLON) {
last(parsed.declarations).value.add(block[i].token.unwrap());
} else {
parsingContent = false;
}
}
break;
}
}
return parsed;
}

// No spec, we take the AST we built and convert it to a usable list of rules
Vec<CSSOM::CSSStyleRule> parseAST(Ast ast) {
Vec<CSSOM::CSSStyleRule> rules;
for (usize i = 0; i < ast.content.len(); i++) {
switch (ast.content[i].type) {

case Ast::_Type::QUALIFIED_RULE:
rules.add(parseQualifiedRule(ast.content[i]));
break;
case Ast::_Type::FUNC:
case Ast::_Type::DECL:
case Ast::_Type::LIST:
case Ast::_Type::TOKEN:
case Ast::_Type::BLOCK:
break;
}
}
return rules;
}

} // namespace Web::Css
2 changes: 1 addition & 1 deletion src/web/web-css/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Res<> entryPoint(Sys::Ctx &) {
Sys::println("Parsing:");
try$(Web::Css::parseStylesheet("bundle://web-css-cli/exemple_2.css"));
try$(Web::Css::parseStylesheet("bundle://web-css-cli/exemple_1.css"));

Sys::println("Result:");
Io::Emit emit{Sys::out()};
Expand Down
2 changes: 1 addition & 1 deletion src/web/web-css/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ Res<Token> nextToken(Io::SScan &s) {
return Ok(Token{Token::Type::HASH, s.end()});
}

// https://www.w3.org/TR/css-syntax-3/#consume-numeric-token
// https://www.w3.org/TR/css-syntax-3/#consume-comment
if (s.skip("/*")) {
s.skip(Re::untilAndConsume(Re::word("*/")));

Expand Down
101 changes: 47 additions & 54 deletions src/web/web-css/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,39 @@
#include <karm-sys/file.h>
#include <web-cssom/stylesheet.h>

#include "builder.h"
#include "lexer.h"

namespace Web::Css {

struct Ast;

using Block = Vec<Box<Ast>>;

struct Ast {
enum struct Type {
QUALIFIED_RULE,
FUNC,
DECL,
LIST,
BLOCK,
TOKEN,
};

using enum Type;

Type type;
Opt<Token> token;
Opt<Box<Ast>> pre;
Block block;
};

Res<Ast> _consumeComponentValue(Io::SScan &s, Token token);

// https://www.w3.org/TR/css-syntax-3/#consume-a-simple-block
Res<Ast> _consumeBlock(Io::SScan &s, Token::Type term) {
Ast block;
block.type = Ast::Type::BLOCK;
Ast block = Ast::BLOCK;

while (true) {
auto token = nextToken(s).unwrap();

switch (token.type) {
case Token::Type::END_OF_FILE:
case Token::END_OF_FILE:
// this is a parse error
return Ok(std::move(block));
return Ok(block);
default:
if (token.type == term) {
return Ok(std::move(block));
logDebug("closing the curent block");
return Ok(block);
} else {
block.block.emplaceBack(try$(_consumeComponentValue(s, token)));
auto a = try$(_consumeComponentValue(s, token));
logDebug("adding {#} to the curent block", a);
block.content.emplaceBack(a);
}
}
}
}

// https://www.w3.org/TR/css-syntax-3/#consume-function
Res<Ast> _consumeFunc(Io::SScan &s) {
Ast fn;
fn.type = Ast::Type::FUNC;
Ast fn = Ast::FUNC;

while (true) {
auto token = nextToken(s).unwrap();
Expand All @@ -71,7 +50,7 @@ Res<Ast> _consumeFunc(Io::SScan &s) {
// this is a parse error
return Ok(std::move(fn));
default:
fn.block.emplaceBack(try$(_consumeComponentValue(s, token)));
fn.content.emplaceBack(try$(_consumeComponentValue(s, token)));
}
}
}
Expand All @@ -92,73 +71,87 @@ Res<Ast> _consumeComponentValue(Io::SScan &s, Token token) {
return _consumeFunc(s);

default:
Ast tok;
tok.type = Ast::TOKEN;
Ast tok = Ast::TOKEN;
tok.token = token;
return Ok(std::move(tok));
}
}

// https://www.w3.org/TR/css-syntax-3/#consume-qualified-rule
Res<Ast> _consumeQualifiedRule(Io::SScan &s, Token token) {
Ast ast;
ast.type = Ast::QUALIFIED_RULE;
Ast ast{Ast::QUALIFIED_RULE};
Ast pre{Ast::LIST};

while (true) {
switch (token.type) {
case Token::Type::END_OF_FILE:
case Token::END_OF_FILE:
return Error();
case Token::Type::LEFT_CURLY_BRACKET:
ast.block.emplaceBack(try$(_consumeBlock(s, Token::RIGHT_CURLY_BRACKET)));
case Token::LEFT_CURLY_BRACKET:
logDebug("creating a block for curent rule");
ast.prefix = Box{pre};
ast.content.emplaceBack(try$(_consumeBlock(s, Token::RIGHT_CURLY_BRACKET)));
return Ok(ast);
default:
ast.pre.emplace(try$(_consumeComponentValue(s, token)));
auto a = try$(_consumeComponentValue(s, token));
logDebug("adding a new component value to the prefix {#}", a);
pre.content.pushBack(a);
break;
}
token = nextToken(s).unwrap();
}

return Ok(ast);
}

// https://www.w3.org/TR/css-syntax-3/#consume-list-of-rules
Res<Ast> _consumeRuleList(Str str) {
auto s = Io::SScan(str);
Ast ast;
ast.type = Ast::LIST;
Ast ast = Ast::LIST;

while (true) {
auto token = try$(nextToken(s));
switch (token.type) {
case Token::END_OF_FILE:
return Ok(ast);
case Token::COMMENT:
case Token::WHITESPACE:
break;
default:
ast.block.pushBack(try$(_consumeQualifiedRule(s, token)));
ast.content.pushBack(try$(_consumeQualifiedRule(s, token)));
break;
}
}
}

// No spec, we take the AST we built and convert it to a usable list of rules
Vec<CSSOM::CSSStyleRule> parseAST(Ast) {
Vec<CSSOM::CSSStyleRule> rules;

return rules;
}

// https://www.w3.org/TR/css-syntax-3/#parse-stylesheet
Res<> parseStylesheet(Str source) {
CSSOM::StyleSheet stylesheet = CSSOM::StyleSheet(source);
auto file = try$(Sys::File::open(Mime::parseUrlOrPath(source).unwrap()));
auto buf = try$(Io::readAllUtf8(file));
Ast ast = try$(_consumeRuleList(buf));
logDebug("AST : {#}", ast);
stylesheet.cssRules = parseAST(ast);
logDebug("AST : {}", ast);
logDebug("RULES : {#}", stylesheet.cssRules);
return Ok();
}

static inline Str toStr(Ast::_Type type) {
switch (type) {
#define ITER(NAME) \
case Ast::NAME: \
return #NAME;
FOREACH_AST(ITER)
#undef ITER
default:
panic("invalid ast type");
}
}

} // namespace Web::Css

Reflectable$(Web::Css::Ast, token, pre, block);
Reflectable$(Web::Css::Ast, type, token, prefix, content);

template <>
struct Karm::Io::Formatter<Web::Css::Ast::_Type> {
Res<usize> format(Io::TextWriter &writer, Web::Css::Ast::_Type val) {
return (writer.writeStr(try$(Io::toParamCase(toStr(val)))));
}
};
Loading

0 comments on commit f81a201

Please sign in to comment.