1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 18:57:35 +00:00

LibWeb: Use CSS::Number for Token numeric values

This commit is contained in:
Sam Atkins 2022-03-21 21:01:27 +00:00 committed by Andreas Kling
parent e8ab2dab11
commit fe372cd073
5 changed files with 53 additions and 85 deletions

View file

@ -1233,14 +1233,14 @@ Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID med
// Boolean (<mq-boolean> in the spec: a 1 or 0) // Boolean (<mq-boolean> in the spec: a 1 or 0)
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) { if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Boolean)) {
if (first.is(Token::Type::Number) && first.token().number_type() == Token::NumberType::Integer if (first.is(Token::Type::Number) && first.token().number().is_integer()
&& (first.token().number_value() == 0 || first.token().number_value() == 1)) && (first.token().number_value() == 0 || first.token().number_value() == 1))
return MediaFeatureValue(first.token().number_value()); return MediaFeatureValue(first.token().number_value());
} }
// Integer // Integer
if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) { if (media_feature_accepts_type(media_feature, MediaFeatureValueType::Integer)) {
if (first.is(Token::Type::Number) && first.token().number_type() == Token::NumberType::Integer) if (first.is(Token::Type::Number) && first.token().number().is_integer())
return MediaFeatureValue(first.token().number_value()); return MediaFeatureValue(first.token().number_value());
} }
@ -2392,7 +2392,7 @@ RefPtr<StyleValue> Parser::parse_numeric_value(StyleComponentValueRule const& co
{ {
if (component_value.is(Token::Type::Number)) { if (component_value.is(Token::Type::Number)) {
auto number = component_value.token(); auto number = component_value.token();
if (number.number_type() == Token::NumberType::Integer) { if (number.number().is_integer()) {
return NumericStyleValue::create_integer(number.to_integer()); return NumericStyleValue::create_integer(number.to_integer());
} else { } else {
return NumericStyleValue::create_float(number.number_value()); return NumericStyleValue::create_float(number.number_value());
@ -2462,9 +2462,9 @@ Optional<Color> Parser::parse_color(StyleComponentValueRule const& component_val
auto g_val = params[1]; auto g_val = params[1];
auto b_val = params[2]; auto b_val = params[2];
if (r_val.is(Token::NumberType::Integer) if (r_val.is(Token::Type::Number) && r_val.number().is_integer()
&& g_val.is(Token::NumberType::Integer) && g_val.is(Token::Type::Number) && g_val.number().is_integer()
&& b_val.is(Token::NumberType::Integer)) { && b_val.is(Token::Type::Number) && b_val.number().is_integer()) {
auto r = r_val.to_integer(); auto r = r_val.to_integer();
auto g = g_val.to_integer(); auto g = g_val.to_integer();
@ -2490,9 +2490,9 @@ Optional<Color> Parser::parse_color(StyleComponentValueRule const& component_val
auto b_val = params[2]; auto b_val = params[2];
auto a_val = params[3]; auto a_val = params[3];
if (r_val.is(Token::NumberType::Integer) if (r_val.is(Token::Type::Number) && r_val.number().is_integer()
&& g_val.is(Token::NumberType::Integer) && g_val.is(Token::Type::Number) && g_val.number().is_integer()
&& b_val.is(Token::NumberType::Integer) && b_val.is(Token::Type::Number) && b_val.number().is_integer()
&& a_val.is(Token::Type::Number)) { && a_val.is(Token::Type::Number)) {
auto r = r_val.to_integer(); auto r = r_val.to_integer();
@ -2571,7 +2571,7 @@ Optional<Color> Parser::parse_color(StyleComponentValueRule const& component_val
if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) { if (cv.is(Token::Type::Number) || cv.is(Token::Type::Dimension)) {
// 1. If cvs type flag is not "integer", return an error. // 1. If cvs type flag is not "integer", return an error.
// This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse. // This means that values that happen to use scientific notation, e.g., 5e5e5e, will fail to parse.
if (cv.token().number_type() != Token::NumberType::Integer) if (!cv.token().number().is_integer())
return {}; return {};
// 2. If cvs value is less than zero, return an error. // 2. If cvs value is less than zero, return an error.
@ -4372,7 +4372,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
auto is_n_dimension = [](StyleComponentValueRule const& value) -> bool { auto is_n_dimension = [](StyleComponentValueRule const& value) -> bool {
if (!value.is(Token::Type::Dimension)) if (!value.is(Token::Type::Dimension))
return false; return false;
if (value.token().number_type() != Token::NumberType::Integer) if (!value.token().number().is_integer())
return false; return false;
if (!value.token().dimension_unit().equals_ignoring_case("n"sv)) if (!value.token().dimension_unit().equals_ignoring_case("n"sv))
return false; return false;
@ -4381,7 +4381,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
auto is_ndash_dimension = [](StyleComponentValueRule const& value) -> bool { auto is_ndash_dimension = [](StyleComponentValueRule const& value) -> bool {
if (!value.is(Token::Type::Dimension)) if (!value.is(Token::Type::Dimension))
return false; return false;
if (value.token().number_type() != Token::NumberType::Integer) if (!value.token().number().is_integer())
return false; return false;
if (!value.token().dimension_unit().equals_ignoring_case("n-"sv)) if (!value.token().dimension_unit().equals_ignoring_case("n-"sv))
return false; return false;
@ -4390,7 +4390,7 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
auto is_ndashdigit_dimension = [](StyleComponentValueRule const& value) -> bool { auto is_ndashdigit_dimension = [](StyleComponentValueRule const& value) -> bool {
if (!value.is(Token::Type::Dimension)) if (!value.is(Token::Type::Dimension))
return false; return false;
if (value.token().number_type() != Token::NumberType::Integer) if (!value.token().number().is_integer())
return false; return false;
auto dimension_unit = value.token().dimension_unit(); auto dimension_unit = value.token().dimension_unit();
if (!dimension_unit.starts_with("n-"sv, CaseSensitivity::CaseInsensitive)) if (!dimension_unit.starts_with("n-"sv, CaseSensitivity::CaseInsensitive))
@ -4426,13 +4426,13 @@ Optional<Selector::SimpleSelector::ANPlusBPattern> Parser::parse_a_n_plus_b_patt
return true; return true;
}; };
auto is_integer = [](StyleComponentValueRule const& value) -> bool { auto is_integer = [](StyleComponentValueRule const& value) -> bool {
return value.is(Token::Type::Number) && value.token().is(Token::NumberType::Integer); return value.is(Token::Type::Number) && value.token().number().is_integer();
}; };
auto is_signed_integer = [is_integer](StyleComponentValueRule const& value) -> bool { auto is_signed_integer = [](StyleComponentValueRule const& value) -> bool {
return is_integer(value) && value.token().is_integer_value_signed(); return value.is(Token::Type::Number) && value.token().number().is_integer_with_explicit_sign();
}; };
auto is_signless_integer = [is_integer](StyleComponentValueRule const& value) -> bool { auto is_signless_integer = [](StyleComponentValueRule const& value) -> bool {
return is_integer(value) && !value.token().is_integer_value_signed(); return value.is(Token::Type::Number) && !value.token().number().is_integer_with_explicit_sign();
}; };
// https://www.w3.org/TR/css-syntax-3/#the-anb-type // https://www.w3.org/TR/css-syntax-3/#the-anb-type

View file

@ -43,11 +43,11 @@ String Token::to_string() const
case Type::Delim: case Type::Delim:
return m_value; return m_value;
case Type::Number: case Type::Number:
return String::number(m_number_value); return String::number(m_number_value.value());
case Type::Percentage: case Type::Percentage:
return String::formatted("{}%", m_number_value); return String::formatted("{}%", m_number_value.value());
case Type::Dimension: case Type::Dimension:
return String::formatted("{}{}", m_number_value, m_unit); return String::formatted("{}{}", m_number_value.value(), dimension_unit());
case Type::Whitespace: case Type::Whitespace:
return " "; return " ";
case Type::CDO: case Type::CDO:
@ -122,8 +122,8 @@ String Token::to_debug_string() const
return builder.to_string(); return builder.to_string();
case Type::Number: case Type::Number:
builder.append("Number: "); builder.append("Number: ");
builder.append(m_value); builder.append(m_number_value.value());
builder.append(m_number_type == NumberType::Integer ? " (int)" : " (float)"); builder.append(m_number_value.is_integer() ? " (int)" : " (float)");
return builder.to_string(); return builder.to_string();
case Type::Percentage: case Type::Percentage:
builder.append("Percentage: "); builder.append("Percentage: ");
@ -132,8 +132,8 @@ String Token::to_debug_string() const
return builder.to_string(); return builder.to_string();
case Type::Dimension: case Type::Dimension:
builder.append("Dimension: "); builder.append("Dimension: ");
builder.append(m_value); builder.append(dimension_value());
builder.append(m_unit); builder.append(dimension_unit());
return builder.to_string(); return builder.to_string();
case Type::Whitespace: case Type::Whitespace:
builder.append("Whitespace"); builder.append("Whitespace");
@ -193,7 +193,7 @@ String Token::to_debug_string() const
if (m_type == Token::Type::Number) { if (m_type == Token::Type::Number) {
builder.append("', number_type: '"); builder.append("', number_type: '");
if (m_number_type == Token::NumberType::Integer) { if (m_number_value.is_integer()) {
builder.append("Integer"); builder.append("Integer");
} else { } else {
builder.append("Number"); builder.append("Number");
@ -202,14 +202,14 @@ String Token::to_debug_string() const
if (m_type == Token::Type::Dimension) { if (m_type == Token::Type::Dimension) {
builder.append("', number_type: '"); builder.append("', number_type: '");
if (m_number_type == Token::NumberType::Integer) { if (m_number_value.is_integer()) {
builder.append("Integer"); builder.append("Integer");
} else { } else {
builder.append("Number"); builder.append("Number");
} }
builder.append("', unit: '"); builder.append("', unit: '");
builder.append(m_unit); builder.append(dimension_unit());
} }
builder.append("' }"); builder.append("' }");

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2020-2021, the SerenityOS developers. * Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -10,7 +10,7 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <math.h> #include <LibWeb/CSS/Number.h>
namespace Web::CSS { namespace Web::CSS {
@ -52,11 +52,6 @@ public:
Unrestricted, Unrestricted,
}; };
enum class NumberType {
Integer,
Number,
};
struct Position { struct Position {
size_t line { 0 }; size_t line { 0 };
size_t column { 0 }; size_t column { 0 };
@ -112,23 +107,21 @@ public:
return m_value.view(); return m_value.view();
} }
bool is(NumberType number_type) const { return is(Token::Type::Number) && m_number_type == number_type; } Number const& number() const
StringView number_string_value() const
{ {
VERIFY(m_type == Type::Number); VERIFY(m_type == Type::Number || m_type == Type::Dimension || m_type == Type::Percentage);
return m_value.view(); return m_number_value;
} }
float number_value() const float number_value() const
{ {
VERIFY(m_type == Type::Number); VERIFY(m_type == Type::Number);
return m_number_value; return m_number_value.value();
} }
i64 to_integer() const i64 to_integer() const
{ {
VERIFY(m_type == Type::Number && m_number_type == NumberType::Integer); VERIFY(m_type == Type::Number && m_number_value.is_integer());
return to_closest_integer(m_number_value); return m_number_value.integer_value();
} }
bool is_integer_value_signed() const { return number_string_value().starts_with('-') || number_string_value().starts_with('+'); }
StringView dimension_unit() const StringView dimension_unit() const
{ {
@ -138,20 +131,14 @@ public:
float dimension_value() const float dimension_value() const
{ {
VERIFY(m_type == Type::Dimension); VERIFY(m_type == Type::Dimension);
return m_number_value; return m_number_value.value();
} }
i64 dimension_value_int() const { return to_closest_integer(dimension_value()); } i64 dimension_value_int() const { return m_number_value.integer_value(); }
float percentage() const float percentage() const
{ {
VERIFY(m_type == Type::Percentage); VERIFY(m_type == Type::Percentage);
return m_number_value; return m_number_value.value();
}
NumberType number_type() const
{
VERIFY((m_type == Type::Number) || (m_type == Type::Dimension) || (m_type == Type::Percentage));
return m_number_type;
} }
Type mirror_variant() const; Type mirror_variant() const;
@ -165,21 +152,11 @@ public:
Position const& end_position() const { return m_end_position; } Position const& end_position() const { return m_end_position; }
private: private:
static i64 to_closest_integer(float value)
{
// https://www.w3.org/TR/css-values-4/#numeric-types
// When a value cannot be explicitly supported due to range/precision limitations, it must be converted
// to the closest value supported by the implementation, but how the implementation defines "closest"
// is explicitly undefined as well.
return llroundf(value);
}
Type m_type { Type::Invalid }; Type m_type { Type::Invalid };
FlyString m_value; FlyString m_value;
FlyString m_unit; FlyString m_unit;
float m_number_value { 0 }; Number m_number_value;
NumberType m_number_type { NumberType::Integer };
HashType m_hash_type { HashType::Unrestricted }; HashType m_hash_type { HashType::Unrestricted };
Position m_start_position; Position m_start_position;

View file

@ -465,7 +465,7 @@ Token Tokenizer::consume_an_ident_like_token()
} }
// https://www.w3.org/TR/css-syntax-3/#consume-number // https://www.w3.org/TR/css-syntax-3/#consume-number
CSSNumber Tokenizer::consume_a_number() Number Tokenizer::consume_a_number()
{ {
// This section describes how to consume a number from a stream of code points. // This section describes how to consume a number from a stream of code points.
// It returns a numeric value, and a type which is either "integer" or "number". // It returns a numeric value, and a type which is either "integer" or "number".
@ -478,12 +478,14 @@ CSSNumber Tokenizer::consume_a_number()
// 1. Initially set type to "integer". Let repr be the empty string. // 1. Initially set type to "integer". Let repr be the empty string.
StringBuilder repr; StringBuilder repr;
Token::NumberType type = Token::NumberType::Integer; Number::Type type = Number::Type::Integer;
// 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-),
// consume it and append it to repr. // consume it and append it to repr.
bool has_explicit_sign = false;
auto next_input = peek_code_point(); auto next_input = peek_code_point();
if (is_plus_sign(next_input) || is_hyphen_minus(next_input)) { if (is_plus_sign(next_input) || is_hyphen_minus(next_input)) {
has_explicit_sign = true;
repr.append_code_point(next_code_point()); repr.append_code_point(next_code_point());
} }
@ -505,7 +507,7 @@ CSSNumber Tokenizer::consume_a_number()
repr.append_code_point(next_code_point()); repr.append_code_point(next_code_point());
// 3. Set type to "number". // 3. Set type to "number".
type = Token::NumberType::Number; type = Number::Type::Number;
// 4. While the next input code point is a digit, consume it and append it to repr. // 4. While the next input code point is a digit, consume it and append it to repr.
for (;;) { for (;;) {
@ -537,7 +539,7 @@ CSSNumber Tokenizer::consume_a_number()
} }
// 3. Set type to "number". // 3. Set type to "number".
type = Token::NumberType::Number; type = Number::Type::Number;
// 4. While the next input code point is a digit, consume it and append it to repr. // 4. While the next input code point is a digit, consume it and append it to repr.
for (;;) { for (;;) {
@ -553,7 +555,9 @@ CSSNumber Tokenizer::consume_a_number()
auto value = convert_a_string_to_a_number(repr.string_view()); auto value = convert_a_string_to_a_number(repr.string_view());
// 7. Return value and type. // 7. Return value and type.
return { repr.to_string(), value, type }; if (type == Number::Type::Integer && has_explicit_sign)
return Number { Number::Type::IntegerWithExplicitSign, value };
return Number { type, value };
} }
// https://www.w3.org/TR/css-syntax-3/#convert-string-to-number // https://www.w3.org/TR/css-syntax-3/#convert-string-to-number
@ -836,9 +840,7 @@ Token Tokenizer::consume_a_numeric_token()
// 1. Create a <dimension-token> with the same value and type flag as number, // 1. Create a <dimension-token> with the same value and type flag as number,
// and a unit set initially to the empty string. // and a unit set initially to the empty string.
auto token = create_new_token(Token::Type::Dimension); auto token = create_new_token(Token::Type::Dimension);
token.m_value = move(number.string); token.m_number_value = number;
token.m_number_type = number.type;
token.m_number_value = number.value;
// 2. Consume a name. Set the <dimension-token>s unit to the returned value. // 2. Consume a name. Set the <dimension-token>s unit to the returned value.
auto unit = consume_a_name(); auto unit = consume_a_name();
@ -855,17 +857,13 @@ Token Tokenizer::consume_a_numeric_token()
// Create a <percentage-token> with the same value as number, and return it. // Create a <percentage-token> with the same value as number, and return it.
auto token = create_new_token(Token::Type::Percentage); auto token = create_new_token(Token::Type::Percentage);
token.m_value = move(number.string); token.m_number_value = number;
token.m_number_type = number.type;
token.m_number_value = number.value;
return token; return token;
} }
// Otherwise, create a <number-token> with the same value and type flag as number, and return it. // Otherwise, create a <number-token> with the same value and type flag as number, and return it.
auto token = create_new_token(Token::Type::Number); auto token = create_new_token(Token::Type::Number);
token.m_value = move(number.string); token.m_number_value = number;
token.m_number_type = number.type;
token.m_number_value = number.value;
return token; return token;
} }

View file

@ -57,13 +57,6 @@ public:
u32 third {}; u32 third {};
}; };
class CSSNumber {
public:
String string;
float value { 0 };
Token::NumberType type {};
};
class Tokenizer { class Tokenizer {
public: public:
@ -89,7 +82,7 @@ private:
[[nodiscard]] Token consume_string_token(u32 ending_code_point); [[nodiscard]] Token consume_string_token(u32 ending_code_point);
[[nodiscard]] Token consume_a_numeric_token(); [[nodiscard]] Token consume_a_numeric_token();
[[nodiscard]] Token consume_an_ident_like_token(); [[nodiscard]] Token consume_an_ident_like_token();
[[nodiscard]] CSSNumber consume_a_number(); [[nodiscard]] Number consume_a_number();
[[nodiscard]] float convert_a_string_to_a_number(StringView); [[nodiscard]] float convert_a_string_to_a_number(StringView);
[[nodiscard]] String consume_a_name(); [[nodiscard]] String consume_a_name();
[[nodiscard]] u32 consume_escaped_code_point(); [[nodiscard]] u32 consume_escaped_code_point();