DAW JSON Link
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"
15 #include "daw_json_parse_digit.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 
32 namespace 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
171  DAW_JSON_REQUIRES( ParseState::is_unchecked_input or
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
216  DAW_JSON_REQUIRES( not( ParseState::is_unchecked_input or
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 =
255  ParseState::is_unchecked_input )>( first, last );
256  }
257 
258  CharT *decimal = nullptr;
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 =
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 =
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(...)
DAW_ATTRIB_NOINLINE void daw_json_error(ErrorReason reason)
std::bool_constant< is_zero_terminated_string_v< T > > is_zero_terminated_string
Customization point traits.
DAW_JSON_REQUIRES(boost::describe::has_describe_members< T >::value and use_boost_describe_v< T >) struct json_data_contract< T >
#define DAW_JSON_VER
The version string used in namespace definitions. Must be a valid namespace name.
Definition: version.h:25