/src/serenity/Userland/Libraries/LibJS/Runtime/PropertyKey.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/DeprecatedFlyString.h> |
10 | | #include <AK/FlyString.h> |
11 | | #include <LibJS/Heap/Handle.h> |
12 | | #include <LibJS/Runtime/Completion.h> |
13 | | #include <LibJS/Runtime/StringOrSymbol.h> |
14 | | |
15 | | namespace JS { |
16 | | |
17 | | class PropertyKey { |
18 | | public: |
19 | | enum class Type : u8 { |
20 | | Invalid, |
21 | | Number, |
22 | | String, |
23 | | Symbol, |
24 | | }; |
25 | | |
26 | | enum class StringMayBeNumber { |
27 | | Yes, |
28 | | No, |
29 | | }; |
30 | | |
31 | | static ThrowCompletionOr<PropertyKey> from_value(VM& vm, Value value) |
32 | 0 | { |
33 | 0 | if (value.is_empty()) |
34 | 0 | return PropertyKey {}; |
35 | 0 | if (value.is_symbol()) |
36 | 0 | return PropertyKey { value.as_symbol() }; |
37 | 0 | if (value.is_integral_number() && value.as_double() >= 0 && value.as_double() < NumericLimits<u32>::max()) |
38 | 0 | return static_cast<u32>(value.as_double()); |
39 | 0 | return TRY(value.to_byte_string(vm)); |
40 | 0 | } |
41 | | |
42 | 0 | PropertyKey() = default; |
43 | | |
44 | | template<Integral T> |
45 | | PropertyKey(T index) |
46 | 0 | { |
47 | | // FIXME: Replace this with requires(IsUnsigned<T>)? |
48 | | // Needs changes in various places using `int` (but not actually being in the negative range) |
49 | 0 | VERIFY(index >= 0); |
50 | 0 | if constexpr (NumericLimits<T>::max() >= NumericLimits<u32>::max()) { |
51 | 0 | if (index >= NumericLimits<u32>::max()) { |
52 | 0 | m_string = ByteString::number(index); |
53 | 0 | m_type = Type::String; |
54 | 0 | m_string_may_be_number = false; |
55 | 0 | return; |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | 0 | m_type = Type::Number; |
60 | 0 | m_number = index; |
61 | 0 | } Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEmEET_ Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEjEET_ Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralEiEET_ Unexecuted instantiation: _ZN2JS11PropertyKeyC2ITkN2AK8Concepts8IntegralElEET_ |
62 | | |
63 | | PropertyKey(char const* chars) |
64 | 0 | : m_type(Type::String) |
65 | 0 | , m_string(DeprecatedFlyString(chars)) |
66 | 0 | { |
67 | 0 | } |
68 | | |
69 | | PropertyKey(ByteString const& string) |
70 | 0 | : m_type(Type::String) |
71 | 0 | , m_string(DeprecatedFlyString(string)) |
72 | 0 | { |
73 | 0 | } |
74 | | |
75 | | PropertyKey(FlyString const& string) |
76 | | : m_type(Type::String) |
77 | | , m_string(string.to_deprecated_fly_string()) |
78 | 0 | { |
79 | 0 | } |
80 | | |
81 | | PropertyKey(DeprecatedFlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes) |
82 | 0 | : m_string_may_be_number(string_may_be_number == StringMayBeNumber::Yes) |
83 | 0 | , m_type(Type::String) |
84 | 0 | , m_string(move(string)) |
85 | 0 | { |
86 | 0 | } |
87 | | |
88 | | PropertyKey(NonnullGCPtr<Symbol> symbol) |
89 | 0 | : m_type(Type::Symbol) |
90 | 0 | , m_symbol(symbol) |
91 | 0 | { |
92 | 0 | } |
93 | | |
94 | | PropertyKey(StringOrSymbol const& string_or_symbol) |
95 | 0 | { |
96 | 0 | if (string_or_symbol.is_string()) { |
97 | 0 | m_string = string_or_symbol.as_string(); |
98 | 0 | m_type = Type::String; |
99 | 0 | } else if (string_or_symbol.is_symbol()) { |
100 | 0 | m_symbol = const_cast<Symbol*>(string_or_symbol.as_symbol()); |
101 | 0 | m_type = Type::Symbol; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | 0 | ALWAYS_INLINE Type type() const { return m_type; } |
106 | | |
107 | 0 | bool is_valid() const { return m_type != Type::Invalid; } |
108 | | bool is_number() const |
109 | 0 | { |
110 | 0 | if (m_type == Type::Number) |
111 | 0 | return true; |
112 | 0 | if (m_type != Type::String || !m_string_may_be_number) |
113 | 0 | return false; |
114 | | |
115 | 0 | return const_cast<PropertyKey*>(this)->try_coerce_into_number(); |
116 | 0 | } |
117 | | bool is_string() const |
118 | 0 | { |
119 | 0 | if (m_type != Type::String) |
120 | 0 | return false; |
121 | 0 | if (!m_string_may_be_number) |
122 | 0 | return true; |
123 | | |
124 | 0 | return !const_cast<PropertyKey*>(this)->try_coerce_into_number(); |
125 | 0 | } |
126 | 0 | bool is_symbol() const { return m_type == Type::Symbol; } |
127 | | |
128 | | bool try_coerce_into_number() |
129 | 0 | { |
130 | 0 | VERIFY(m_string_may_be_number); |
131 | 0 | if (m_string.is_empty()) { |
132 | 0 | m_string_may_be_number = false; |
133 | 0 | return false; |
134 | 0 | } |
135 | | |
136 | 0 | if (char first = m_string.characters()[0]; first < '0' || first > '9') { |
137 | 0 | m_string_may_be_number = false; |
138 | 0 | return false; |
139 | 0 | } else if (m_string.length() > 1 && first == '0') { |
140 | 0 | m_string_may_be_number = false; |
141 | 0 | return false; |
142 | 0 | } |
143 | | |
144 | 0 | auto property_index = m_string.to_number<unsigned>(TrimWhitespace::No); |
145 | 0 | if (!property_index.has_value() || property_index.value() == NumericLimits<u32>::max()) { |
146 | 0 | m_string_may_be_number = false; |
147 | 0 | return false; |
148 | 0 | } |
149 | 0 | m_type = Type::Number; |
150 | 0 | m_number = *property_index; |
151 | 0 | return true; |
152 | 0 | } |
153 | | |
154 | | u32 as_number() const |
155 | 0 | { |
156 | 0 | VERIFY(is_number()); |
157 | 0 | return m_number; |
158 | 0 | } |
159 | | |
160 | | DeprecatedFlyString const& as_string() const |
161 | 0 | { |
162 | 0 | VERIFY(is_string()); |
163 | 0 | return m_string; |
164 | 0 | } |
165 | | |
166 | | Symbol const* as_symbol() const |
167 | 0 | { |
168 | 0 | VERIFY(is_symbol()); |
169 | 0 | return m_symbol; |
170 | 0 | } |
171 | | |
172 | | ByteString to_string() const |
173 | 0 | { |
174 | 0 | VERIFY(is_valid()); |
175 | 0 | VERIFY(!is_symbol()); |
176 | 0 | if (is_string()) |
177 | 0 | return as_string(); |
178 | 0 | return ByteString::number(as_number()); |
179 | 0 | } |
180 | | |
181 | | StringOrSymbol to_string_or_symbol() const |
182 | 0 | { |
183 | 0 | VERIFY(is_valid()); |
184 | 0 | VERIFY(!is_number()); |
185 | 0 | if (is_string()) |
186 | 0 | return StringOrSymbol(as_string()); |
187 | 0 | return StringOrSymbol(as_symbol()); |
188 | 0 | } |
189 | | |
190 | | private: |
191 | | bool m_string_may_be_number { true }; |
192 | | Type m_type { Type::Invalid }; |
193 | | u32 m_number { 0 }; |
194 | | DeprecatedFlyString m_string; |
195 | | Handle<Symbol> m_symbol; |
196 | | }; |
197 | | |
198 | | } |
199 | | |
200 | | namespace AK { |
201 | | |
202 | | template<> |
203 | | struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> { |
204 | | static unsigned hash(JS::PropertyKey const& name) |
205 | 0 | { |
206 | 0 | VERIFY(name.is_valid()); |
207 | 0 | if (name.is_string()) |
208 | 0 | return name.as_string().hash(); |
209 | 0 | if (name.is_number()) |
210 | 0 | return int_hash(name.as_number()); |
211 | 0 | return ptr_hash(name.as_symbol()); |
212 | 0 | } |
213 | | |
214 | | static bool equals(JS::PropertyKey const& a, JS::PropertyKey const& b) |
215 | 0 | { |
216 | 0 | if (a.type() != b.type()) |
217 | 0 | return false; |
218 | | |
219 | 0 | switch (a.type()) { |
220 | 0 | case JS::PropertyKey::Type::Number: |
221 | 0 | return a.as_number() == b.as_number(); |
222 | 0 | case JS::PropertyKey::Type::String: |
223 | 0 | return a.as_string() == b.as_string(); |
224 | 0 | case JS::PropertyKey::Type::Symbol: |
225 | 0 | return a.as_symbol() == b.as_symbol(); |
226 | 0 | default: |
227 | 0 | VERIFY_NOT_REACHED(); |
228 | 0 | } |
229 | 0 | } |
230 | | }; |
231 | | |
232 | | template<> |
233 | | struct Formatter<JS::PropertyKey> : Formatter<StringView> { |
234 | | ErrorOr<void> format(FormatBuilder& builder, JS::PropertyKey const& property_key) |
235 | 0 | { |
236 | 0 | if (!property_key.is_valid()) |
237 | 0 | return builder.put_string("<invalid PropertyKey>"sv); |
238 | 0 | if (property_key.is_number()) |
239 | 0 | return builder.put_u64(property_key.as_number()); |
240 | 0 | return builder.put_string(property_key.to_string_or_symbol().to_display_string()); |
241 | 0 | } |
242 | | }; |
243 | | |
244 | | } |