DAW JSON Link
Loading...
Searching...
No Matches
daw_json_parse_unsigned_int.h
Go to the documentation of this file.
1// Copyright (c) Darrell Wright
2//
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5//
6// Official repository: https://github.com/beached/daw_json_link
7//
8
9#pragma once
10
12
18
19#include <daw/daw_arith_traits.h>
20#include <daw/daw_construct_a.h>
21#include <daw/daw_cxmath.h>
22#include <daw/daw_not_null.h>
23#include <daw/daw_remove_cvref.h>
24#include <daw/daw_restrict.h>
25#include <daw/daw_uint_buffer.h>
26
27#include <cstddef>
28#include <daw/stdinc/data_access.h>
29#include <limits>
30
31namespace daw::json {
32 inline namespace DAW_JSON_VER {
33 namespace json_details {
34 template<typename Signed, typename Unsigned>
35 DAW_ATTRIB_INLINE constexpr Signed to_signed( Unsigned &&u,
36 Signed sign ) {
37 if( sign <= Signed{ 0 } ) {
38 return static_cast<Signed>( -u );
39 }
40 return static_cast<Signed>( u );
41 }
42
43 [[nodiscard]]
44 constexpr bool
45 is_made_of_eight_digits_cx( daw::not_null<char const *> const ptr ) {
46 // The copy to local buffer is to get the compiler to treat it like a
47 // reinterpret_cast
48
49 auto const val = daw::to_uint64_buffer( ptr.get( ) );
50 return ( ( ( val & daw::UInt64{0xF0F0'F0F0'F0F0'F0F0ULL} ) |
51 ( ( ( val + daw::UInt64{0x0606'0606'0606'0606ULL} ) &
52 daw::UInt64{0xF0F0'F0F0'F0F0'F0F0ULL} ) >>
53 4U ) ) == daw::UInt64{0x3333'3333'3333'3333ULL} );
54 }
55
56 template<options::JsonRangeCheck RangeCheck, typename Unsigned,
57 typename MaxArithUnsigned>
58 using max_unsigned_t = daw::conditional_t<
59 daw::is_integral_v<Unsigned> or std::is_enum_v<Unsigned>,
60 daw::conditional_t<( sizeof( Unsigned ) > sizeof( MaxArithUnsigned ) ),
61 Unsigned, MaxArithUnsigned>,
62 Unsigned>;
63
64 // Constexpr'ified version from
65 // https://kholdstare.github.io/technical/2020/05/26/faster-integer-parsing.html
66 constexpr UInt64 parse_8_digits( daw::not_null<char const *> const str ) {
67 auto const chunk = daw::to_uint64_buffer( str.get( ) );
68 // 1-byte mask trick (works on 4 pairs of single digits)
69 auto const lower_digits =
70 ( chunk & daw::UInt64{0x0F'00'0F'00'0F'00'0F'00ULL} ) >> 8U;
71 auto const upper_digits =
72 ( chunk & daw::UInt64{0x00'0F'00'0F'00'0F'00'0FULL} ) * 10U;
73 auto const chunk2 = lower_digits + upper_digits;
74
75 // 2-byte mask trick (works on 2 pairs of two digits)
76 auto const lower_digits2 =
77 ( chunk2 & daw::UInt64{0x00'FF'00'00'00'FF'00'00ULL} ) >> 16U;
78 auto const upper_digits2 =
79 ( chunk2 & daw::UInt64{0x00'00'00'FF'00'00'00'FFULL} ) * 100U;
80 auto const chunk3 = lower_digits2 + upper_digits2;
81
82 // 4-byte mask trick (works on pair of four digits)
83 auto const lower_digits3 =
84 ( chunk3 & daw::UInt64{0x00'00'FF'FF'00'00'00'00ULL} ) >> 32U;
85 auto const upper_digits3 =
86 ( chunk3 & daw::UInt64{0x00'00'00'00'00'00'FF'FFULL} ) * 10000U;
87 auto const chunk4 = lower_digits3 + upper_digits3;
88
89 return chunk4 & daw::UInt64{0xFFFF'FFFFULL};
90 }
91
92 static_assert( parse_8_digits( "12345678" ) == daw::UInt64{1234'5678},
93 "8 digit parser does not work on this platform" );
94
95 constexpr UInt64
96 parse_16_digits( daw::not_null<char const *> const str ) {
97 auto const upper = parse_8_digits( str );
98 auto const lower = parse_8_digits( str + 8 );
99 return upper * daw::UInt64{100'000'000ULL} + lower;
100 }
101
102 static_assert( parse_16_digits( "1234567890123456" ) ==
103 daw::UInt64{1234567890123456ULL},
104 "16 digit parser does not work on this platform" );
105
106 template<typename T>
107 struct make_unsigned_with_bool : daw::make_unsigned<T> {};
108
109 template<>
110 struct make_unsigned_with_bool<bool> {
111 using type = bool;
112 };
113
114 template<typename Integer, typename T, typename ParseState>
115 [[nodiscard]] constexpr Integer
116 narrow_cast( T value, ParseState const &parse_state ) {
117 if constexpr( std::is_signed_v<T> ) {
118 if constexpr( std::is_signed_v<Integer> ) {
119 if constexpr( sizeof( T ) <= sizeof( Integer ) ) {
120 return value;
121 } else if( value <= static_cast<T>( daw::max_value<Integer> ) ) {
122 return static_cast<Integer>( value );
123 } else {
125 true, ErrorReason::NumberOutOfRange, parse_state );
126 }
127 } else if constexpr( sizeof( T ) <= sizeof( Integer ) ) {
128 if( value >= 0 ) {
129 return value;
130 }
131 daw_json_error( true, ErrorReason::NumberOutOfRange, parse_state );
132 } else {
133 if( value >= 0 and
134 value <= static_cast<T>( daw::max_value<Integer> ) ) {
135 return value;
136 }
137 daw_json_error( true, ErrorReason::NumberOutOfRange, parse_state );
138 }
139 } else if constexpr( std::is_signed_v<Integer> ) {
140 if constexpr( sizeof( T ) < sizeof( Integer ) ) {
141 return static_cast<Integer>( value );
142 } else {
143 if( value > static_cast<T>( daw::max_value<Integer> ) ) {
145 true, ErrorReason::NumberOutOfRange, parse_state );
146 }
147 return static_cast<Integer>( value );
148 }
149 } else if constexpr( sizeof( T ) <= sizeof( Integer ) ) {
150 return static_cast<Integer>( value );
151 } else {
152 if( value <= static_cast<T>( daw::max_value<Integer> ) ) {
153 return static_cast<Integer>( value );
154 }
155 daw_json_error( true, ErrorReason::NumberOutOfRange, parse_state );
156 }
157 }
158
159 template<typename T>
160 using make_unsigned_with_bool_t =
161 typename make_unsigned_with_bool<T>::type;
162
163 template<typename Unsigned, options::JsonRangeCheck RangeChecked,
164 typename ParseState>
165 [[nodiscard]] static constexpr Unsigned
166 unsigned_parser_known( ParseState &parse_state ) {
167 // We know how many digits are in the number
168 using result_t = max_unsigned_t<RangeChecked, Unsigned, UInt64>;
169 using uresult_t = max_unsigned_t<RangeChecked,
170 make_unsigned_with_bool_t<Unsigned>,
171 UInt64>;
172 static_assert(
173 not static_cast<bool>( RangeChecked ) or
174 std::is_same_v<uresult_t, UInt64>,
175 "Range checking is only supported for std integral types" );
176
177 daw::not_null<char const *> first = parse_state.first;
178 daw::not_null<char const *> const last = parse_state.last;
179 uresult_t result = uresult_t( );
180
181 while( last - first >= 16 ) {
182 result *= static_cast<uresult_t>( 10'000'000'000'000'000ULL );
183 result += static_cast<uresult_t>( parse_16_digits( first ) );
184 first += 16;
185 }
186 if( last - first >= 8 ) {
187 result *= static_cast<uresult_t>( 100'000'000ULL );
188 result += static_cast<uresult_t>( parse_8_digits( first ) );
189 first += 8;
190 }
191 if constexpr( ParseState::is_zero_terminated_string ) {
192 auto dig = parse_digit( *first );
193 while( dig < 10U ) {
194 result *= 10U;
195 result += dig;
196 ++first;
197 dig = parse_digit( *first );
198 }
199 } else {
200 while( first < last ) {
201 result *= 10U;
202 result += parse_digit( *first );
203 ++first;
204 }
205 }
206 if constexpr( RangeChecked != options::JsonRangeCheck::Never ) {
207 auto const count =
208 ( daw::digits10<result_t> + 1U ) - std::size( parse_state );
210 ( ( result <= static_cast<uresult_t>( daw::max_value<result_t> ) ) &
211 ( count >= 0 ) ),
212 ErrorReason::NumberOutOfRange,
213 parse_state );
214 }
215 if constexpr( std::is_same_v<char const *,
216 typename ParseState::iterator> ) {
217 parse_state.first = first;
218 } else {
219 parse_state.first += first.get( ) - parse_state.first;
220 }
221 if constexpr( RangeChecked == options::JsonRangeCheck::Never ) {
222 return daw::construct_a<Unsigned>( static_cast<Unsigned>( result ) );
223 } else {
224 return daw::construct_a<Unsigned>(
225 narrow_cast<Unsigned>( result, parse_state ) );
226 }
227 }
228
229 //**************************
230 template<typename Unsigned, options::JsonRangeCheck RangeChecked,
231 typename ParseState>
232 [[nodiscard]] static constexpr Unsigned
233 unsigned_parser_not_known( ParseState &parse_state ) {
234 // We do not know how long the string is
235 using result_t = max_unsigned_t<RangeChecked, Unsigned, UInt64>;
236 using uresult_t = max_unsigned_t<RangeChecked,
237 make_unsigned_with_bool_t<Unsigned>,
238 UInt64>;
239 static_assert(
240 not static_cast<bool>( RangeChecked ) or
241 std::is_same_v<uresult_t, UInt64>,
242 "Range checking is only supported for std integral types" );
243 daw_json_assert_weak( parse_state.has_more( ),
244 ErrorReason::UnexpectedEndOfData,
245 parse_state );
246 daw::not_null<char const *> first = parse_state.first;
247 auto const orig_first = first;
248 (void)orig_first; // only used inside if constexpr and gcc9 warns
249 daw::not_null<char const *> const last = parse_state.last;
250 uresult_t result = uresult_t( );
251 bool has_eight =
252 last - first >= 8 ? is_made_of_eight_digits_cx( first ) : false;
253 if( has_eight & ( last - first >= 16 ) ) {
254 bool has_sixteen = is_made_of_eight_digits_cx( first + 8 );
255 while( has_sixteen ) {
256 result *= static_cast<uresult_t>( 10'000'000'000'000'000ULL );
257 result += static_cast<uresult_t>( parse_16_digits( first ) );
258 first += 16;
259 has_eight =
260 last - first >= 8 ? is_made_of_eight_digits_cx( first ) : false;
261 has_sixteen =
262 has_eight and
263 ( last - first >= 16 ? is_made_of_eight_digits_cx( first + 8 )
264 : false );
265 }
266 }
267 if( has_eight ) {
268 result *= static_cast<uresult_t>( 100'000'000ULL );
269 result += static_cast<uresult_t>( parse_8_digits( first ) );
270 first += 8;
271 }
272 if constexpr( ParseState::is_zero_terminated_string ) {
273 auto dig = parse_digit( *first );
274 while( dig < 10U ) {
275 result *= 10U;
276 result += dig;
277 ++first;
278 dig = parse_digit( *first );
279 }
280 } else {
281 while( first < last ) {
282 auto const dig = parse_digit( *first );
283 if( dig >= 10U ) {
284 break;
285 }
286 result *= 10U;
287 result += dig;
288 ++first;
289 }
290 }
291
292 if constexpr( RangeChecked != options::JsonRangeCheck::Never ) {
293 auto const count =
294 static_cast<std::ptrdiff_t>( daw::digits10<result_t> + 1 ) -
295 ( first - orig_first );
297 count >= 0, ErrorReason::NumberOutOfRange, parse_state );
298 }
299
300 if constexpr( std::is_same_v<char const *,
301 typename ParseState::iterator> ) {
302 parse_state.first = first;
303 } else {
304 parse_state.first += first.get( ) - parse_state.first;
305 }
306 if constexpr( RangeChecked == options::JsonRangeCheck::Never ) {
307 return daw::construct_a<Unsigned>(
308 static_cast<Unsigned>( static_cast<result_t>( result ) ) );
309 } else {
310 return daw::construct_a<Unsigned>(
311 narrow_cast<Unsigned>( result, parse_state ) );
312 }
313 }
314
315 template<typename Unsigned, options::JsonRangeCheck RangeChecked,
316 bool KnownBounds, typename ParseState>
317 [[nodiscard]] DAW_ATTRIB_INLINE static constexpr Unsigned
318 unsigned_parser( ParseState &parse_state ) {
319 if constexpr( KnownBounds ) {
320 return unsigned_parser_known<Unsigned, RangeChecked>( parse_state );
321 } else {
322 return unsigned_parser_not_known<Unsigned, RangeChecked>(
323 parse_state );
324 }
325 }
326 } // namespace json_details
327 } // namespace DAW_JSON_VER
328} // namespace daw::json
#define daw_json_assert_weak(Bool,...)
Assert that Bool is true when in Checked Input mode If false pass rest of args to daw_json_error.
#define daw_json_ensure(Bool,...)
Ensure that Bool is true. If false pass rest of args to daw_json_error.
DAW_ATTRIB_NOINLINE void daw_json_error(bool b, ErrorReason reason)
Customization point traits.
#define DAW_JSON_VER
The version string used in namespace definitions. Must be a valid namespace name.
Definition version.h:20