DAW JSON Link
Loading...
Searching...
No Matches
daw_json_skip.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
11#include "version.h"
12
13#include "daw_json_assert.h"
14#include "daw_json_enums.h"
19
20#include <daw/daw_attributes.h>
21#include <daw/daw_bit_cast.h>
22#include <daw/daw_is_any_of.h>
23#include <daw/daw_likely.h>
24#include <daw/daw_unreachable.h>
25
26#if defined( DAW_CX_BIT_CAST )
27#include "daw_count_digits.h"
28#endif
29
30#include <iterator>
31
32namespace daw::json {
33 inline namespace DAW_JSON_VER {
34 namespace json_details {
35 /***
36 * Skip a string, after the initial quote has been skipped already
37 */
38 template<typename ParseState>
39 [[nodiscard]] DAW_ATTRIB_FLATINLINE static inline constexpr ParseState
40 skip_string_nq( ParseState &parse_state ) {
41 auto result = parse_state;
42 result.counter =
43 string_quote::string_quote_parser::parse_nq( parse_state );
44
45 daw_json_assert_weak( parse_state.front( ) == '"',
46 ErrorReason::InvalidString, parse_state );
47 result.last = parse_state.first;
48 parse_state.remove_prefix( );
49 return result;
50 }
51
52 /***
53 * Skip a string and store the first escaped element's position, if any
54 */
55 template<bool KeepQuotes = false, typename ParseState>
56 [[nodiscard]] DAW_ATTRIB_FLATINLINE static inline constexpr ParseState
57 skip_string( ParseState &parse_state ) {
58 if( parse_state.empty( ) ) {
59 return parse_state;
60 }
61 daw_json_ensure( parse_state.front( ) == '"',
62 ErrorReason::InvalidString, parse_state );
63 parse_state.remove_prefix( );
64
65 daw_json_assert_weak( parse_state.has_more( ),
66 ErrorReason::InvalidString, parse_state );
67 auto result = skip_string_nq( parse_state );
68 if constexpr( KeepQuotes ) {
69 --result.first;
70 ++result.last;
71 }
72 return result;
73 }
74
75 template<typename ParseState>
76 [[nodiscard]] static inline constexpr ParseState
77 skip_true( ParseState &parse_state ) {
78 auto result = parse_state;
79 if constexpr( ( ParseState::is_zero_terminated_string or
80 ParseState::is_unchecked_input ) ) {
81 parse_state.remove_prefix( 4 );
82 } else {
83 parse_state.remove_prefix( );
84 daw_json_ensure( parse_state.starts_with( "rue" ),
85 ErrorReason::InvalidTrue, parse_state );
86 parse_state.remove_prefix( 3 );
87 }
88 result.last = parse_state.first;
89 parse_state.trim_left( );
90 daw_json_assert_weak( not parse_state.has_more( ) or
91 parse_state.is_at_token_after_value( ),
92 ErrorReason::InvalidEndOfValue, parse_state );
93 result.counter = static_cast<bool>( true );
94 return result;
95 }
96
97 template<typename ParseState>
98 [[nodiscard]] static inline constexpr ParseState
99 skip_false( ParseState &parse_state ) {
100 auto result = parse_state;
101 if constexpr( ( ParseState::is_zero_terminated_string or
102 ParseState::is_unchecked_input ) ) {
103 parse_state.remove_prefix( 5 );
104 } else {
105 parse_state.remove_prefix( );
106 daw_json_ensure( parse_state.starts_with( "alse" ),
107 ErrorReason::InvalidFalse, parse_state );
108 parse_state.remove_prefix( 4 );
109 }
110 result.last = parse_state.first;
111 parse_state.trim_left( );
112 daw_json_assert_weak( not parse_state.has_more( ) or
113 parse_state.is_at_token_after_value( ),
114 ErrorReason::InvalidEndOfValue, parse_state );
115 result.counter = static_cast<bool>( false );
116 return result;
117 }
118
119 template<typename ParseState>
120 [[nodiscard]] static inline constexpr ParseState
121 skip_null( ParseState &parse_state ) {
122 if constexpr( ( ParseState::is_zero_terminated_string or
123 ParseState::is_unchecked_input ) ) {
124 parse_state.remove_prefix( 4 );
125 } else {
126 parse_state.remove_prefix( );
127 daw_json_ensure( parse_state.starts_with( "ull" ),
128 ErrorReason::InvalidNull, parse_state );
129 parse_state.remove_prefix( 3 );
130 }
131 daw_json_assert_weak( parse_state.has_more( ),
132 ErrorReason::UnexpectedEndOfData, parse_state );
133 parse_state.trim_left( );
134 daw_json_assert_weak( not parse_state.has_more( ) or
135 parse_state.is_at_token_after_value( ),
136 ErrorReason::UnexpectedEndOfData, parse_state );
137 auto result = parse_state;
138 result.first = nullptr;
139 result.last = nullptr;
140 return result;
141 }
142
143 template<bool skip_end_check, typename CharT>
144 DAW_ATTRIB_NONNULL( )
145 DAW_ATTRIB_RET_NONNULL DAW_ATTRIB_FLATINLINE
146 [[nodiscard]] static inline constexpr CharT *skip_digits(
147 CharT *first, CharT *const last ) {
148 (void)last; // only used inside if constexpr and gcc9 warns
149 unsigned dig = parse_digit( *first );
150 while( dig < 10 ) {
151 ++first;
152 if constexpr( not skip_end_check ) {
153 if( DAW_UNLIKELY( first >= last ) ) {
154 break;
155 }
156 }
157 dig = parse_digit( *first );
158 }
159 return first;
160 }
161 /***
162 * Skip a number and store the position of it's components in the returned
163 * ParseState
164 */
165
166 // DAW TODO: This branch has a bug that shows up in twitter_test2
167#if false and defined( DAW_CX_BIT_CAST )
168 template<typename ParseState DAW_JSON_ENABLEIF(
169 ParseState::is_unchecked_input or
170 ParseState::is_zero_terminated_string )>
171 DAW_JSON_REQUIRES( ParseState::is_unchecked_input or
172 ParseState::is_zero_terminated_string )
173 [[nodiscard]] static constexpr ParseState
174 skip_number( ParseState &parse_state ) {
175 using CharT = typename ParseState::CharT;
176
177 auto result = parse_state;
178 CharT *first = parse_state.first;
179 CharT *const last = parse_state.last;
180
181 if( *first == '-' ) {
182 ++first;
183 }
184
185 first = count_digits( first, last );
186
187 CharT *decimal = nullptr;
188 if( *first == '.' ) {
189 decimal = first++;
190 first = count_digits( first, last );
191 }
192
193 CharT *exp = nullptr;
194 char const maybe_e = *first;
195 if( ( maybe_e == 'e' ) | ( maybe_e == 'E' ) ) {
196 exp = ++first;
197 char const maybe_sign = *first;
198 if( ( maybe_sign == '+' ) | ( maybe_sign == '-' ) ) {
199 ++first;
200 }
201 first = count_digits( first, last );
202 }
203
204 daw_json_assert_weak( first <= last, ErrorReason::UnexpectedEndOfData );
205
206 parse_state.first = first;
207 result.last = first;
208 result.class_first = decimal;
209 result.class_last = exp;
210 return result;
211 }
212
213 template<typename ParseState DAW_JSON_ENABLEIF(
214 not( ParseState::is_unchecked_input or
215 ParseState::is_zero_terminated_string ) )>
216 DAW_JSON_REQUIRES( not( ParseState::is_unchecked_input or
217 ParseState::is_zero_terminated_string ) )
218#else
219 template<typename ParseState>
220#endif
221 [[nodiscard]] static constexpr ParseState
222 skip_number( ParseState &parse_state ) {
223 using CharT = typename ParseState::CharT;
224 daw_json_assert_weak( parse_state.has_more( ),
225 ErrorReason::UnexpectedEndOfData, parse_state );
226
227 auto result = parse_state;
228 CharT *first = parse_state.first;
229 CharT *const last = parse_state.last;
230 if constexpr( ParseState::allow_leading_zero_plus ) {
231 if( *first == '-' ) {
232 ++first;
233 }
234 } else {
235 switch( *first ) {
236 case '-':
237 ++first;
238 break;
239 case '+':
240 daw_json_error( ErrorReason::InvalidNumberStart, parse_state );
241 case '0':
242 if( last - first > 1 ) {
244 not parse_policy_details::is_number( *std::next( first ) ),
245 ErrorReason::InvalidNumberStart, parse_state );
246 }
247 break;
248 }
249 }
250
251 if( DAW_LIKELY( first < last ) ) {
252 DAW_LIKELY_BRANCH
253 first =
254 skip_digits<( ParseState::is_zero_terminated_string or
255 ParseState::is_unchecked_input )>( first, last );
256 }
257
258 CharT *decimal = nullptr;
259 if( ( ( ParseState::is_zero_terminated_string or
260 ParseState::is_unchecked_input ) or
261 first < last ) and
262 ( *first == '.' ) ) {
263 decimal = first;
264 ++first;
265 if( DAW_LIKELY( first < last ) ) {
266 DAW_LIKELY_BRANCH
267 first =
268 skip_digits<( ParseState::is_zero_terminated_string or
269 ParseState::is_unchecked_input )>( first, last );
270 }
271 }
272 CharT *exp = nullptr;
273
274 unsigned dig = [&] {
275 if( ParseState::is_zero_terminated_string or first < last ) {
276 return parse_digit( *first );
277 }
278 // We are out of range and the exponent part is optional
279 return 0U;
280 }( );
281 if( ( dig == parsed_constants::e_char ) |
282 ( dig == parsed_constants::E_char ) ) {
283 exp = first;
284 ++first;
285 daw_json_assert_weak( first < last, ErrorReason::UnexpectedEndOfData,
286 [&] {
287 auto r = parse_state;
288 r.first = first;
289 return r;
290 }( ) );
291 dig = parse_digit( *first );
292 if( ( dig == parsed_constants::plus_char ) |
293 ( dig == parsed_constants::minus_char ) ) {
294 ++first;
295 }
296 daw_json_assert_weak( first < last and parse_digit( *first ) < 10U,
297 ErrorReason::InvalidNumber );
298
299 if( DAW_LIKELY( first < last ) ) {
300 DAW_LIKELY_BRANCH
301 first =
302 skip_digits<( ParseState::is_zero_terminated_string or
303 ParseState::is_unchecked_input )>( first, last );
304 }
305 }
306
307 parse_state.first = first;
308 result.last = first;
309 result.class_first = decimal;
310 result.class_last = exp;
311 return result;
312 }
313
314 /***
315 * When we don't know ahead of time what we are skipping switch on the
316 * first value and call that types specific skipper
317 * TODO: Investigate if there is a difference for the times we know
318 * what the member should be if that can increase performance
319 */
320 template<bool KeepInitialQuote = false, typename ParseState>
321 [[nodiscard]] DAW_ATTRIB_NOINLINE constexpr ParseState
322 skip_value( ParseState &parse_state ) {
323 daw_json_assert_weak( parse_state.has_more( ),
324 ErrorReason::UnexpectedEndOfData, parse_state );
325
326 // reset counter
327 parse_state.counter = 0;
328 switch( parse_state.front( ) ) {
329 case '"':
330 return skip_string<KeepInitialQuote>( parse_state );
331 case '[':
332 return parse_state.skip_array( );
333 case '{':
334 return parse_state.skip_class( );
335 case 't':
336 return skip_true( parse_state );
337 case 'f':
338 return skip_false( parse_state );
339 case 'n':
340 return skip_null( parse_state );
341 case '-':
342 case '0':
343 case '1':
344 case '2':
345 case '3':
346 case '4':
347 case '5':
348 case '6':
349 case '7':
350 case '8':
351 case '9':
352 return skip_number( parse_state );
353 }
354 DAW_UNLIKELY_BRANCH
355 if constexpr( ParseState::is_unchecked_input ) {
356 if( DAW_UNLIKELY( parse_state.front( ) == '\0' ) ) {
357 daw_json_error( ErrorReason::InvalidStartOfValue, parse_state );
358 }
359 DAW_UNREACHABLE( );
360 } else {
361 daw_json_error( ErrorReason::InvalidStartOfValue, parse_state );
362 }
363 }
364
365 /***
366 * Used in json_array_iterator::operator++( ) as we know the type we
367 * are skipping
368 */
369 template<typename JsonMember, typename ParseState>
370 [[nodiscard]] DAW_ATTRIB_FLATINLINE static inline constexpr ParseState
371 skip_known_value( ParseState &parse_state ) {
372 daw_json_assert_weak( parse_state.has_more( ),
373 ErrorReason::UnexpectedEndOfData, parse_state );
374 if constexpr( JsonMember::expected_type == JsonParseTypes::Date or
375 JsonMember::expected_type == JsonParseTypes::StringRaw or
376 JsonMember::expected_type ==
377 JsonParseTypes::StringEscaped or
378 JsonMember::expected_type == JsonParseTypes::Custom ) {
379 // json string encodings
380 daw_json_assert_weak( parse_state.front( ) == '"',
381 ErrorReason::InvalidString, parse_state );
382 parse_state.remove_prefix( );
383 return json_details::skip_string_nq( parse_state );
384 } else if constexpr( daw::is_any_of_v<
385 JsonMember::expected_type, JsonParseTypes::Real,
386 JsonParseTypes::Signed, JsonParseTypes::Unsigned,
387 JsonParseTypes::Bool, JsonParseTypes::Null> ) {
388 // All literals
389 return skip_number( parse_state );
390 } else if constexpr( JsonMember::expected_type ==
391 JsonParseTypes::Array ) {
392 daw_json_assert_weak( parse_state.is_opening_bracket_checked( ),
393 ErrorReason::InvalidArrayStart, parse_state );
394 return parse_state.skip_array( );
395 } else if constexpr( JsonMember::expected_type ==
396 JsonParseTypes::Class ) {
397 daw_json_assert_weak( parse_state.is_opening_brace_checked( ),
398 ErrorReason::InvalidClassStart, parse_state );
399 return parse_state.skip_class( );
400 } else {
401 return skip_value( parse_state );
402 }
403 }
404
405 template<typename ParseState>
406 [[nodiscard]] static inline constexpr ParseState
407 skip_literal( ParseState &parse_state ) {
408 daw_json_assert_weak( parse_state.has_more( ),
409 ErrorReason::UnexpectedEndOfData, parse_state );
410
411 // reset counter
412 parse_state.counter = 0;
413 switch( parse_state.front( ) ) {
414 case 't':
415 return skip_true( parse_state );
416 case 'f':
417 return skip_false( parse_state );
418 case 'n':
419 return skip_null( parse_state );
420 case '-':
421 case '0':
422 case '1':
423 case '2':
424 case '3':
425 case '4':
426 case '5':
427 case '6':
428 case '7':
429 case '8':
430 case '9':
431 return skip_number( parse_state );
432 case '\0':
433 daw_json_error( ErrorReason::InvalidStartOfValue, parse_state );
434 }
435 if constexpr( ParseState::is_unchecked_input ) {
436 DAW_UNREACHABLE( );
437 } else {
438 daw_json_error( ErrorReason::InvalidStartOfValue, parse_state );
439 }
440 }
441 } // namespace json_details
442 } // namespace DAW_JSON_VER
443} // 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.
#define DAW_JSON_ENABLEIF(...)
#define DAW_JSON_REQUIRES(...)
DAW_ATTRIB_NOINLINE void daw_json_error(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