mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
845 lines
28 KiB
C++
845 lines
28 KiB
C++
#ifdef _MSC_VER
|
|
/* Microsoft C/C++-compatible compiler */
|
|
#include <intrin.h>
|
|
#else
|
|
#include <immintrin.h>
|
|
#include <x86intrin.h>
|
|
#endif
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
|
|
#include "jsonparser/common_defs.h"
|
|
#include "jsonparser/simdjson_internal.h"
|
|
|
|
#include <iostream>
|
|
//#define DEBUG
|
|
#define PATH_SEP '/'
|
|
|
|
#if defined(DEBUG) && !defined(DEBUG_PRINTF)
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#define DEBUG_PRINTF(format, ...) printf("%s:%s:%d:" format, \
|
|
strrchr(__FILE__, PATH_SEP) + 1, \
|
|
__func__, __LINE__, ## __VA_ARGS__)
|
|
#elif !defined(DEBUG_PRINTF)
|
|
#define DEBUG_PRINTF(format, ...) do { } while(0)
|
|
#endif
|
|
|
|
using namespace std;
|
|
// structural chars here are
|
|
// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c
|
|
// we are also interested in the four whitespace characters
|
|
// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d
|
|
|
|
// these are the chars that can follow a true/false/null or number atom
|
|
// and nothing else
|
|
const u32 structural_or_whitespace_negated[256] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
|
|
|
// return non-zero if not a structural or whitespace char
|
|
// zero otherwise
|
|
really_inline u32 is_not_structural_or_whitespace(u8 c) {
|
|
return structural_or_whitespace_negated[c];
|
|
}
|
|
|
|
// begin copypasta
|
|
// These chars yield themselves: " \ /
|
|
// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab
|
|
// u not handled in this table as it's complex
|
|
const u8 escape_map[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0.
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4.
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5.
|
|
0, 0, 0x08, 0, 0, 0, 0x12, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6.
|
|
0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7.
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
};
|
|
|
|
const u32 leading_zeros_to_utf_bytes[33] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, // 7 bits for first one
|
|
2, 2, 2, 2, // 11 bits for next
|
|
3, 3, 3, 3, 3, // 16 bits for next
|
|
4, 4, 4, 4, 4, // 21 bits for next
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // error
|
|
|
|
const u32 UTF_PDEP_MASK[5] = {0x00, // error
|
|
0x7f, 0x1f3f, 0x0f3f3f, 0x073f3f3f};
|
|
|
|
const u32 UTF_OR_MASK[5] = {0x00, // error
|
|
0x00, 0xc080, 0xe08080, 0xf0808080};
|
|
|
|
inline bool is_hex_digit(u8 v) {
|
|
if (v >= '0' && v <= '9')
|
|
return true;
|
|
v &= 0xdf;
|
|
if (v >= 'A' && v <= 'F')
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
inline u8 digit_to_val(u8 v) {
|
|
if (v >= '0' && v <= '9')
|
|
return v - '0';
|
|
v &= 0xdf;
|
|
return v - 'A' + 10;
|
|
}
|
|
|
|
inline bool hex_to_u32(const u8 *src, u32 *res) {
|
|
u8 v1 = src[0];
|
|
u8 v2 = src[1];
|
|
u8 v3 = src[2];
|
|
u8 v4 = src[3];
|
|
if (!is_hex_digit(v1) || !is_hex_digit(v2) || !is_hex_digit(v3) ||
|
|
!is_hex_digit(v4)) {
|
|
return false;
|
|
}
|
|
*res = digit_to_val(v1) << 24 | digit_to_val(v2) << 16 |
|
|
digit_to_val(v3) << 8 | digit_to_val(v4);
|
|
return true;
|
|
}
|
|
|
|
// handle a unicode codepoint
|
|
// write appropriate values into dest
|
|
// src will always advance 6 bytes
|
|
// dest will advance a variable amount (return via pointer)
|
|
// return true if the unicode codepoint was valid
|
|
// We work in little-endian then swap at write time
|
|
really_inline bool handle_unicode_codepoint(const u8 **src_ptr, u8 **dst_ptr) {
|
|
u32 code_point = 0; // read the hex, potentially reading another \u beyond if
|
|
// it's a // wacky one
|
|
if (!hex_to_u32(*src_ptr + 2, &code_point)) {
|
|
return false;
|
|
}
|
|
*src_ptr += 6;
|
|
// check for the weirdo double-UTF-16 nonsense for things outside Basic
|
|
// Multilingual Plane.
|
|
if (code_point >= 0xd800 && code_point < 0xdc00) {
|
|
// TODO: sanity check and clean up; snippeted from RapidJSON and poorly
|
|
// understood at the moment
|
|
if (((*src_ptr)[0] != '\\') || (*src_ptr)[1] != 'u') {
|
|
return false;
|
|
}
|
|
u32 code_point_2 = 0;
|
|
if (!hex_to_u32(*src_ptr + 2, &code_point_2)) {
|
|
return false;
|
|
}
|
|
if (code_point_2 < 0xdc00 || code_point_2 > 0xdfff) {
|
|
return false;
|
|
}
|
|
code_point =
|
|
(((code_point - 0xd800) << 10) | (code_point_2 - 0xdc00)) + 0x10000;
|
|
*src_ptr += 6;
|
|
}
|
|
// TODO: check to see whether the below code is nonsense (it's really only a
|
|
// sketch at this point)
|
|
u32 lz = __builtin_clz(code_point);
|
|
u32 utf_bytes = leading_zeros_to_utf_bytes[lz];
|
|
u32 tmp =
|
|
_pdep_u32(code_point, UTF_PDEP_MASK[utf_bytes]) | UTF_OR_MASK[utf_bytes];
|
|
// swap and move to the other side of the register
|
|
tmp = __builtin_bswap32(tmp);
|
|
tmp >>= ((4 - utf_bytes) * 8) & 31; // if utf_bytes, this could become a shift
|
|
// by 32, hence the mask with 31
|
|
// use memcpy to avoid undefined behavior:
|
|
std::memcpy(*(u32 **)dst_ptr, &tmp, sizeof(u32)); //**(u32 **)dst_ptr = tmp;
|
|
*dst_ptr += utf_bytes;
|
|
return true;
|
|
}
|
|
|
|
really_inline bool parse_string(const u8 *buf, UNUSED size_t len,
|
|
ParsedJson &pj, u32 depth, u32 offset) {
|
|
|
|
const u8 *src = &buf[offset + 1]; // we know that buf at offset is a "
|
|
u8 *dst = pj.current_string_buf_loc;
|
|
#ifdef DEBUG
|
|
cout << "Entering parse string with offset " << offset << "\n";
|
|
#endif
|
|
while (1) {
|
|
#ifdef DEBUG
|
|
for (u32 j = 0; j < 32; j++) {
|
|
char c = *(src + j);
|
|
if (isprint(c)) {
|
|
cout << c;
|
|
} else {
|
|
cout << '_';
|
|
}
|
|
}
|
|
cout << "| ... string handling input\n";
|
|
#endif
|
|
m256 v = _mm256_loadu_si256((const m256 *)(src));
|
|
u32 bs_bits =
|
|
(u32)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, _mm256_set1_epi8('\\')));
|
|
dumpbits32(bs_bits, "backslash bits 2");
|
|
u32 quote_bits =
|
|
(u32)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, _mm256_set1_epi8('"')));
|
|
dumpbits32(quote_bits, "quote_bits");
|
|
u32 quote_dist = __builtin_ctz(quote_bits);
|
|
u32 bs_dist = __builtin_ctz(bs_bits);
|
|
// store to dest unconditionally - we can overwrite the bits we don't like
|
|
// later
|
|
_mm256_storeu_si256((m256 *)(dst), v);
|
|
#ifdef DEBUG
|
|
cout << "quote dist: " << quote_dist << " bs dist: " << bs_dist << "\n";
|
|
#endif
|
|
|
|
if (quote_dist < bs_dist) {
|
|
#ifdef DEBUG
|
|
cout << "Found end, leaving!\n";
|
|
#endif
|
|
// we encountered quotes first. Move dst to point to quotes and exit
|
|
dst[quote_dist] = 0; // null terminate and get out
|
|
|
|
pj.write_tape(depth, pj.current_string_buf_loc - pj.string_buf, '"');
|
|
|
|
pj.current_string_buf_loc = dst + quote_dist + 1;
|
|
|
|
return true;
|
|
} else if (quote_dist > bs_dist) {
|
|
u8 escape_char = src[bs_dist + 1];
|
|
#ifdef DEBUG
|
|
cout << "Found escape char: " << escape_char << "\n";
|
|
#endif
|
|
// we encountered backslash first. Handle backslash
|
|
if (escape_char == 'u') {
|
|
// move src/dst up to the start; they will be further adjusted
|
|
// within the unicode codepoint handling code.
|
|
src += bs_dist;
|
|
dst += bs_dist;
|
|
if (!handle_unicode_codepoint(&src, &dst)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} else {
|
|
// simple 1:1 conversion. Will eat bs_dist+2 characters in input and
|
|
// write bs_dist+1 characters to output
|
|
// note this may reach beyond the part of the buffer we've actually
|
|
// seen. I think this is ok
|
|
u8 escape_result = escape_map[escape_char];
|
|
if (!escape_result)
|
|
return false; // bogus escape value is an error
|
|
dst[bs_dist] = escape_result;
|
|
src += bs_dist + 2;
|
|
dst += bs_dist + 1;
|
|
}
|
|
} else {
|
|
// they are the same. Since they can't co-occur, it means we encountered
|
|
// neither.
|
|
src += 32;
|
|
dst += 32;
|
|
}
|
|
return true;
|
|
}
|
|
// can't be reached
|
|
return true;
|
|
}
|
|
|
|
#ifdef DOUBLECONV
|
|
#include "double-conversion/double-conversion.h"
|
|
#include "double-conversion/ieee.h"
|
|
using namespace double_conversion;
|
|
static StringToDoubleConverter
|
|
converter(StringToDoubleConverter::ALLOW_TRAILING_JUNK, 2000000.0,
|
|
Double::NaN(), NULL, NULL);
|
|
#endif
|
|
|
|
|
|
// does not validation whatsoever, assumes that all digit
|
|
// this is CS 101
|
|
inline u64 naivestrtoll(const char *p, const char *end) {
|
|
if(p == end) return 0; // should be an error?
|
|
// this code could get a whole lot smarter if we have many long ints:
|
|
u64 x = *p - '0';
|
|
p++;
|
|
for(;p < end;p++) {
|
|
x = (x*10) + (*p - '0');
|
|
}
|
|
return x;
|
|
}
|
|
// put a parsed version of number (either as a double or a signed long) into the
|
|
// number buffer, put a 'tag' indicating which type and where it is back onto
|
|
// the tape at that location return false if we can't parse the number which
|
|
// means either (a) the number isn't valid, or (b) the number is followed by
|
|
// something that isn't whitespace, comma or a close }] character which are the
|
|
// only things that should follow a number at this stage bools to detect what we
|
|
// found in our initial character already here - we are already switching on 0
|
|
// vs 1-9 vs - so we may as well keep separate paths where that's useful
|
|
|
|
// TODO: see if we really need a separate number_buf or whether we should just
|
|
// have a generic scratch - would need to align before using for this
|
|
really_inline bool parse_number(const u8 *buf, UNUSED size_t len,
|
|
ParsedJson &pj,
|
|
u32 depth, u32 offset,
|
|
UNUSED bool found_zero, bool found_minus) {
|
|
////////////////
|
|
// This is temporary... but it illustrates how one could use Google's double
|
|
// conv.
|
|
///
|
|
#ifdef DOUBLECONV
|
|
// Maybe surprisingly, StringToDouble does not parse according to the JSON
|
|
// spec (e.g., it will happily parse 012 as 12).
|
|
int processed_characters_count;
|
|
double result_double_conv = converter.StringToDouble(
|
|
(const char *)(buf + offset), 10, &processed_characters_count);
|
|
pj.write_tape_double(depth, result_double_conv);
|
|
|
|
return result_double_conv == result_double_conv;
|
|
#endif
|
|
////////////////
|
|
// end of double conv temporary stuff.
|
|
////////////////
|
|
if (found_minus) {
|
|
offset++;
|
|
}
|
|
const u8 *src = &buf[offset];
|
|
m256 v = _mm256_loadu_si256((const m256 *)(src));
|
|
u64 error_sump = 0;
|
|
#ifdef DEBUG
|
|
for (u32 j = 0; j < 32; j++) {
|
|
char c = *(src + j);
|
|
if (isprint(c)) {
|
|
cout << c;
|
|
} else {
|
|
cout << '_';
|
|
}
|
|
}
|
|
cout << "| ... number handling input\n";
|
|
#endif
|
|
|
|
// categories to extract
|
|
// Digits:
|
|
// 0 (0x30) - bucket 0
|
|
// 1-9 (never any distinction except if we didn't get the free kick at 0 due
|
|
// to the leading minus) (0x31-0x39) - bucket 1
|
|
// . (0x2e) - bucket 2
|
|
// E or e - no distinction (0x45/0x65) - bucket 3
|
|
// + (0x2b) - bucket 4
|
|
// - (0x2d) - bucket 4
|
|
// Terminators
|
|
// Whitespace: 0x20, 0x09, 0x0a, 0x0d - bucket 5+6
|
|
// Comma and the closes: 0x2c is comma, } is 0x5d, ] is 0x7d - bucket 5+7
|
|
|
|
// Another shufti - also a bit hand-hacked. Need to make a better construction
|
|
const m256 low_nibble_mask = _mm256_setr_epi8(
|
|
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
|
|
33, 2, 2, 2, 2, 10, 2, 2, 2, 66, 64, 16, 32, 0xd0, 4, 0, 33, 2, 2, 2, 2,
|
|
10, 2, 2, 2, 66, 64, 16, 32, 0xd0, 4, 0);
|
|
const m256 high_nibble_mask = _mm256_setr_epi8(
|
|
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
|
|
64, 0, 52, 3, 8, -128, 8, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 52, 3, 8,
|
|
-128, 8, 0x80, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
|
|
m256 tmp = _mm256_and_si256(
|
|
_mm256_shuffle_epi8(low_nibble_mask, v),
|
|
_mm256_shuffle_epi8(
|
|
high_nibble_mask,
|
|
_mm256_and_si256(_mm256_srli_epi32(v, 4), _mm256_set1_epi8(0x7f))));
|
|
#ifdef DEBUG
|
|
// let us print out the magic:
|
|
uint8_t buffer[32];
|
|
_mm256_storeu_si256((__m256i *)buffer,tmp);
|
|
for(int k = 0; k < 32; k++)
|
|
printf("%.2x ",buffer[k]);
|
|
printf("\n");
|
|
#endif
|
|
m256 enders_mask = _mm256_set1_epi8(0xe0);
|
|
m256 tmp_enders = _mm256_cmpeq_epi8(_mm256_and_si256(tmp, enders_mask),
|
|
_mm256_set1_epi8(0));
|
|
u32 enders = ~(u32)_mm256_movemask_epi8(tmp_enders);
|
|
dumpbits32(enders, "ender characters");
|
|
//dumpbits32_always(enders, "ender characters");
|
|
|
|
if (enders == 0) {
|
|
error_sump = 1;
|
|
// if enders == 0 we have
|
|
// a heroically long number string or some garbage
|
|
}
|
|
// TODO: make a mask that indicates where our digits are // DANIEL: Isn't that digit_characters?
|
|
u32 number_mask = ~enders & (enders - 1);
|
|
dumpbits32(number_mask, "number mask");
|
|
//dumpbits32_always(number_mask, "number mask");
|
|
m256 n_mask = _mm256_set1_epi8(0x1f);
|
|
m256 tmp_n =
|
|
_mm256_cmpeq_epi8(_mm256_and_si256(tmp, n_mask), _mm256_set1_epi8(0));
|
|
u32 number_characters = ~(u32)_mm256_movemask_epi8(tmp_n);
|
|
|
|
// put something into our error sump if we have something
|
|
// before our ending characters that isn't a valid character
|
|
// for the inside of our JSON
|
|
number_characters &= number_mask;
|
|
error_sump |= number_characters ^ number_mask;
|
|
dumpbits32(number_characters, "number characters");
|
|
|
|
m256 d_mask = _mm256_set1_epi8(0x03);
|
|
m256 tmp_d =
|
|
_mm256_cmpeq_epi8(_mm256_and_si256(tmp, d_mask), _mm256_set1_epi8(0));
|
|
u32 digit_characters = ~(u32)_mm256_movemask_epi8(tmp_d);
|
|
digit_characters &= number_mask;
|
|
dumpbits32(digit_characters, "digit characters");
|
|
// dumpbits32_always(digit_characters, "digit characters");
|
|
|
|
|
|
m256 p_mask = _mm256_set1_epi8(0x04);
|
|
m256 tmp_p =
|
|
_mm256_cmpeq_epi8(_mm256_and_si256(tmp, p_mask), _mm256_set1_epi8(0));
|
|
u32 decimal_characters = ~(u32)_mm256_movemask_epi8(tmp_p);
|
|
decimal_characters &= number_mask;
|
|
dumpbits32(decimal_characters, "decimal characters");
|
|
|
|
m256 e_mask = _mm256_set1_epi8(0x08);
|
|
m256 tmp_e =
|
|
_mm256_cmpeq_epi8(_mm256_and_si256(tmp, e_mask), _mm256_set1_epi8(0));
|
|
u32 exponent_characters = ~(u32)_mm256_movemask_epi8(tmp_e);
|
|
exponent_characters &= number_mask;
|
|
dumpbits32(exponent_characters, "exponent characters");
|
|
|
|
|
|
m256 zero_mask = _mm256_set1_epi8(0x1);
|
|
m256 tmp_zero =
|
|
_mm256_cmpeq_epi8(tmp, zero_mask);
|
|
u32 zero_characters = (u32)_mm256_movemask_epi8(tmp_zero);
|
|
dumpbits32(zero_characters, "zero characters");
|
|
|
|
// if the zero character is in first position, it
|
|
// needs to be followed by decimal or exponent or ender (note: we
|
|
// handle found_minus separately)
|
|
u32 expo_or_decimal_or_ender = exponent_characters | decimal_characters | enders;
|
|
error_sump |= zero_characters & 0x01 & (~(expo_or_decimal_or_ender >> 1));
|
|
|
|
m256 s_mask = _mm256_set1_epi8(0x10);
|
|
m256 tmp_s =
|
|
_mm256_cmpeq_epi8(_mm256_and_si256(tmp, s_mask), _mm256_set1_epi8(0));
|
|
u32 sign_characters = ~(u32)_mm256_movemask_epi8(tmp_s);
|
|
sign_characters &= number_mask;
|
|
dumpbits32(sign_characters, "sign characters");
|
|
|
|
u32 digit_edges = ~(digit_characters << 1) & digit_characters;
|
|
dumpbits32(digit_edges, "digit_edges");
|
|
|
|
// check that we have 1-3 'edges' only
|
|
u32 t = digit_edges;
|
|
t &= t - 1;
|
|
t &= t - 1;
|
|
t &= t - 1;
|
|
error_sump |= t;
|
|
|
|
// check that we start with a digit
|
|
error_sump |= ~digit_characters & 0x1;
|
|
|
|
// having done some checks, get lazy and fall back
|
|
// to strtoll or strtod
|
|
// TODO: handle the easy cases ourselves; these are
|
|
// expensive and we've done a lot of the prepwork.
|
|
// return errors if strto* fail, otherwise fill in a code on the tape
|
|
// 'd' for floating point and 'l' for long and put a pointer to the
|
|
// spot in the buffer.
|
|
if ( digit_edges == 1) {
|
|
//if (__builtin_popcount(digit_edges) == 1) { // DANIEL : shouldn't we have digit_edges == 1
|
|
#define NAIVEINTPARSING
|
|
#ifdef NAIVEINTPARSING
|
|
// this is faster, maybe, because we use a naive strtoll
|
|
// should be all digits?
|
|
error_sump |= number_characters ^ digit_characters;
|
|
int stringlength = __builtin_ctz(~digit_characters);
|
|
const char *end = (const char *)src + stringlength;
|
|
u64 result = naivestrtoll((const char *)src,end);
|
|
if (found_minus) { // unfortunate that it is a branch?
|
|
result = -result;
|
|
}
|
|
#else
|
|
// try a strtoll
|
|
char *end;
|
|
s64 result = strtoll((const char *)src, &end, 10);
|
|
if ((errno != 0) || (end == (const char *)src)) {
|
|
error_sump |= 1;
|
|
}
|
|
error_sump |= is_not_structural_or_whitespace(*end);
|
|
if (found_minus) {
|
|
result = -result;
|
|
}
|
|
#endif
|
|
#ifdef DEBUG
|
|
cout << "Found number " << result << "\n";
|
|
#endif
|
|
pj.write_tape_s64(depth, result);
|
|
} else {
|
|
// try a strtod
|
|
char *end;
|
|
double result = strtod((const char *)src, &end);
|
|
if ((errno != 0) || (end == (const char *)src)) {
|
|
error_sump |= 1;
|
|
}
|
|
error_sump |= is_not_structural_or_whitespace(*end);
|
|
if (found_minus) {
|
|
result = -result;
|
|
}
|
|
#ifdef DEBUG
|
|
cout << "Found number " << result << "\n";
|
|
#endif
|
|
pj.write_tape_double(depth, result);
|
|
// HACK: return true regardless
|
|
return true; // FIXME: we have a spurious error here
|
|
}
|
|
// TODO: check the MSB element is a digit
|
|
|
|
// TODO: a whole bunch of checks
|
|
|
|
// TODO: <=1 decimal point, eE mark, +- construct
|
|
|
|
// TODO: first and last character in mask region must be
|
|
// digit
|
|
|
|
// TODO: if it exists,
|
|
// Decimal point is after the first cluster of numbers only
|
|
// and before the second cluster of numbers only. It must
|
|
// be digit_or_zero . digit_or_zero strictly
|
|
|
|
// TODO: eE mark and +- construct are adjacent with eE first
|
|
// eE mark preceeds final cluster of numbers only
|
|
// and immediately follows second-last cluster of numbers only (not
|
|
// necessarily second, as we may have 4e10).
|
|
// it may suffice to insist that eE is preceeded immediately
|
|
// by a digit of any kind and that it's followed locally by
|
|
// a digit immediately or a +- construct then a digit.
|
|
|
|
// TODO: if we have both . and the eE mark then the . must
|
|
// precede the eE mark
|
|
|
|
if (error_sump)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
// end copypasta
|
|
|
|
really_inline bool is_valid_true_atom(const u8 * loc) {
|
|
u64 tv = *(const u64 *)"true ";
|
|
u64 mask4 = 0x00000000ffffffff;
|
|
u32 error = 0;
|
|
u64 locval; // we want to avoid unaligned 64-bit loads (undefined in C/C++)
|
|
std::memcpy(&locval, loc, sizeof(u64));
|
|
error = (locval & mask4) ^ tv;
|
|
error |= is_not_structural_or_whitespace(loc[4]);
|
|
return error == 0;
|
|
}
|
|
|
|
really_inline bool is_valid_false_atom(const u8 * loc) {
|
|
u64 fv = *(const u64 *)"false ";
|
|
u64 mask5 = 0x000000ffffffffff;
|
|
u32 error = 0;
|
|
u64 locval; // we want to avoid unaligned 64-bit loads (undefined in C/C++)
|
|
std::memcpy(&locval, loc, sizeof(u64));
|
|
error = (locval & mask5) ^ fv;
|
|
error |= is_not_structural_or_whitespace(loc[5]);
|
|
return error == 0;
|
|
}
|
|
|
|
really_inline bool is_valid_null_atom(const u8 * loc) {
|
|
u64 nv = *(const u64 *)"null ";
|
|
u64 mask4 = 0x00000000ffffffff;
|
|
u32 error = 0;
|
|
u64 locval; // we want to avoid unaligned 64-bit loads (undefined in C/C++)
|
|
std::memcpy(&locval, loc, sizeof(u64));
|
|
error = (locval & mask4) ^ nv;
|
|
error |= is_not_structural_or_whitespace(loc[4]);
|
|
return error == 0;
|
|
}
|
|
|
|
bool unified_machine(const u8 *buf, size_t len, ParsedJson &pj) {
|
|
u32 i = 0;
|
|
u32 idx;
|
|
u8 c;
|
|
u32 depth = START_DEPTH; // an arbitrary starting depth
|
|
void * ret_address[MAX_DEPTH];
|
|
|
|
u32 last_loc = 0; // this is the location of the previous call site; only need one
|
|
// We should also track the tape address of our containing
|
|
// scope for two reasons. First, we will need to put an
|
|
// up pointer there at each call site so we can navigate
|
|
// upwards. Second, when we encounter the end of the scope
|
|
// we can put the current offset into a record for the
|
|
// scope so we know where it is
|
|
|
|
u32 containing_scope_offset[MAX_DEPTH];
|
|
|
|
pj.init();
|
|
|
|
// add a sentinel to the end to avoid premature exit
|
|
// need to be able to find the \0 at the 'padded length' end of the buffer
|
|
// FIXME: TERRIFYING!
|
|
size_t j;
|
|
for (j = len; buf[j] != 0; j++)
|
|
;
|
|
pj.structural_indexes[pj.n_structural_indexes++] = j;
|
|
|
|
#define UPDATE_CHAR() { idx = pj.structural_indexes[i++]; c = buf[idx]; DEBUG_PRINTF("Got %c at %d (%d offset)\n", c, idx, i-1);}
|
|
|
|
// format: call site has 2 entries: 56-bit + '{' or '[' entries pointing first to header then to this location
|
|
// scope has 2 entries: 56 + '_' entries pointing first to call site then to the last entry in this scope
|
|
|
|
#define OPEN_SCOPE() { \
|
|
pj.write_saved_loc(last_loc, pj.save_loc(depth), '_'); \
|
|
pj.write_tape(depth, last_loc, '_'); \
|
|
containing_scope_offset[depth] = pj.save_loc(depth); \
|
|
pj.write_tape(depth, 0, '_'); \
|
|
}
|
|
|
|
#define ESTABLISH_CALLSITE(RETURN_LABEL, SITE_LABEL) { \
|
|
pj.write_tape(depth, containing_scope_offset[depth], c); \
|
|
last_loc = pj.save_loc(depth); \
|
|
pj.write_tape(depth, 0, c); \
|
|
ret_address[depth] = RETURN_LABEL; \
|
|
depth++; \
|
|
goto SITE_LABEL; \
|
|
}
|
|
|
|
|
|
////////////////////////////// START STATE /////////////////////////////
|
|
|
|
DEBUG_PRINTF("at start\n");
|
|
UPDATE_CHAR();
|
|
// do these two speculatively as we will always do
|
|
// them except on fail, in which case it doesn't matter
|
|
ret_address[depth] = &&start_continue;
|
|
containing_scope_offset[depth] = pj.save_loc(depth);
|
|
pj.write_tape(depth, 0, c); // dummy entries
|
|
|
|
last_loc = pj.save_loc(depth);
|
|
pj.write_tape(depth, 0, c); // dummy entries
|
|
depth++;
|
|
|
|
switch (c) {
|
|
case '{': goto object_begin;
|
|
case '[': goto array_begin;
|
|
default: goto fail;
|
|
}
|
|
|
|
start_continue:
|
|
// land here after popping our outer object if an object
|
|
DEBUG_PRINTF("in start_object_close\n");
|
|
UPDATE_CHAR();
|
|
switch (c) {
|
|
case 0: goto succeed;
|
|
default: goto fail;
|
|
}
|
|
|
|
////////////////////////////// OBJECT STATES /////////////////////////////
|
|
|
|
object_begin:
|
|
DEBUG_PRINTF("in object_begin\n");
|
|
OPEN_SCOPE();
|
|
UPDATE_CHAR();
|
|
switch (c) {
|
|
case '"': {
|
|
if (!parse_string(buf, len, pj, depth, idx)) {
|
|
goto fail;
|
|
}
|
|
goto object_key_state;
|
|
}
|
|
case '}': goto scope_end;
|
|
default: goto fail;
|
|
}
|
|
|
|
object_key_state:
|
|
DEBUG_PRINTF("in object_key_state\n");
|
|
UPDATE_CHAR();
|
|
if (c != ':') {
|
|
goto fail;
|
|
}
|
|
UPDATE_CHAR();
|
|
switch (c) {
|
|
case '"': {
|
|
if (!parse_string(buf, len, pj, depth, idx)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case 't': if (!is_valid_true_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
case 'f': if (!is_valid_false_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
case 'n': if (!is_valid_null_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
case '0': {
|
|
if (!parse_number(buf, len, pj, depth, idx, true, false)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': {
|
|
if (!parse_number(buf, len, pj, depth, idx, false, false)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '-': {
|
|
if (!parse_number(buf, len, pj, depth, idx, false, true)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '{': {
|
|
ESTABLISH_CALLSITE(&&object_continue, object_begin);
|
|
}
|
|
case '[': {
|
|
ESTABLISH_CALLSITE(&&object_continue, array_begin);
|
|
}
|
|
default: goto fail;
|
|
}
|
|
|
|
object_continue:
|
|
DEBUG_PRINTF("in object_continue\n");
|
|
UPDATE_CHAR();
|
|
switch (c) {
|
|
case ',':
|
|
UPDATE_CHAR();
|
|
if (c != '"') {
|
|
goto fail;
|
|
} else {
|
|
if (!parse_string(buf, len, pj, depth, idx)) {
|
|
goto fail;
|
|
}
|
|
goto object_key_state;
|
|
}
|
|
case '}': goto scope_end;
|
|
default: goto fail;
|
|
}
|
|
|
|
////////////////////////////// COMMON STATE /////////////////////////////
|
|
|
|
scope_end:
|
|
// write our tape location to the header scope
|
|
pj.write_saved_loc(containing_scope_offset[depth], pj.save_loc(depth), '_');
|
|
depth--;
|
|
// goto saved_state
|
|
goto *ret_address[depth];
|
|
|
|
|
|
////////////////////////////// ARRAY STATES /////////////////////////////
|
|
|
|
array_begin:
|
|
DEBUG_PRINTF("in array_begin\n");
|
|
OPEN_SCOPE();
|
|
// fall through
|
|
|
|
UPDATE_CHAR();
|
|
if (c == ']') {
|
|
goto scope_end;
|
|
}
|
|
|
|
main_array_switch:
|
|
// we call update char on all paths in, so we can peek at c on the
|
|
// on paths that can accept a close square brace (post-, and at start)
|
|
switch (c) {
|
|
case '"': {
|
|
if (!parse_string(buf, len, pj, depth, idx)) {
|
|
goto fail;
|
|
}
|
|
goto array_continue;
|
|
}
|
|
case 't': if (!is_valid_true_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
case 'f': if (!is_valid_false_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
case 'n': if (!is_valid_null_atom(buf + idx)) {
|
|
goto fail;
|
|
}
|
|
pj.write_tape(depth, 0, c);
|
|
break;
|
|
|
|
case '0': {
|
|
if (!parse_number(buf, len, pj, depth, idx, true, false)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': {
|
|
if (!parse_number(buf, len, pj, depth, idx, false, false)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '-': {
|
|
if (!parse_number(buf, len, pj, depth, idx, false, true)) {
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
case '{': {
|
|
ESTABLISH_CALLSITE(&&array_continue, object_begin);
|
|
}
|
|
case '[': {
|
|
ESTABLISH_CALLSITE(&&array_continue, array_begin);
|
|
}
|
|
default: goto fail;
|
|
}
|
|
|
|
array_continue:
|
|
DEBUG_PRINTF("in array_continue\n");
|
|
UPDATE_CHAR();
|
|
switch (c) {
|
|
case ',': UPDATE_CHAR(); goto main_array_switch;
|
|
case ']': goto scope_end;
|
|
default: goto fail;
|
|
}
|
|
|
|
////////////////////////////// FINAL STATES /////////////////////////////
|
|
|
|
succeed:
|
|
DEBUG_PRINTF("in succeed\n");
|
|
#ifdef DEBUG
|
|
pj.dump_tapes();
|
|
#endif
|
|
return true;
|
|
|
|
fail:
|
|
DEBUG_PRINTF("in fail\n");
|
|
#ifdef DEBUG
|
|
pj.dump_tapes();
|
|
#endif
|
|
return false;
|
|
}
|