LibWeb/CSS: Use NumericCalculationNode for constants

Having multiple kinds of node that hold numeric values made things more
complicated than they needed to be, and we were already converting
ConstantCalculationNodes to NumericCalculationNodes in the first
simplification pass that happens at parse-time, so they didn't exist
after that.

As noted, the spec allows for other contexts to introduce their own
numeric keywords, which might be resolved later than parse-time. We'll
need a different mechanism to support those, but
ConstantCalculationNode could not have done so anyway.
This commit is contained in:
Sam Atkins 2025-02-25 16:37:10 +00:00 committed by Andreas Kling
commit f97ac33cb3
Notes: github-actions[bot] 2025-02-27 20:43:53 +00:00
6 changed files with 45 additions and 140 deletions

View file

@ -1,4 +1,5 @@
[
"-infinity",
"-libweb-center",
"-libweb-left",
"-libweb-link",
@ -151,6 +152,7 @@
"dotted",
"double",
"down",
"e",
"e-resize",
"ease",
"ease-in",
@ -207,6 +209,7 @@
"inactivecaption",
"inactivecaptiontext",
"infinite",
"infinity",
"infobackground",
"infotext",
"inherit",
@ -285,6 +288,7 @@
"move",
"multiply",
"n-resize",
"nan",
"ne-resize",
"nearest",
"nesw-resize",
@ -325,6 +329,7 @@
"paint",
"paused",
"petite-caps",
"pi",
"pixelated",
"plaintext",
"plus-darker",

View file

@ -3340,10 +3340,10 @@ RefPtr<CalculationNode> Parser::convert_to_calculation_node(CalcParsing::Node co
// AD-HOC: We also need to convert tokens into their numeric types.
if (component_value->is(Token::Type::Ident)) {
auto maybe_constant = CalculationNode::constant_type_from_string(component_value->token().ident());
if (!maybe_constant.has_value())
auto maybe_keyword = keyword_from_string(component_value->token().ident());
if (!maybe_keyword.has_value())
return nullptr;
return ConstantCalculationNode::create(*maybe_constant);
return NumericCalculationNode::from_keyword(*maybe_keyword, context);
}
if (component_value->is(Token::Type::Number))

View file

@ -129,26 +129,6 @@ static NonnullRefPtr<CalculationNode> simplify_2_children(T const& original, Non
return original;
}
Optional<CalculationNode::ConstantType> CalculationNode::constant_type_from_string(StringView string)
{
if (string.equals_ignoring_ascii_case("e"sv))
return CalculationNode::ConstantType::E;
if (string.equals_ignoring_ascii_case("pi"sv))
return CalculationNode::ConstantType::Pi;
if (string.equals_ignoring_ascii_case("infinity"sv))
return CalculationNode::ConstantType::Infinity;
if (string.equals_ignoring_ascii_case("-infinity"sv))
return CalculationNode::ConstantType::MinusInfinity;
if (string.equals_ignoring_ascii_case("NaN"sv))
return CalculationNode::ConstantType::NaN;
return {};
}
CalculationNode::CalculationNode(Type type, Optional<CSSNumericType> numeric_type)
: m_type(type)
, m_numeric_type(move(numeric_type))
@ -228,6 +208,29 @@ NonnullRefPtr<NumericCalculationNode> NumericCalculationNode::create(NumericValu
return adopt_ref(*new (nothrow) NumericCalculationNode(move(value), numeric_type));
}
RefPtr<NumericCalculationNode> NumericCalculationNode::from_keyword(Keyword keyword, CalculationContext const& context)
{
switch (keyword) {
case Keyword::E:
// https://drafts.csswg.org/css-values-4/#valdef-calc-e
return create(Number { Number::Type::Number, AK::E<double> }, context);
case Keyword::Pi:
// https://drafts.csswg.org/css-values-4/#valdef-calc-pi
return create(Number { Number::Type::Number, AK::Pi<double> }, context);
case Keyword::Infinity:
// https://drafts.csswg.org/css-values-4/#valdef-calc-infinity
return create(Number { Number::Type::Number, AK::Infinity<double> }, context);
case Keyword::NegativeInfinity:
// https://drafts.csswg.org/css-values-4/#valdef-calc--infinity
return create(Number { Number::Type::Number, -AK::Infinity<double> }, context);
case Keyword::Nan:
// https://drafts.csswg.org/css-values-4/#valdef-calc-nan
return create(Number { Number::Type::Number, AK::NaN<double> }, context);
default:
return nullptr;
}
}
NumericCalculationNode::NumericCalculationNode(NumericValue value, CSSNumericType numeric_type)
: CalculationNode(Type::Numeric, move(numeric_type))
, m_value(move(value))
@ -1114,73 +1117,6 @@ bool SignCalculationNode::equals(CalculationNode const& other) const
return m_value->equals(*static_cast<SignCalculationNode const&>(other).m_value);
}
NonnullRefPtr<ConstantCalculationNode> ConstantCalculationNode::create(ConstantType constant)
{
return adopt_ref(*new (nothrow) ConstantCalculationNode(constant));
}
ConstantCalculationNode::ConstantCalculationNode(ConstantType constant)
// https://www.w3.org/TR/css-values-4/#determine-the-type-of-a-calculation
// Anything else is a terminal value, whose type is determined based on its CSS type:
// -> <calc-constant>
// the type is «[ ]» (empty map)
: CalculationNode(Type::Constant, CSSNumericType {})
, m_constant(constant)
{
}
ConstantCalculationNode::~ConstantCalculationNode() = default;
String ConstantCalculationNode::to_string() const
{
switch (m_constant) {
case CalculationNode::ConstantType::E:
return "e"_string;
case CalculationNode::ConstantType::Pi:
return "pi"_string;
case CalculationNode::ConstantType::Infinity:
return "infinity"_string;
case CalculationNode::ConstantType::MinusInfinity:
return "-infinity"_string;
case CalculationNode::ConstantType::NaN:
return "NaN"_string;
}
VERIFY_NOT_REACHED();
}
CalculatedStyleValue::CalculationResult ConstantCalculationNode::resolve(CalculationResolutionContext const&) const
{
switch (m_constant) {
case ConstantType::E:
return { AK::E<double>, CSSNumericType {} };
case ConstantType::Pi:
return { AK::Pi<double>, CSSNumericType {} };
case ConstantType::Infinity:
return { AK::Infinity<double>, CSSNumericType {} };
case ConstantType::MinusInfinity:
return { -AK::Infinity<double>, CSSNumericType {} };
case ConstantType::NaN:
return { AK::NaN<double>, CSSNumericType {} };
}
VERIFY_NOT_REACHED();
}
void ConstantCalculationNode::dump(StringBuilder& builder, int indent) const
{
builder.appendff("{: >{}}CONSTANT: {}\n", "", indent, to_string());
}
bool ConstantCalculationNode::equals(CalculationNode const& other) const
{
if (this == &other)
return true;
if (type() != other.type())
return false;
return m_constant == static_cast<ConstantCalculationNode const&>(other).m_constant;
}
NonnullRefPtr<SinCalculationNode> SinCalculationNode::create(NonnullRefPtr<CalculationNode> value)
{
return adopt_ref(*new (nothrow) SinCalculationNode(move(value)));
@ -2795,24 +2731,13 @@ NonnullRefPtr<CalculationNode> simplify_a_calculation_tree(CalculationNode const
}
// 3. If root is a <calc-keyword> that can be resolved, return what it resolves to, simplified.
// NOTE: This is handled below.
// NOTE: We already resolve our `<calc-keyword>`s at parse-time.
// FIXME: Revisit this once we support any keywords that need resolving later.
// 4. Otherwise, return root.
return root;
}
// AD-HOC: Step 1.3 is done here as we have a separate node type for them.
if (root->type() == CalculationNode::Type::Constant) {
auto const& root_constant = as<ConstantCalculationNode>(*root);
// 3. If root is a <calc-keyword> that can be resolved, return what it resolves to, simplified.
// FIXME: At the moment these are all constant numbers. Revisit this once that's not the case.
// (Notably, relative-color syntax allows some other keywords that are relative to the color.)
auto resolved = root_constant.resolve(resolution_context);
VERIFY(resolved.type()->matches_number({}));
return NumericCalculationNode::create(Number { Number::Type::Number, resolved.value() }, context);
}
// 2. If root is any other leaf node (not an operator node):
// FIXME: We don't yet allow any of these inside a calculation tree. Revisit once we do.

View file

@ -128,17 +128,6 @@ private:
// https://www.w3.org/TR/css-values-4/#calculation-tree
class CalculationNode : public RefCounted<CalculationNode> {
public:
// https://drafts.csswg.org/css-values-4/#calc-constants
// https://drafts.csswg.org/css-values-4/#calc-error-constants
enum class ConstantType {
E,
Pi,
NaN,
Infinity,
MinusInfinity,
};
static Optional<ConstantType> constant_type_from_string(StringView);
enum class Type {
Numeric,
// NOTE: Currently, any value with a `var()` or `attr()` function in it is always an
@ -162,10 +151,6 @@ public:
Abs,
Sign,
// Constant Nodes
// https://drafts.csswg.org/css-values-4/#calc-constants
Constant,
// Trigonometric functions, a sub-type of operator node
// https://drafts.csswg.org/css-values-4/#trig-funcs
Sin,
@ -260,6 +245,7 @@ private:
class NumericCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<NumericCalculationNode> create(NumericValue, CalculationContext const&);
static RefPtr<NumericCalculationNode> from_keyword(Keyword, CalculationContext const&);
~NumericCalculationNode();
virtual String to_string() const override;
@ -461,24 +447,6 @@ private:
NonnullRefPtr<CalculationNode> m_value;
};
class ConstantCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<ConstantCalculationNode> create(CalculationNode::ConstantType);
~ConstantCalculationNode();
virtual String to_string() const override;
virtual bool contains_percentage() const override { return false; }
virtual CalculatedStyleValue::CalculationResult resolve(CalculationResolutionContext const&) const override;
virtual NonnullRefPtr<CalculationNode> with_simplified_children(CalculationContext const&, CalculationResolutionContext const&) const override { return *this; }
virtual void dump(StringBuilder&, int indent) const override;
virtual bool equals(CalculationNode const&) const override;
private:
ConstantCalculationNode(ConstantType);
CalculationNode::ConstantType m_constant;
};
class SinCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<SinCalculationNode> create(NonnullRefPtr<CalculationNode>);

View file

@ -39,6 +39,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
return 0;
}
static String keyword_name(String const& dashy_name)
{
if (dashy_name == "-infinity"sv)
return "NegativeInfinity"_string;
return title_casify(dashy_name);
}
ErrorOr<void> generate_header_file(JsonArray& keyword_data, Core::File& file)
{
StringBuilder builder;
@ -57,7 +64,7 @@ enum class Keyword {
keyword_data.for_each([&](auto& name) {
auto member_generator = generator.fork();
member_generator.set("name:titlecase", title_casify(name.as_string()));
member_generator.set("name:titlecase", keyword_name(name.as_string()));
member_generator.append(R"~~~(
@name:titlecase@,
@ -107,7 +114,7 @@ HashMap<StringView, Keyword, AK::CaseInsensitiveASCIIStringViewTraits> g_stringv
keyword_data.for_each([&](auto& name) {
auto member_generator = generator.fork();
member_generator.set("name", name.as_string());
member_generator.set("name:titlecase", title_casify(name.as_string()));
member_generator.set("name:titlecase", keyword_name(name.as_string()));
member_generator.append(R"~~~(
{"@name@"sv, Keyword::@name:titlecase@},
)~~~");
@ -128,7 +135,7 @@ StringView string_from_keyword(Keyword keyword) {
keyword_data.for_each([&](auto& name) {
auto member_generator = generator.fork();
member_generator.set("name", name.as_string());
member_generator.set("name:titlecase", title_casify(name.as_string()));
member_generator.set("name:titlecase", keyword_name(name.as_string()));
member_generator.append(R"~~~(
case Keyword::@name:titlecase@:
return "@name@"sv;

View file

@ -251,7 +251,7 @@ RefPtr<CalculationNode> Parser::parse_math_function(Function const& function, Ca
// NOTE: We have exactly one default value in the data right now, and it's a `<calc-constant>`,
// so that's all we handle.
if (auto default_value = parameter.get_string("default"sv); default_value.has_value()) {
parameter_generator.set("parameter_default", MUST(String::formatted(" = ConstantCalculationNode::create(CalculationNode::constant_type_from_string(\"{}\"sv).value())", default_value.value())));
parameter_generator.set("parameter_default", MUST(String::formatted(" = NumericCalculationNode::from_keyword(Keyword::{}, context)", title_casify(default_value.value()))));
} else {
parameter_generator.set("parameter_default", ""_string);
}