Skip to content

Commit 1f83498

Browse files
committed
feat: add CSS calc constants and operators support
1 parent f10cded commit 1f83498

File tree

5 files changed

+206
-1
lines changed

5 files changed

+206
-1
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <optional>
11+
#include <string_view>
12+
13+
#include <react/utils/fnv1a.h>
14+
15+
namespace facebook::react {
16+
17+
/**
18+
* Numeric keyword constants usable within CSS math functions.
19+
* https://www.w3.org/TR/css-values-4/#calc-constants
20+
*/
21+
enum class CSSMathConstant {
22+
E,
23+
Pi,
24+
Infinity,
25+
NegativeInfinity,
26+
NaN,
27+
};
28+
29+
constexpr auto parseCSSMathConstant(std::string_view unit) -> std::optional<CSSMathConstant>
30+
{
31+
switch (fnv1aLowercase(unit)) {
32+
case fnv1a("e"):
33+
return CSSMathConstant::E;
34+
case fnv1a("pi"):
35+
return CSSMathConstant::Pi;
36+
case fnv1a("infinity"):
37+
return CSSMathConstant::Infinity;
38+
case fnv1a("-infinity"):
39+
return CSSMathConstant::NegativeInfinity;
40+
case fnv1a("nan"):
41+
return CSSMathConstant::NaN;
42+
default:
43+
return std::nullopt;
44+
}
45+
}
46+
47+
} // namespace facebook::react
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <optional>
11+
#include <utility>
12+
#include <react/utils/to_underlying.h>
13+
14+
namespace facebook::react {
15+
16+
/**
17+
* Arithmetic operators used in CSS math functions.
18+
* https://www.w3.org/TR/css-values-4/#calc-syntax
19+
*/
20+
enum class CSSMathOperator : char {
21+
Add = '+',
22+
Subtract = '-',
23+
Multiply = '*',
24+
Divide = '/',
25+
};
26+
27+
constexpr auto parseCSSMathOperator(char c) -> std::optional<CSSMathOperator>
28+
{
29+
for (auto op : {CSSMathOperator::Add, CSSMathOperator::Subtract,
30+
CSSMathOperator::Multiply, CSSMathOperator::Divide}) {
31+
if (c == to_underlying(op)) {
32+
return op;
33+
}
34+
}
35+
return std::nullopt;
36+
}
37+
38+
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/css/CSSToken.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77

88
#pragma once
99

10+
#include <optional>
1011
#include <string_view>
1112

13+
#include <react/renderer/css/CSSMathConstant.h>
14+
#include <react/renderer/css/CSSMathOperator.h>
15+
1216
namespace facebook::react {
1317

1418
/**
@@ -76,6 +80,33 @@ class CSSToken {
7680
return unit_;
7781
}
7882

83+
/**
84+
* If this token is a <delim-token> representing a math operator (+, -, *,
85+
* /), return the corresponding CSSMathOperator.
86+
* https://www.w3.org/TR/css-values-4/#calc-syntax
87+
*/
88+
constexpr std::optional<CSSMathOperator> mathOperator() const
89+
{
90+
if (type_ != CSSTokenType::Delim || stringValue_.size() != 1) {
91+
return std::nullopt;
92+
}
93+
return parseCSSMathOperator(stringValue_[0]);
94+
}
95+
96+
/**
97+
* If this token is an <ident-token> representing a CSS math constant
98+
* (e, pi, infinity, -infinity, NaN), return the corresponding
99+
* CSSMathConstant
100+
* https://www.w3.org/TR/css-values-4/#calc-constants
101+
*/
102+
constexpr std::optional<CSSMathConstant> mathConstant() const
103+
{
104+
if (type_ != CSSTokenType::Ident) {
105+
return std::nullopt;
106+
}
107+
return parseCSSMathConstant(stringValue_);
108+
}
109+
79110
constexpr bool operator==(const CSSToken &other) const = default;
80111

81112
private:

packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,20 @@ class CSSTokenizer {
5454
case ',':
5555
return consumeCharacter(CSSTokenType::Comma);
5656
case '+':
57-
case '-':
5857
case '.':
5958
if (wouldStartNumber()) {
6059
return consumeNumeric();
6160
} else {
6261
return consumeDelim();
6362
}
63+
case '-':
64+
if (wouldStartNumber()) {
65+
return consumeNumeric();
66+
} else if (wouldStartIdentSequence()) {
67+
return consumeIdentlikeToken();
68+
} else {
69+
return consumeDelim();
70+
}
6471
case '#':
6572
if (isIdent(peek(1))) {
6673
return consumeHash();
@@ -140,6 +147,16 @@ class CSSTokenizer {
140147
return false;
141148
}
142149

150+
constexpr bool wouldStartIdentSequence() const
151+
{
152+
// https://www.w3.org/TR/css-syntax-3/#would-start-an-identifier
153+
if (peek() == '-') {
154+
return isIdentStart(peek(1)) || peek(1) == '-';
155+
}
156+
157+
return isIdentStart(peek());
158+
}
159+
143160
constexpr CSSToken consumeNumber()
144161
{
145162
// https://www.w3.org/TR/css-syntax-3/#consume-number

packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,48 @@ TEST(CSSTokenizer, ident_values) {
5858
CSSToken{CSSTokenType::WhiteSpace},
5959
CSSToken{CSSTokenType::Ident, "left"},
6060
CSSToken{CSSTokenType::EndOfFile});
61+
62+
EXPECT_TOKENS(
63+
"-infinity",
64+
CSSToken{CSSTokenType::Ident, "-infinity"},
65+
CSSToken{CSSTokenType::EndOfFile});
66+
67+
EXPECT_TOKENS(
68+
"--custom-ident",
69+
CSSToken{CSSTokenType::Ident, "--custom-ident"},
70+
CSSToken{CSSTokenType::EndOfFile});
71+
72+
EXPECT_TOKENS(
73+
"e pi infinity NaN",
74+
CSSToken{CSSTokenType::Ident, "e"},
75+
CSSToken{CSSTokenType::WhiteSpace},
76+
CSSToken{CSSTokenType::Ident, "pi"},
77+
CSSToken{CSSTokenType::WhiteSpace},
78+
CSSToken{CSSTokenType::Ident, "infinity"},
79+
CSSToken{CSSTokenType::WhiteSpace},
80+
CSSToken{CSSTokenType::Ident, "NaN"},
81+
CSSToken{CSSTokenType::EndOfFile});
82+
83+
EXPECT_TOKENS(
84+
"5 - 3",
85+
CSSToken{CSSTokenType::Number, 5.0f},
86+
CSSToken{CSSTokenType::WhiteSpace},
87+
CSSToken{CSSTokenType::Delim, "-"},
88+
CSSToken{CSSTokenType::WhiteSpace},
89+
CSSToken{CSSTokenType::Number, 3.0f},
90+
CSSToken{CSSTokenType::EndOfFile});
91+
92+
EXPECT_TOKENS(
93+
"5 -3",
94+
CSSToken{CSSTokenType::Number, 5.0f},
95+
CSSToken{CSSTokenType::WhiteSpace},
96+
CSSToken{CSSTokenType::Number, -3.0f},
97+
CSSToken{CSSTokenType::EndOfFile});
98+
99+
EXPECT_TOKENS(
100+
"-calc(",
101+
CSSToken{CSSTokenType::Function, "-calc"},
102+
CSSToken{CSSTokenType::EndOfFile});
61103
}
62104

63105
TEST(CSSTokenizer, number_values) {
@@ -220,6 +262,17 @@ TEST(CSSTokenizer, invalid_values) {
220262
CSSToken{CSSTokenType::OpenParen},
221263
CSSToken{CSSTokenType::Delim, "%"},
222264
CSSToken{CSSTokenType::EndOfFile});
265+
266+
EXPECT_TOKENS(
267+
"+ - * /",
268+
CSSToken{CSSTokenType::Delim, "+"},
269+
CSSToken{CSSTokenType::WhiteSpace},
270+
CSSToken{CSSTokenType::Delim, "-"},
271+
CSSToken{CSSTokenType::WhiteSpace},
272+
CSSToken{CSSTokenType::Delim, "*"},
273+
CSSToken{CSSTokenType::WhiteSpace},
274+
CSSToken{CSSTokenType::Delim, "/"},
275+
CSSToken{CSSTokenType::EndOfFile});
223276
}
224277

225278
TEST(CSSTokenizer, hash_values) {
@@ -239,4 +292,23 @@ TEST(CSSTokenizer, hash_values) {
239292
CSSToken{CSSTokenType::Delim, "*"},
240293
CSSToken{CSSTokenType::EndOfFile});
241294
}
295+
TEST(CSSTokenizer, mathOperator) {
296+
EXPECT_EQ(CSSTokenizer{"+"}.next().mathOperator(), CSSMathOperator::Add);
297+
EXPECT_EQ(CSSTokenizer{"-"}.next().mathOperator(), CSSMathOperator::Subtract);
298+
EXPECT_EQ(CSSTokenizer{"*"}.next().mathOperator(), CSSMathOperator::Multiply);
299+
EXPECT_EQ(CSSTokenizer{"/"}.next().mathOperator(), CSSMathOperator::Divide);
300+
EXPECT_EQ(CSSTokenizer{"%"}.next().mathOperator(), std::nullopt);
301+
}
302+
TEST(CSSTokenizer, math_constants) {
303+
EXPECT_EQ(CSSTokenizer{"pi"}.next().mathConstant(), CSSMathConstant::Pi);
304+
EXPECT_EQ(CSSTokenizer{"e"}.next().mathConstant(), CSSMathConstant::E);
305+
EXPECT_EQ(
306+
CSSTokenizer{"infinity"}.next().mathConstant(),
307+
CSSMathConstant::Infinity);
308+
EXPECT_EQ(
309+
CSSTokenizer{"-infinity"}.next().mathConstant(),
310+
CSSMathConstant::NegativeInfinity);
311+
EXPECT_EQ(CSSTokenizer{"NaN"}.next().mathConstant(), CSSMathConstant::NaN);
312+
EXPECT_EQ(CSSTokenizer{"abc"}.next().mathConstant(), std::nullopt);
313+
}
242314
} // namespace facebook::react

0 commit comments

Comments
 (0)