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