src/detail/skip_value.cpp (152 lines of code) (raw):

/* * Copyright (c) 2015-2016 Spotify AB * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ #include <spotify/json/detail/skip_value.hpp> #include <limits> #include <spotify/json/detail/decode_helpers.hpp> #include <spotify/json/detail/macros.hpp> #include <spotify/json/detail/stack.hpp> namespace spotify { namespace json { namespace detail { namespace { json_force_inline bool is_digit(const char c) { return (c >= '0' && c <= '9'); } json_force_inline bool is_hex_digit(const char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } void skip_unicode_escape(decode_context &context) { require_bytes<4>(context, "\\u must be followed by 4 hex digits"); const bool h0 = is_hex_digit(*(context.position++)); const bool h1 = is_hex_digit(*(context.position++)); const bool h2 = is_hex_digit(*(context.position++)); const bool h3 = is_hex_digit(*(context.position++)); fail_if(context, !(h0 && h1 && h2 && h3), "\\u must be followed by 4 hex digits"); } void skip_escape(decode_context &context) { switch (next(context, "Unterminated string")) { case '"': break; case '/': break; case 'b': break; case 'f': break; case 'n': break; case 'r': break; case 't': break; case '\\': break; case 'u': skip_unicode_escape(context); break; default: detail::fail(context, "Invalid escape character", -1); } } void skip_string(decode_context &context) { skip_1(context, '"'); while (json_likely(context.remaining())) { detail::skip_any_simple_characters(context); switch (next(context, "Unterminated string")) { case '"': return; case '\\': skip_escape(context); break; default: json_unreachable(); } } detail::fail(context, "Unterminated string"); } void skip_number(decode_context &context) { // Parse negative sign if (peek(context) == '-') { ++context.position; } // Parse integer part if (peek(context) == '0') { ++context.position; } else { fail_if(context, !is_digit(peek(context)), "Expected digit"); do { ++context.position; } while (is_digit(peek(context))); } // Parse fractional part if (peek(context) == '.') { ++context.position; fail_if(context, !is_digit(peek(context)), "Expected digit after decimal point"); do { ++context.position; } while (is_digit(peek(context))); } // Parse exp part const char maybe_e = peek(context); if (maybe_e == 'e' || maybe_e == 'E') { ++context.position; const char maybe_plus_minus = peek(context); if (maybe_plus_minus == '+' || maybe_plus_minus == '-') { ++context.position; } fail_if(context, !is_digit(peek(context)), "Expected digit after exponent sign"); do { ++context.position; } while (is_digit(peek(context))); } } /** * Advance past one simple JSON value, that is any value that is not an object * {} or an array []. If parsing fails, context will be set to that it has * failed. If parsing suceeds, context.position will point to the character * after the last character of the JSON object that was parsed. * * context.has_failed() must be false when this function is called. */ void skip_simple_value(decode_context &context) { switch (peek(context)) { case '-': // fallthrough case '0': case '1': case '2': case '3': case '4': // fallthrough case '5': case '6': case '7': case '8': case '9': skip_number(context); break; case '"': skip_string(context); break; case 'f': skip_false(context); break; case 't': skip_true(context); break; case 'n': skip_null(context); break; default: fail(context, (std::string("Encountered token '") + peek(context) + "'").c_str()); } } } // namespace void skip_value(decode_context &context) { enum state { done = 0, want = 1 << 0, need = 1 << 1, read_sep = 1 << 2, read_key = 1 << 3, read_val = 1 << 4, want_sep = want | read_sep, want_key = want | read_key, need_key = need | read_key, want_val = want | read_val, need_val = need | read_val }; // We can deal with the first 64 nesting levels {[[{[[ ... ]]}]]} without heap // allocations. Most reasonable JSON will have way less than this, but in case // we encounter an unusual JSON file (perhaps one designed to stack overflow), // the nesting stack will be moved over to the heap. detail::stack<char, 64> stack; auto inside = 0; auto closer = int_fast16_t(std::numeric_limits<int16_t>::max()); // a value outside the range of a 'char' auto pstate = need_val; while (json_likely(context.remaining() && pstate != done)) { if (json_likely(inside)) { skip_any_whitespace(context); } const auto c = peek_unchecked(context); if (c == ',' && (pstate & read_sep)) { skip_unchecked_1(context); pstate = (inside == '{' ? need_key : need_val); continue; } if (c == '"' && (pstate & read_key)) { skip_string(context); skip_any_whitespace(context); skip_1(context, ':'); pstate = need_val; continue; } if (c == closer && !(pstate & need)) { skip_unchecked_1(context); inside = stack.pop(); closer = inside + 2; // '{' + 2 == '}', '[' + 2 == ']' pstate = (inside ? want_sep : done); continue; } fail_if(context, pstate & read_key, "Expected '\"'"); fail_if(context, pstate & read_sep, inside == '{' ? "Expected ',' or '}'" : "Expected ',' or ']"); if (c == '{' || c == '[') { skip_unchecked_1(context); stack.push(inside); inside = c; closer = inside + 2; // '{' + 2 == '}', '[' + 2 == ']' pstate = (inside == '{' ? want_key : want_val); continue; } skip_simple_value(context); pstate = (inside ? want_sep : done); } fail_if(context, inside == '{', "Expected '}'"); fail_if(context, inside == '[', "Expected ']'"); fail_if(context, pstate != done, "Unexpected EOF"); } } // namespace detail } // namespace json } // namespace spotify