1352 lines
26 KiB
C++
1352 lines
26 KiB
C++
//-----------------------------------------------------------------------
|
|
// github.com/samisalreadytaken/sqdbg
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
|
|
#ifndef SQDBG_JSON_H
|
|
#define SQDBG_JSON_H
|
|
|
|
// Most messages are going to require less than 256 bytes,
|
|
// only approaching 1024 on large breakpoint requests
|
|
#define JSON_SCRATCH_CHUNK_SIZE 1024
|
|
|
|
typedef enum
|
|
{
|
|
JSON_NULL = 0x0000,
|
|
JSON_BOOL = 0x0001,
|
|
JSON_INTEGER = 0x0002,
|
|
JSON_FLOAT = 0x0004,
|
|
JSON_STRING = 0x0008,
|
|
JSON_TABLE = 0x0010,
|
|
JSON_ARRAY = 0x0020,
|
|
} JSONTYPE;
|
|
|
|
class json_table_t;
|
|
class json_array_t;
|
|
struct json_value_t;
|
|
|
|
struct ostr_t
|
|
{
|
|
// ushort can handle strings that fit the recv net buf,
|
|
// use a larger type for validation of sent messages in the send net buf
|
|
#ifdef SQDBG_VALIDATE_SENT_MSG
|
|
typedef unsigned int index_t;
|
|
#else
|
|
typedef unsigned short index_t;
|
|
#endif
|
|
|
|
index_t ofs;
|
|
index_t len;
|
|
};
|
|
|
|
struct json_value_t
|
|
{
|
|
union
|
|
{
|
|
ostr_t _string;
|
|
int _integer;
|
|
json_table_t *_table;
|
|
json_array_t *_array;
|
|
};
|
|
|
|
int type;
|
|
};
|
|
|
|
struct json_field_t
|
|
{
|
|
ostr_t key;
|
|
json_value_t val;
|
|
};
|
|
|
|
class json_array_t
|
|
{
|
|
public:
|
|
const char *m_pBase;
|
|
CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *m_Allocator;
|
|
int *m_Elements;
|
|
unsigned short m_nElementCount;
|
|
unsigned short m_nElementsSize;
|
|
|
|
void Init( const char *base, CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *allocator )
|
|
{
|
|
m_pBase = base;
|
|
m_Allocator = allocator;
|
|
m_nElementCount = 0;
|
|
m_nElementsSize = 0;
|
|
}
|
|
|
|
json_value_t *NewElement()
|
|
{
|
|
if ( m_nElementCount == m_nElementsSize )
|
|
{
|
|
// doesn't free old ptr, this is an uncommon operation and extra allocation is fine
|
|
int oldsize = m_nElementsSize;
|
|
int *oldptr = m_Elements;
|
|
|
|
m_nElementsSize = !m_nElementsSize ? 8 : ( m_nElementsSize << 1 );
|
|
m_Elements = (int*)m_Allocator->Alloc( m_nElementsSize * sizeof(int) );
|
|
|
|
if ( oldsize )
|
|
memcpy( m_Elements, oldptr, oldsize * sizeof(int) );
|
|
}
|
|
|
|
int index;
|
|
json_value_t *ret = (json_value_t*)m_Allocator->Alloc( sizeof(json_value_t), &index );
|
|
m_Elements[ m_nElementCount++ ] = index;
|
|
return ret;
|
|
}
|
|
|
|
int Size() const
|
|
{
|
|
return m_nElementCount;
|
|
}
|
|
|
|
bool GetString( int i, string_t *out )
|
|
{
|
|
Assert( m_nElementCount );
|
|
Assert( i >= 0 && i < m_nElementCount );
|
|
|
|
json_value_t *val = (json_value_t*)m_Allocator->Get( m_Elements[i] );
|
|
|
|
if ( val->type & JSON_STRING )
|
|
{
|
|
out->Assign( m_pBase + val->_string.ofs, val->_string.len );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GetTable( int i, json_table_t **out )
|
|
{
|
|
Assert( m_nElementCount );
|
|
Assert( i >= 0 && i < m_nElementCount );
|
|
|
|
json_value_t *val = (json_value_t*)m_Allocator->Get( m_Elements[i] );
|
|
|
|
if ( val->type & JSON_TABLE )
|
|
{
|
|
*out = val->_table;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class json_table_t
|
|
{
|
|
public:
|
|
const char *m_pBase;
|
|
CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *m_Allocator;
|
|
int *m_Elements;
|
|
unsigned short m_nElementCount;
|
|
unsigned short m_nElementsSize;
|
|
|
|
void Init( const char *base, CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *allocator )
|
|
{
|
|
m_pBase = base;
|
|
m_Allocator = allocator;
|
|
m_nElementCount = 0;
|
|
m_nElementsSize = 0;
|
|
}
|
|
|
|
json_value_t *Get( const string_t &key )
|
|
{
|
|
for ( int i = 0; i < m_nElementCount; i++ )
|
|
{
|
|
json_field_t *kv = (json_field_t*)m_Allocator->Get( m_Elements[i] );
|
|
|
|
if ( key.IsEqualTo( m_pBase + kv->key.ofs, kv->key.len ) )
|
|
return &kv->val;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
json_value_t *Get( const string_t &key ) const
|
|
{
|
|
return const_cast< json_table_t * >( this )->Get( key );
|
|
}
|
|
|
|
json_field_t *NewElement()
|
|
{
|
|
if ( m_nElementCount == m_nElementsSize )
|
|
{
|
|
int oldsize = m_nElementsSize;
|
|
int *oldptr = m_Elements;
|
|
|
|
m_nElementsSize = !m_nElementsSize ? 8 : ( m_nElementsSize << 1 );
|
|
m_Elements = (int*)m_Allocator->Alloc( m_nElementsSize * sizeof(int) );
|
|
|
|
if ( oldsize )
|
|
memcpy( m_Elements, oldptr, oldsize * sizeof(int) );
|
|
}
|
|
|
|
int index;
|
|
json_field_t *ret = (json_field_t*)m_Allocator->Alloc( sizeof(json_field_t), &index );
|
|
m_Elements[ m_nElementCount++ ] = index;
|
|
return ret;
|
|
}
|
|
|
|
bool GetBool( const string_t &key, bool *out ) const
|
|
{
|
|
json_value_t *kval = Get( key );
|
|
|
|
if ( kval && ( kval->type & JSON_BOOL ) )
|
|
{
|
|
*out = kval->_integer;
|
|
return true;
|
|
}
|
|
|
|
*out = false;
|
|
return false;
|
|
}
|
|
|
|
bool GetInt( const string_t &key, int *out, int defaultVal = 0 ) const
|
|
{
|
|
json_value_t *kval = Get( key );
|
|
|
|
if ( kval && ( kval->type & JSON_INTEGER ) )
|
|
{
|
|
*out = kval->_integer;
|
|
return true;
|
|
}
|
|
|
|
*out = defaultVal;
|
|
return false;
|
|
}
|
|
|
|
bool GetString( const string_t &key, string_t *out, const char *defaultVal = "" ) const
|
|
{
|
|
json_value_t *kval = Get( key );
|
|
|
|
if ( kval && ( kval->type & JSON_STRING ) )
|
|
{
|
|
out->Assign( m_pBase + kval->_string.ofs, kval->_string.len );
|
|
return true;
|
|
}
|
|
|
|
out->Assign( defaultVal, strlen(defaultVal) );
|
|
return false;
|
|
}
|
|
|
|
bool GetTable( const string_t &key, json_table_t **out ) const
|
|
{
|
|
json_value_t *kval = Get( key );
|
|
|
|
if ( kval && ( kval->type & JSON_TABLE ) )
|
|
{
|
|
*out = kval->_table;
|
|
return true;
|
|
}
|
|
|
|
*out = NULL;
|
|
return false;
|
|
}
|
|
|
|
bool GetArray( const string_t &key, json_array_t **out ) const
|
|
{
|
|
json_value_t *kval = Get( key );
|
|
|
|
if ( kval && ( kval->type & JSON_ARRAY ) )
|
|
{
|
|
*out = kval->_array;
|
|
return true;
|
|
}
|
|
|
|
*out = NULL;
|
|
return false;
|
|
}
|
|
|
|
bool Get( const string_t &key, bool *out ) const { return GetBool( key, out ); }
|
|
bool Get( const string_t &key, int *out ) const { return GetInt( key, out ); }
|
|
bool Get( const string_t &key, string_t *out ) const { return GetString( key, out ); }
|
|
bool Get( const string_t &key, json_table_t **out ) const { return GetTable( key, out ); }
|
|
bool Get( const string_t &key, json_array_t **out ) const { return GetArray( key, out ); }
|
|
};
|
|
|
|
static inline void PutStr( CBuffer *buffer, const string_t &str )
|
|
{
|
|
buffer->base.Ensure( buffer->Size() + str.len );
|
|
memcpy( buffer->Base() + buffer->Size(), str.ptr, str.len );
|
|
buffer->size += str.len;
|
|
|
|
#ifdef SQDBG_VALIDATE_SENT_MSG
|
|
for ( unsigned int i = 0; i < str.len; i++ )
|
|
{
|
|
if ( str.ptr[i] == '\\' &&
|
|
( str.ptr[i+1] == '\\' ||
|
|
str.ptr[i+1] == '\"' ||
|
|
str.ptr[i+1] == 'n' ||
|
|
str.ptr[i+1] == 'r' ||
|
|
str.ptr[i+1] == 't' ) )
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
AssertMsg( str.ptr[i] != '\\' && IN_RANGE_CHAR( str.ptr[i], 0x20, 0x7E ), "control char in json string" );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline void PutStr( CBuffer *buffer, const string_t &str, bool quote )
|
|
{
|
|
const char *c = str.ptr;
|
|
unsigned int i = str.len;
|
|
|
|
unsigned int len = i;
|
|
|
|
if ( quote )
|
|
len += 4;
|
|
|
|
for ( ; i--; c++ )
|
|
{
|
|
switch ( *c )
|
|
{
|
|
case '\\': case '\"':
|
|
case '\a': case '\b': case '\f':
|
|
case '\n': case '\r': case '\t': case '\v':
|
|
len++;
|
|
if ( quote )
|
|
{
|
|
len++;
|
|
if ( *c == '\\' || *c == '\"' )
|
|
len++;
|
|
}
|
|
break;
|
|
default:
|
|
if ( !IN_RANGE_CHAR( *c, 0x20, 0x7E ) )
|
|
{
|
|
int ret = IsValidUTF8( (unsigned char*)c, i + 1 );
|
|
if ( ret != 0 )
|
|
{
|
|
i -= ret - 1;
|
|
c += ret - 1;
|
|
}
|
|
else
|
|
{
|
|
if ( !quote )
|
|
{
|
|
len += sizeof(uint16_t) * 2 + 1;
|
|
}
|
|
else
|
|
{
|
|
len += sizeof(SQChar) * 2 + 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
buffer->base.Ensure( buffer->Size() + len );
|
|
|
|
char *mem = buffer->Base();
|
|
unsigned int idx = buffer->Size();
|
|
|
|
c = str.ptr;
|
|
i = str.len;
|
|
|
|
if ( quote )
|
|
{
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = '\"';
|
|
}
|
|
|
|
for ( ; i--; c++ )
|
|
{
|
|
mem[idx++] = *c;
|
|
|
|
switch ( *c )
|
|
{
|
|
case '\\':
|
|
case '\"':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
{
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = '\\';
|
|
}
|
|
mem[idx++] = *c;
|
|
break;
|
|
case '\a':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'a';
|
|
break;
|
|
case '\b':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'b';
|
|
break;
|
|
case '\f':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'f';
|
|
break;
|
|
case '\n':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'n';
|
|
break;
|
|
case '\r':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'r';
|
|
break;
|
|
case '\t':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 't';
|
|
break;
|
|
case '\v':
|
|
mem[idx-1] = '\\';
|
|
if ( quote )
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'v';
|
|
break;
|
|
default:
|
|
if ( !IN_RANGE_CHAR( *c, 0x20, 0x7E ) )
|
|
{
|
|
int ret = IsValidUTF8( (unsigned char*)c, i + 1 );
|
|
if ( ret != 0 )
|
|
{
|
|
memcpy( mem + idx, c + 1, ret - 1 );
|
|
idx += ret - 1;
|
|
i -= ret - 1;
|
|
c += ret - 1;
|
|
}
|
|
else
|
|
{
|
|
mem[idx-1] = '\\';
|
|
|
|
if ( !quote )
|
|
{
|
|
mem[idx++] = 'u';
|
|
idx += printhex< true, false >(
|
|
mem + idx,
|
|
buffer->Capacity() - idx,
|
|
(uint16_t)*(unsigned char*)c );
|
|
}
|
|
else
|
|
{
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = 'x';
|
|
idx += printhex< true, false >(
|
|
mem + idx,
|
|
buffer->Capacity() - idx,
|
|
(SQUnsignedChar)*(unsigned char*)c );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( quote )
|
|
{
|
|
mem[idx++] = '\\';
|
|
mem[idx++] = '\"';
|
|
}
|
|
|
|
buffer->size = idx;
|
|
}
|
|
|
|
#ifdef SQUNICODE
|
|
static inline void PutStr( CBuffer *buffer, const sqstring_t &str, bool quote )
|
|
{
|
|
unsigned int len;
|
|
|
|
if ( !quote )
|
|
{
|
|
len = UTF8Length< kUTFEscapeJSON >( str.ptr, str.len );
|
|
}
|
|
else
|
|
{
|
|
len = UTF8Length< kUTFEscapeQuoted >( str.ptr, str.len );
|
|
}
|
|
|
|
buffer->base.Ensure( buffer->Size() + len );
|
|
|
|
if ( !quote )
|
|
{
|
|
len = SQUnicodeToUTF8< kUTFEscapeJSON >(
|
|
buffer->Base() + buffer->Size(),
|
|
buffer->Capacity() - buffer->Size(),
|
|
str.ptr,
|
|
str.len );
|
|
}
|
|
else
|
|
{
|
|
len = SQUnicodeToUTF8< kUTFEscapeQuoted >(
|
|
buffer->Base() + buffer->Size(),
|
|
buffer->Capacity() - buffer->Size(),
|
|
str.ptr,
|
|
str.len );
|
|
}
|
|
|
|
buffer->size += len;
|
|
}
|
|
#endif
|
|
|
|
static inline void PutChar( CBuffer *buffer, char c )
|
|
{
|
|
buffer->base.Ensure( buffer->Size() + 1 );
|
|
buffer->Base()[buffer->size++] = c;
|
|
}
|
|
|
|
template < typename I >
|
|
static inline void PutInt( CBuffer *buffer, I val )
|
|
{
|
|
buffer->base.Ensure( buffer->Size() + countdigits( val ) + 1 );
|
|
int len = printint( buffer->Base() + buffer->Size(), buffer->Capacity() - buffer->Size(), val );
|
|
buffer->size += len;
|
|
}
|
|
|
|
template < bool padding, typename I >
|
|
static inline void PutHex( CBuffer *buffer, I val )
|
|
{
|
|
STATIC_ASSERT( IS_UNSIGNED( I ) );
|
|
buffer->base.Ensure( buffer->Size() + countdigits<16>( val ) + 1 );
|
|
int len = printhex< padding >( buffer->Base() + buffer->Size(), buffer->Capacity() - buffer->Size(), val );
|
|
buffer->size += len;
|
|
}
|
|
|
|
struct jstringbuf_t
|
|
{
|
|
CBuffer *m_pBuffer;
|
|
|
|
jstringbuf_t( CBuffer *b ) : m_pBuffer(b)
|
|
{
|
|
::PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
~jstringbuf_t()
|
|
{
|
|
::PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
jstringbuf_t( const jstringbuf_t &src );
|
|
|
|
void Seek( int i )
|
|
{
|
|
m_pBuffer->size += i;
|
|
}
|
|
|
|
template < int SIZE >
|
|
void Puts( const char (&str)[SIZE] )
|
|
{
|
|
::PutStr( m_pBuffer, str );
|
|
}
|
|
|
|
void Puts( const conststring_t &str )
|
|
{
|
|
::PutStr( m_pBuffer, str );
|
|
}
|
|
|
|
void Puts( const string_t &str, bool quote = false )
|
|
{
|
|
::PutStr( m_pBuffer, str, quote );
|
|
}
|
|
|
|
#ifdef SQUNICODE
|
|
void Puts( const sqstring_t &str, bool quote = false )
|
|
{
|
|
::PutStr( m_pBuffer, str, quote );
|
|
}
|
|
#endif
|
|
|
|
void Put( char c )
|
|
{
|
|
::PutChar( m_pBuffer, c );
|
|
}
|
|
|
|
template < typename I >
|
|
void PutInt( I val )
|
|
{
|
|
::PutInt( m_pBuffer, val );
|
|
}
|
|
|
|
template < typename I >
|
|
void PutHex( I val, bool padding = true )
|
|
{
|
|
if ( padding )
|
|
{
|
|
::PutHex< true >( m_pBuffer, val );
|
|
}
|
|
else
|
|
{
|
|
::PutHex< false >( m_pBuffer, val );
|
|
}
|
|
}
|
|
};
|
|
|
|
class wjson_t
|
|
{
|
|
public:
|
|
CBuffer *m_pBuffer;
|
|
int m_nElementCount;
|
|
|
|
wjson_t( CBuffer *b ) :
|
|
m_pBuffer(b),
|
|
m_nElementCount(0)
|
|
{
|
|
}
|
|
};
|
|
|
|
class wjson_table_t : public wjson_t
|
|
{
|
|
public:
|
|
wjson_table_t( CBuffer &b ) : wjson_t(&b)
|
|
{
|
|
PutChar( m_pBuffer, '{' );
|
|
}
|
|
|
|
~wjson_table_t()
|
|
{
|
|
PutChar( m_pBuffer, '}' );
|
|
}
|
|
|
|
wjson_table_t( const wjson_t &src ) : wjson_t(src)
|
|
{
|
|
}
|
|
|
|
wjson_table_t( const wjson_table_t &src );
|
|
|
|
void PutKey( const string_t &key )
|
|
{
|
|
if ( m_nElementCount++ )
|
|
PutChar( m_pBuffer, ',' );
|
|
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutChar( m_pBuffer, ':' );
|
|
}
|
|
|
|
void SetInt( const string_t &key, int val )
|
|
{
|
|
PutKey( key );
|
|
PutInt( m_pBuffer, val );
|
|
}
|
|
|
|
void SetNull( const string_t &key )
|
|
{
|
|
PutKey( key );
|
|
PutStr( m_pBuffer, "null" );
|
|
}
|
|
|
|
void SetBool( const string_t &key, bool val )
|
|
{
|
|
PutKey( key );
|
|
PutStr( m_pBuffer, val ? string_t("true") : string_t("false") );
|
|
}
|
|
|
|
jstringbuf_t SetStringAsBuf( const string_t &key )
|
|
{
|
|
PutKey( key );
|
|
return { m_pBuffer };
|
|
}
|
|
|
|
template < int SIZE >
|
|
void SetString( const string_t &key, const char (&val)[SIZE] )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, val );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
void SetString( const string_t &key, const conststring_t &val )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, val );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
void SetString( const string_t &key, const string_t &val, bool quote = false )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, val, quote );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
#ifdef SQUNICODE
|
|
void SetString( const string_t &key, const sqstring_t &val, bool quote = false )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, val, quote );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
#endif
|
|
|
|
void SetIntString( const string_t &key, int val )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutInt( m_pBuffer, val );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
template < typename I >
|
|
void SetIntBrackets( const string_t &key, I val, bool hex = false )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutChar( m_pBuffer, '[' );
|
|
if ( !hex )
|
|
{
|
|
PutInt( m_pBuffer, val );
|
|
}
|
|
else
|
|
{
|
|
PutHex< false >( m_pBuffer, cast_unsigned( I, val ) );
|
|
}
|
|
PutChar( m_pBuffer, ']' );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
|
|
wjson_t SetArray( const string_t &key )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '[' );
|
|
return { m_pBuffer };
|
|
}
|
|
|
|
wjson_t SetTable( const string_t &key )
|
|
{
|
|
PutKey( key );
|
|
PutChar( m_pBuffer, '{' );
|
|
return { m_pBuffer };
|
|
}
|
|
|
|
void Set( const string_t &key, bool val ) { SetBool( key, val ); }
|
|
void Set( const string_t &key, int val ) { SetInt( key, val ); }
|
|
void Set( const string_t &key, unsigned int val ) { SetInt( key, val ); }
|
|
void Set( const string_t &key, const string_t &val ) { SetString( key, val ); }
|
|
};
|
|
|
|
class wjson_array_t : public wjson_t
|
|
{
|
|
public:
|
|
wjson_array_t( CBuffer &b ) : wjson_t(&b)
|
|
{
|
|
PutChar( m_pBuffer, '[' );
|
|
}
|
|
|
|
~wjson_array_t()
|
|
{
|
|
PutChar( m_pBuffer, ']' );
|
|
}
|
|
|
|
wjson_array_t( const wjson_t &src ) : wjson_t(src)
|
|
{
|
|
}
|
|
|
|
wjson_array_t( const wjson_array_t &src );
|
|
|
|
int Size()
|
|
{
|
|
return m_nElementCount;
|
|
}
|
|
|
|
wjson_t AppendTable()
|
|
{
|
|
if ( m_nElementCount++ )
|
|
PutChar( m_pBuffer, ',' );
|
|
|
|
PutChar( m_pBuffer, '{' );
|
|
return { m_pBuffer };
|
|
}
|
|
|
|
void Append( int val )
|
|
{
|
|
if ( m_nElementCount++ )
|
|
PutChar( m_pBuffer, ',' );
|
|
|
|
PutInt( m_pBuffer, val );
|
|
}
|
|
|
|
void Append( const string_t &val )
|
|
{
|
|
if ( m_nElementCount++ )
|
|
PutChar( m_pBuffer, ',' );
|
|
|
|
PutChar( m_pBuffer, '\"' );
|
|
PutStr( m_pBuffer, val );
|
|
PutChar( m_pBuffer, '\"' );
|
|
}
|
|
};
|
|
|
|
class JSONParser
|
|
{
|
|
private:
|
|
char *m_cur;
|
|
char *m_end;
|
|
char *m_start;
|
|
CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *m_Allocator;
|
|
char *m_error;
|
|
|
|
enum
|
|
{
|
|
Token_Error = 0,
|
|
Token_String,
|
|
Token_Integer,
|
|
Token_Float,
|
|
Token_False,
|
|
Token_True = Token_False + 1,
|
|
Token_Null,
|
|
Token_Table = '{',
|
|
Token_Array = '[',
|
|
};
|
|
|
|
public:
|
|
JSONParser( CScratch< true, JSON_SCRATCH_CHUNK_SIZE > *allocator, char *ptr, int len, json_table_t *pTable ) :
|
|
m_cur( ptr ),
|
|
m_end( ptr + len + 1 ),
|
|
m_start( ptr ),
|
|
m_Allocator( allocator ),
|
|
m_error( NULL )
|
|
{
|
|
string_t token;
|
|
char type = NextToken( token );
|
|
|
|
if ( type == '{' )
|
|
{
|
|
pTable->Init( m_start, m_Allocator );
|
|
ParseTable( pTable, token );
|
|
}
|
|
else
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", '{', Char(type), Index() );
|
|
}
|
|
}
|
|
|
|
const char *GetError() const
|
|
{
|
|
return m_error;
|
|
}
|
|
|
|
private:
|
|
int Index()
|
|
{
|
|
return m_cur - m_start;
|
|
}
|
|
|
|
char *Char( char token )
|
|
{
|
|
char *buf;
|
|
|
|
if ( token == Token_Error )
|
|
token = *m_cur;
|
|
|
|
if ( IN_RANGE_CHAR( token, 0x20, 0x7E ) )
|
|
{
|
|
buf = m_Allocator->Alloc(4);
|
|
buf[0] = '\'';
|
|
buf[1] = token;
|
|
buf[2] = '\'';
|
|
buf[3] = 0;
|
|
}
|
|
else
|
|
{
|
|
buf = m_Allocator->Alloc(5);
|
|
int i = printhex< true, true, false >( buf, 5, (unsigned char)token );
|
|
Assert( i == 4 );
|
|
buf[i] = 0;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
void SetError( const char *fmt, ... )
|
|
{
|
|
if ( m_error )
|
|
return;
|
|
|
|
const int size = 48;
|
|
m_error = m_Allocator->Alloc( size );
|
|
|
|
va_list va;
|
|
va_start( va, fmt );
|
|
int len = vsnprintf( m_error, size, fmt, va );
|
|
va_end( va );
|
|
|
|
if ( len < 0 || len > size-1 )
|
|
len = size-1;
|
|
|
|
m_error[len] = 0;
|
|
}
|
|
|
|
bool IsValue( char token )
|
|
{
|
|
switch ( token )
|
|
{
|
|
case Token_String:
|
|
case Token_Integer:
|
|
case Token_Float:
|
|
case Token_False:
|
|
case Token_True:
|
|
case Token_Null:
|
|
case Token_Table:
|
|
case Token_Array:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
char NextToken( string_t &token )
|
|
{
|
|
while ( m_cur < m_end )
|
|
{
|
|
switch ( *m_cur )
|
|
{
|
|
case 0x20: case 0x0A: case 0x0D: case 0x09:
|
|
m_cur++;
|
|
break;
|
|
|
|
case '\"':
|
|
return ParseString( token );
|
|
|
|
case '-':
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
return ParseNumber( token );
|
|
|
|
case ':': case ',':
|
|
case '{': case '}':
|
|
case '[': case ']':
|
|
return *m_cur++;
|
|
|
|
case 't':
|
|
if ( m_cur + 4 < m_end &&
|
|
m_cur[1] == 'r' && m_cur[2] == 'u' && m_cur[3] == 'e' )
|
|
{
|
|
m_cur += 4;
|
|
return Token_True;
|
|
}
|
|
|
|
SetError( "expected %s @ %i", "\"true\"", Index() );
|
|
return Token_Error;
|
|
|
|
case 'f':
|
|
if ( m_cur + 5 < m_end &&
|
|
m_cur[1] == 'a' && m_cur[2] == 'l' && m_cur[3] == 's' && m_cur[4] == 'e' )
|
|
{
|
|
m_cur += 5;
|
|
return Token_False;
|
|
}
|
|
|
|
SetError( "expected %s @ %i", "\"false\"", Index() );
|
|
return Token_Error;
|
|
|
|
case 'n':
|
|
if ( m_cur + 4 < m_end &&
|
|
m_cur[1] == 'u' && m_cur[2] == 'l' && m_cur[3] == 'l' )
|
|
{
|
|
m_cur += 4;
|
|
return Token_Null;
|
|
}
|
|
|
|
SetError( "expected %s @ %i", "\"null\"", Index() );
|
|
return Token_Error;
|
|
|
|
default:
|
|
return Token_Error;
|
|
}
|
|
}
|
|
|
|
return Token_Error;
|
|
}
|
|
|
|
char ParseString( string_t &token )
|
|
{
|
|
char *pStart = ++m_cur;
|
|
bool bEscape = false;
|
|
|
|
for (;;)
|
|
{
|
|
if ( m_cur >= m_end )
|
|
{
|
|
SetError( "unfinished string @ %i", Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
if ( *m_cur == '\"' )
|
|
{
|
|
token.Assign( pStart, m_cur - pStart );
|
|
*m_cur++ = 0;
|
|
break;
|
|
}
|
|
|
|
if ( *m_cur != '\\' )
|
|
{
|
|
m_cur++;
|
|
continue;
|
|
}
|
|
|
|
m_cur++;
|
|
|
|
if ( m_cur >= m_end )
|
|
{
|
|
SetError( "unfinished string @ %i", Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
bEscape = true;
|
|
|
|
// Defer unescape until the end of the string is found
|
|
switch ( *m_cur )
|
|
{
|
|
case '\\': case '\"': case '/':
|
|
case 'b': case 'f':
|
|
case 'n': case 'r': case 't':
|
|
m_cur++;
|
|
break;
|
|
|
|
case 'u':
|
|
if ( m_cur + 4 >= m_end ||
|
|
!_isxdigit( m_cur[1] ) || !_isxdigit( m_cur[2] ) ||
|
|
!_isxdigit( m_cur[3] ) || !_isxdigit( m_cur[4] ) )
|
|
{
|
|
SetError( "invalid hex escape @ %i", Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
m_cur += 4;
|
|
break;
|
|
|
|
default:
|
|
SetError( "invalid escape char 0x%02x @ %i", *(unsigned char*)m_cur, Index() );
|
|
return Token_Error;
|
|
}
|
|
}
|
|
|
|
if ( bEscape )
|
|
{
|
|
char *cur = pStart;
|
|
char *end = pStart + token.len;
|
|
|
|
do
|
|
{
|
|
if ( cur[0] != '\\' )
|
|
{
|
|
cur++;
|
|
continue;
|
|
}
|
|
|
|
#define _shift( bytesWritten, bytesRead ) \
|
|
Assert( (bytesWritten) < (bytesRead) ); \
|
|
memmove( cur + (bytesWritten), cur + (bytesRead), end - ( cur + (bytesRead) ) ); \
|
|
cur += (bytesWritten); \
|
|
end -= (bytesRead) - (bytesWritten);
|
|
|
|
switch ( cur[1] )
|
|
{
|
|
case '\\':
|
|
shift_one:
|
|
_shift( 1, 2 );
|
|
break;
|
|
case '\"': cur[0] = '\"'; goto shift_one;
|
|
case '/': cur[0] = '/'; goto shift_one;
|
|
case 'b': cur[0] = '\b'; goto shift_one;
|
|
case 'f': cur[0] = '\f'; goto shift_one;
|
|
case 'n': cur[0] = '\n'; goto shift_one;
|
|
case 'r': cur[0] = '\r'; goto shift_one;
|
|
case 't': cur[0] = '\t'; goto shift_one;
|
|
case 'u':
|
|
{
|
|
unsigned int val;
|
|
Verify( atox( { cur + 2, 4 }, &val ) );
|
|
|
|
if ( val <= 0x7F )
|
|
{
|
|
cur[0] = (char)val;
|
|
|
|
_shift( 1, 6 );
|
|
break;
|
|
}
|
|
else if ( val <= 0x7FF )
|
|
{
|
|
UTF8_2_FROM_UTF32( (unsigned char*)cur, val );
|
|
|
|
_shift( 2, 6 );
|
|
break;
|
|
}
|
|
else if ( UTF_SURROGATE(val) )
|
|
{
|
|
if ( UTF_SURROGATE_LEAD(val) )
|
|
{
|
|
if ( cur + 11 < end &&
|
|
cur[6] == '\\' && cur[7] == 'u' &&
|
|
_isxdigit( cur[8] ) && _isxdigit( cur[9] ) &&
|
|
_isxdigit( cur[10] ) && _isxdigit( cur[11] ) )
|
|
{
|
|
unsigned int low;
|
|
Verify( atox( { cur + 8, 4 }, &low ) );
|
|
|
|
if ( UTF_SURROGATE_TRAIL( low ) )
|
|
{
|
|
val = UTF32_FROM_UTF16_SURROGATE( val, low );
|
|
UTF8_4_FROM_UTF32( (unsigned char*)cur, val );
|
|
|
|
_shift( 4, 12 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UTF8_3_FROM_UTF32( (unsigned char*)cur, val );
|
|
|
|
_shift( 3, 6 );
|
|
break;
|
|
}
|
|
default: UNREACHABLE();
|
|
}
|
|
|
|
#undef _shift
|
|
}
|
|
while ( cur < end );
|
|
|
|
token.len = end - pStart;
|
|
token.ptr[token.len] = 0;
|
|
}
|
|
|
|
return Token_String;
|
|
}
|
|
|
|
char ParseNumber( string_t &token )
|
|
{
|
|
const char *pStart = m_cur;
|
|
char type;
|
|
|
|
if ( *m_cur == '-' )
|
|
{
|
|
m_cur++;
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
}
|
|
|
|
if ( *m_cur == '0' )
|
|
{
|
|
m_cur++;
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
}
|
|
else if ( IN_RANGE_CHAR( *m_cur, '1', '9' ) )
|
|
{
|
|
do
|
|
{
|
|
m_cur++;
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
}
|
|
while ( IN_RANGE_CHAR( *m_cur, '0', '9' ) );
|
|
}
|
|
else
|
|
{
|
|
SetError( "unexpected char 0x%02x in number @ %i", *(unsigned char*)m_cur, Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
type = Token_Integer;
|
|
|
|
if ( *m_cur == '.' )
|
|
{
|
|
type = Token_Float;
|
|
m_cur++;
|
|
|
|
while ( m_cur < m_end && IN_RANGE_CHAR( *m_cur, '0', '9' ) )
|
|
m_cur++;
|
|
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
}
|
|
|
|
if ( *m_cur == 'e' || *m_cur == 'E' )
|
|
{
|
|
type = Token_Float;
|
|
m_cur++;
|
|
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
|
|
if ( *m_cur == '-' || *m_cur == '+' )
|
|
{
|
|
m_cur++;
|
|
|
|
if ( m_cur >= m_end )
|
|
goto err_eof;
|
|
}
|
|
|
|
while ( m_cur < m_end && IN_RANGE_CHAR( *m_cur, '0', '9' ) )
|
|
m_cur++;
|
|
}
|
|
|
|
token.Assign( pStart, m_cur - pStart );
|
|
return type;
|
|
|
|
err_eof:
|
|
SetError( "unexpected eof" );
|
|
return Token_Error;
|
|
}
|
|
|
|
char ParseTable( json_table_t *pTable, string_t &token )
|
|
{
|
|
char type = NextToken( token );
|
|
|
|
if ( type == '}' )
|
|
return Token_Table;
|
|
|
|
for (;;)
|
|
{
|
|
if ( type != Token_String )
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", '\"', Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
type = NextToken( token );
|
|
|
|
if ( type != ':' )
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", ':', Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
json_field_t *kv = pTable->NewElement();
|
|
|
|
Assert( token.ptr - m_start < (ostr_t::index_t)-1 );
|
|
kv->key.ofs = token.ptr - m_start;
|
|
kv->key.len = (ostr_t::index_t)token.len;
|
|
|
|
type = NextToken( token );
|
|
type = ParseValue( type, token, &kv->val );
|
|
|
|
if ( !IsValue( type ) )
|
|
{
|
|
SetError( "invalid token %s @ %i", Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
type = NextToken( token );
|
|
|
|
if ( type == ',' )
|
|
{
|
|
type = NextToken( token );
|
|
}
|
|
else if ( type == '}' )
|
|
{
|
|
return Token_Table;
|
|
}
|
|
else
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", '}', Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
}
|
|
}
|
|
|
|
char ParseArray( json_array_t *pArray, string_t &token )
|
|
{
|
|
char type = NextToken( token );
|
|
|
|
if ( type == ']' )
|
|
return Token_Array;
|
|
|
|
for (;;)
|
|
{
|
|
if ( !IsValue( type ) )
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", ']', Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
json_value_t *val = pArray->NewElement();
|
|
type = ParseValue( type, token, val );
|
|
|
|
if ( type == Token_Error )
|
|
{
|
|
SetError( "invalid token %s @ %i", Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
type = NextToken( token );
|
|
|
|
if ( type == ',' )
|
|
{
|
|
type = NextToken( token );
|
|
}
|
|
else if ( type == ']' )
|
|
{
|
|
return Token_Array;
|
|
}
|
|
else
|
|
{
|
|
SetError( "expected '%c', got %s @ %i", ']', Char(type), Index() );
|
|
return Token_Error;
|
|
}
|
|
}
|
|
}
|
|
|
|
char ParseValue( char type, string_t &token, json_value_t *value )
|
|
{
|
|
switch ( type )
|
|
{
|
|
case Token_Integer:
|
|
if ( token.len > FMT_UINT32_LEN + 1 )
|
|
{
|
|
SetError( "invalid integer literal @ %i", Index() );
|
|
return Token_Error;
|
|
}
|
|
|
|
value->type = JSON_INTEGER;
|
|
Verify( atoi( token, &value->_integer ) );
|
|
return type;
|
|
case Token_Float:
|
|
value->type = JSON_FLOAT;
|
|
// floats are unused, ignore value
|
|
return type;
|
|
case Token_String:
|
|
value->type = JSON_STRING;
|
|
Assert( token.ptr - m_start < (ostr_t::index_t)-1 );
|
|
value->_string.ofs = token.ptr - m_start;
|
|
value->_string.len = (ostr_t::index_t)token.len;
|
|
return type;
|
|
case '{':
|
|
value->type = JSON_TABLE;
|
|
value->_table = (json_table_t*)m_Allocator->Alloc( sizeof(json_table_t) );
|
|
value->_table->Init( m_start, m_Allocator );
|
|
return ParseTable( value->_table, token );
|
|
case '[':
|
|
value->type = JSON_ARRAY;
|
|
value->_array = (json_array_t*)m_Allocator->Alloc( sizeof(json_array_t) );
|
|
value->_array->Init( m_start, m_Allocator );
|
|
return ParseArray( value->_array, token );
|
|
case Token_False:
|
|
case Token_True:
|
|
value->type = JSON_BOOL;
|
|
value->_integer = type - Token_False;
|
|
return type;
|
|
case Token_Null:
|
|
value->type = JSON_NULL;
|
|
return type;
|
|
default:
|
|
return Token_Error;
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif // SQDBG_JSON_H
|