//----------------------------------------------------------------------- // 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