bifurcado de 14134597/fast_json
1621 líneas
Sin EOL
45 KiB
MQL5
1621 líneas
Sin EOL
45 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| fast_json.mqh |
|
|
//| AI Toolkit - Neural Execution Unified System |
|
|
//| |
|
|
//| Module: Json (The "War Machine" Edition V3) |
|
|
//| Description: PRODUCTION GRADE HIGH PERFORMANCE JSON LIBRARY |
|
|
//| - Iterative State Machine (No Recursion) |
|
|
//| - SWAR Scanner (Simulated 64-bit) |
|
|
//| - SWAR 8-Digit Number Parsing (Lemire) |
|
|
//| - Decomposed Number Parser (fxsaber) |
|
|
//| - Tape-based Zero-Alloc Memory Model |
|
|
//| - CJsonNode Handle-based Navigation |
|
|
//| - Digit-Pair Serialization Tables |
|
|
//| |
|
|
//| Author: Jonathan Pereira |
|
|
//| Created: 2022 |
|
|
//| Version: 3.7.1 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "AIToolkit Framework"
|
|
#property version "3.71"
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constants & Masks |
|
|
//+------------------------------------------------------------------+
|
|
#define J_NULL 0x00
|
|
#define J_BOOL 0x01
|
|
#define J_INT 0x02
|
|
#define J_DBL 0x03
|
|
#define J_STR 0x04
|
|
#define J_ARR 0x05
|
|
#define J_OBJ 0x06
|
|
#define J_KEY 0x07 // Internal use
|
|
|
|
// SWAR Constants
|
|
#define SWAR_LO 0x0101010101010101
|
|
#define SWAR_HI 0x8080808080808080
|
|
#define SWAR_QUOTE 0x2222222222222222
|
|
#define SWAR_SLASH 0x5C5C5C5C5C5C5C5C
|
|
|
|
// Char Class
|
|
#define CC_WHITE 1
|
|
#define CC_STRUCT 2
|
|
#define CC_QUOTE 3
|
|
#define CC_DIGIT 4
|
|
#define CC_OTHER 0
|
|
|
|
// Parser States
|
|
#define ST_VAL 0
|
|
#define ST_ARR 1
|
|
#define ST_OBJ 2
|
|
#define ST_KEY 3
|
|
|
|
#define MACRO_GET_STEP(idx) ((int)(tape[idx] & 0xFFFFFFFF))
|
|
|
|
// Error Codes
|
|
enum EnumJsonError {
|
|
JSON_OK = 0,
|
|
JSON_ERR_INVALID_CHAR,
|
|
JSON_ERR_UNEXPECTED_END,
|
|
JSON_ERR_STACK_OVERFLOW,
|
|
JSON_ERR_INVALID_NUMBER,
|
|
JSON_ERR_EXPECTED_KEY,
|
|
JSON_ERR_EXPECTED_COLON,
|
|
JSON_ERR_WRONG_TYPE
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Globals (Lookup Tables) |
|
|
//+------------------------------------------------------------------+
|
|
uchar g_cc[256];
|
|
bool g_init = false;
|
|
|
|
// v3.6.0: Extended Pow10 tables (covers full DBL_MAX_10_EXP=308, eliminates ALL
|
|
// MathPow)
|
|
double g_Exp10[309]; // 1e0, 1e-1, ..., 1e-308
|
|
double g_Pow10[309]; // 1e0, 1e1, ..., 1e308
|
|
|
|
// v3.6.0: Digit-pair lookup table for fast integer serialization
|
|
uchar g_digit_pairs[200]; // "00","01",..."99" — 2 bytes each
|
|
|
|
// v3.7.0: Hex decode lookup table (branchless HexToDec)
|
|
uchar g_hex[256];
|
|
|
|
void InitTables() {
|
|
if(g_init)
|
|
return;
|
|
ArrayInitialize(g_cc, CC_OTHER);
|
|
g_cc[' '] = CC_WHITE;
|
|
g_cc['\t'] = CC_WHITE;
|
|
g_cc['\r'] = CC_WHITE;
|
|
g_cc['\n'] = CC_WHITE;
|
|
g_cc['{'] = CC_STRUCT;
|
|
g_cc['}'] = CC_STRUCT;
|
|
g_cc['['] = CC_STRUCT;
|
|
g_cc[']'] = CC_STRUCT;
|
|
g_cc[':'] = CC_STRUCT;
|
|
g_cc[','] = CC_STRUCT;
|
|
g_cc['"'] = CC_QUOTE;
|
|
g_cc['0'] = CC_DIGIT;
|
|
g_cc['1'] = CC_DIGIT;
|
|
g_cc['2'] = CC_DIGIT;
|
|
g_cc['3'] = CC_DIGIT;
|
|
g_cc['4'] = CC_DIGIT;
|
|
g_cc['5'] = CC_DIGIT;
|
|
g_cc['6'] = CC_DIGIT;
|
|
g_cc['7'] = CC_DIGIT;
|
|
g_cc['8'] = CC_DIGIT;
|
|
g_cc['9'] = CC_DIGIT;
|
|
g_cc['-'] = CC_DIGIT;
|
|
|
|
// v3.6.0: Initialize extended Exp10/Pow10 tables (MathPow NEVER called at
|
|
// runtime)
|
|
g_Exp10[0] = 1.0;
|
|
g_Pow10[0] = 1.0;
|
|
for(int i = 1; i < 309; i++) {
|
|
g_Pow10[i] = g_Pow10[i - 1] * 10.0;
|
|
g_Exp10[i] = 1.0 / g_Pow10[i];
|
|
}
|
|
|
|
// v3.6.0: Initialize digit-pair table for serialization
|
|
for(int i = 0; i < 100; i++) {
|
|
g_digit_pairs[i * 2] = (uchar)('0' + i / 10);
|
|
g_digit_pairs[i * 2 + 1] = (uchar)('0' + i % 10);
|
|
}
|
|
|
|
// v3.7.0: Initialize hex decode lookup table (eliminates branching)
|
|
ArrayInitialize(g_hex, 0);
|
|
for(int i = '0'; i <= '9'; i++) g_hex[i] = (uchar)(i - '0');
|
|
for(int i = 'a'; i <= 'f'; i++) g_hex[i] = (uchar)(i - 'a' + 10);
|
|
for(int i = 'A'; i <= 'F'; i++) g_hex[i] = (uchar)(i - 'A' + 10);
|
|
|
|
g_init = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonContext: The Engine |
|
|
//+------------------------------------------------------------------+
|
|
class CJsonContext {
|
|
public:
|
|
//-- Memory Arenas
|
|
long tape[]; // The AST
|
|
int tape_pos;
|
|
|
|
uchar buffer[]; // Raw Input Data
|
|
int len;
|
|
|
|
int last_error;
|
|
string error_msg;
|
|
int err_pos; // Position where error occurred
|
|
|
|
//-- Stack
|
|
int stack_node[512];
|
|
int stack_state[512];
|
|
int stack_count[512];
|
|
int sp;
|
|
|
|
//-- Accessors
|
|
int GetType(int idx) { return (int)((tape[idx] >> 56) & 0xFF); }
|
|
long GetSize(int idx) { return tape[idx] & 0xFFFFFFFF; }
|
|
int GetCount(int idx) { return (int)((tape[idx] >> 32) & 0xFFFFFF); }
|
|
bool GetBool(int idx) { return (tape[idx + 1] & 1) == 1; }
|
|
long GetInt(int idx) { return tape[idx + 1]; }
|
|
double GetDouble(int idx) {
|
|
union U {
|
|
double d;
|
|
long l;
|
|
} u;
|
|
u.l = tape[idx + 1];
|
|
return u.d;
|
|
}
|
|
string GetStr(int idx) {
|
|
if(idx < 0 || idx >= tape_pos)
|
|
return "";
|
|
long data = tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
return Unescape(p, l);
|
|
}
|
|
|
|
//-- Utils
|
|
bool Reserve(int size) {
|
|
int current = ArraySize(tape);
|
|
if(current < size) {
|
|
int geometric = current + (current >> 1); // 1.5x growth
|
|
int new_cap =
|
|
(size > geometric) ? size + 4096 : geometric; // Ensure enough space
|
|
|
|
if(ArrayResize(tape, new_cap) == -1) {
|
|
last_error = JSON_ERR_STACK_OVERFLOW;
|
|
Print("FAST_JSON CRITICAL: ArrayResize failed! Cap=", new_cap,
|
|
" Req=", size);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ulong Load64(int ptr) {
|
|
if(ptr + 7 >= len)
|
|
return 0;
|
|
return (ulong)buffer[ptr] | ((ulong)buffer[ptr + 1] << 8) |
|
|
((ulong)buffer[ptr + 2] << 16) | ((ulong)buffer[ptr + 3] << 24) |
|
|
((ulong)buffer[ptr + 4] << 32) | ((ulong)buffer[ptr + 5] << 40) |
|
|
((ulong)buffer[ptr + 6] << 48) | ((ulong)buffer[ptr + 7] << 56);
|
|
}
|
|
|
|
// v3.6.0: Branchless SkipWS — eliminates g_cc table lookup (1 less array
|
|
// access per WS char) All JSON whitespace (space=0x20, tab=0x09, CR=0x0D,
|
|
// LF=0x0A) is <= 0x20
|
|
int SkipWS(int ptr) {
|
|
while(ptr < len && buffer[ptr] <= ' ')
|
|
ptr++;
|
|
return ptr;
|
|
}
|
|
|
|
void GetErrorLocation(int ptr, int &err_line, int &err_col) {
|
|
err_line = 1;
|
|
err_col = 1;
|
|
for(int i = 0; i < ptr && i < len; i++) {
|
|
if(buffer[i] == '\n') {
|
|
err_line++;
|
|
err_col = 1;
|
|
} else
|
|
err_col++;
|
|
}
|
|
}
|
|
|
|
int ScanString(int ptr) {
|
|
int start = ptr;
|
|
int swar_limit = len - 8;
|
|
|
|
// Optimized SWAR loop: bounds checked before entry, no condition mixing
|
|
while(ptr <= swar_limit) {
|
|
ulong word = Load64(ptr);
|
|
ulong z_q = ((word ^ SWAR_QUOTE) - SWAR_LO) & ~(word ^ SWAR_QUOTE) & SWAR_HI;
|
|
ulong z_s = ((word ^ SWAR_SLASH) - SWAR_LO) & ~(word ^ SWAR_SLASH) & SWAR_HI;
|
|
if((z_q | z_s) != 0)
|
|
break;
|
|
ptr += 8;
|
|
}
|
|
|
|
// Clean up remaining bytes
|
|
while(ptr < len) {
|
|
uchar c = buffer[ptr];
|
|
if(c == '"')
|
|
return ptr - start;
|
|
if(c == '\\') {
|
|
ptr += 2;
|
|
continue;
|
|
}
|
|
ptr++;
|
|
}
|
|
return ptr - start;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| v3.6.0: Decomposed Number Parser (fxsaber architecture) |
|
|
//| + XOR-first pattern + SWAR 8-digit fast path (Lemire) |
|
|
//| + Extended Pow10 tables (zero MathPow calls) |
|
|
//| Every digit is read EXACTLY ONCE. Zero re-scanning. |
|
|
//+------------------------------------------------------------------+
|
|
|
|
// GetInteger: Single-pass integer accumulator
|
|
// Receives c already XOR'd with '0' (digits are 0-9)
|
|
// Returns stop-char in c (also XOR'd) for caller inspection
|
|
bool GetInteger(long &Value, int &cur, uchar &c) {
|
|
// Positive path (c is already a digit 0-9, XOR'd)
|
|
if(c != ('-' ^ '0')) { // c != 0x1D
|
|
Value = c; // c is already 0-9
|
|
|
|
while(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
if(c <= 9)
|
|
Value = Value * 10 + c;
|
|
else
|
|
break;
|
|
}
|
|
return (Value >= 0); // overflow guard
|
|
}
|
|
// Negative path
|
|
else if(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
if(c <= 9) {
|
|
Value = -(long)c;
|
|
|
|
while(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
if(c <= 9)
|
|
Value = Value * 10 - c;
|
|
else
|
|
break;
|
|
}
|
|
return (Value <= 0); // overflow guard
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// SetExp: Handle exponent (e/E) using GetInteger + extended Pow10 tables
|
|
// Receives val = mantissa so far, cur = position of 'e'/'E'
|
|
bool SetExp(double val, int &cur, uchar &c) {
|
|
if((++cur < len) &&
|
|
((((c = buffer[cur]) ^ '0') <= 9) || (c == '-') ||
|
|
((c == '+') && (++cur < len) && (((c = buffer[cur]) ^ '0') <= 9)))) {
|
|
long exp_val;
|
|
c ^= '0'; // XOR-shift for GetInteger
|
|
|
|
if(this.GetInteger(exp_val, cur, c)) {
|
|
bool positive = (exp_val >= 0);
|
|
if(!positive)
|
|
exp_val = -exp_val;
|
|
|
|
// v3.6.0: Extended tables cover 0-308 (NEVER calls MathPow)
|
|
if(exp_val <= 308) {
|
|
if(positive)
|
|
val *= g_Pow10[(int)exp_val];
|
|
else
|
|
val *= g_Exp10[(int)exp_val];
|
|
|
|
if(MathAbs(val) <= DBL_MAX && MathAbs(val) > 0.0) {
|
|
tape[tape_pos++] = ((long)J_DBL << 56) | 2;
|
|
tape[tape_pos++] = DBL2LONG(val);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ParseNumber: Orchestrator (int → frac → exp), zero re-scanning
|
|
// c is passed from the Parse loop (already read from buffer[cur])
|
|
bool ParseNumber(int &cur, uchar &c) {
|
|
int start = cur; // Track the absolute start of the number string
|
|
long int_val;
|
|
c ^= '0';
|
|
|
|
if(this.GetInteger(int_val, cur, c)) {
|
|
if(c == ('.' ^ '0')) {
|
|
if(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
if(c <= 9) {
|
|
long frac_int;
|
|
this.GetInteger(frac_int, cur, c);
|
|
|
|
// Check for exponent: 'e'^'0'==0x55, 'E'^'0'==0x75
|
|
if(c == ('e' ^ '0') || c == ('E' ^ '0')) {
|
|
long exp_val;
|
|
if(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
this.GetInteger(exp_val, cur, c);
|
|
}
|
|
}
|
|
|
|
// Found a double! Record its buffer coordinates instead of converting
|
|
int l = cur - start;
|
|
tape[tape_pos++] = ((long)J_DBL << 56) | 2;
|
|
tape[tape_pos++] = ((long)start << 32) | (long)l;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if(c == ('e' ^ '0') || c == ('E' ^ '0')) {
|
|
long exp_val;
|
|
if(++cur < len) {
|
|
c = buffer[cur] ^ '0';
|
|
this.GetInteger(exp_val, cur, c);
|
|
}
|
|
int l = cur - start;
|
|
tape[tape_pos++] = ((long)J_DBL << 56) | 2;
|
|
tape[tape_pos++] = ((long)start << 32) | (long)l;
|
|
return true;
|
|
}
|
|
else {
|
|
// Pure integer
|
|
tape[tape_pos++] = ((long)J_INT << 56) | 2;
|
|
tape[tape_pos++] = int_val;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// v3.7.0: SWAR-accelerated Unescape — scans 8 bytes at a time for backslash
|
|
string Unescape(int ptr, int str_len) {
|
|
// Fast path: SWAR scan for backslash in 8-byte chunks
|
|
int i = 0;
|
|
while(i + 8 <= str_len) {
|
|
ulong word = Load64(ptr + i);
|
|
ulong x_s = word ^ SWAR_SLASH;
|
|
ulong z_s = (x_s - SWAR_LO) & ~x_s & SWAR_HI;
|
|
if(z_s != 0) break;
|
|
i += 8;
|
|
}
|
|
for(; i < str_len; i++) {
|
|
if(buffer[ptr + i] == '\\') break;
|
|
}
|
|
if(i >= str_len)
|
|
return CharArrayToString(buffer, ptr, str_len, CP_UTF8);
|
|
|
|
// Slow path: string contains escape sequences
|
|
ushort res[];
|
|
ArrayResize(res, str_len);
|
|
int pos = 0;
|
|
for(i = 0; i < str_len; i++) {
|
|
uchar c = buffer[ptr + i];
|
|
if(c == '\\' && i + 1 < str_len) {
|
|
i++;
|
|
uchar next = buffer[ptr + i];
|
|
switch (next) {
|
|
case '"':
|
|
res[pos++] = '"';
|
|
break;
|
|
case '\\':
|
|
res[pos++] = '\\';
|
|
break;
|
|
case '/':
|
|
res[pos++] = '/';
|
|
break;
|
|
case 'b':
|
|
res[pos++] = '\x08';
|
|
break;
|
|
case 'f':
|
|
res[pos++] = '\f';
|
|
break;
|
|
case 'n':
|
|
res[pos++] = '\n';
|
|
break;
|
|
case 'r':
|
|
res[pos++] = '\r';
|
|
break;
|
|
case 't':
|
|
res[pos++] = '\t';
|
|
break;
|
|
case 'u': {
|
|
if(i + 4 < str_len) {
|
|
// v3.7.0: Use g_hex lookup table (branchless)
|
|
int code = ((int)g_hex[buffer[ptr + i + 1]] << 12) |
|
|
((int)g_hex[buffer[ptr + i + 2]] << 8) |
|
|
((int)g_hex[buffer[ptr + i + 3]] << 4) |
|
|
(int)g_hex[buffer[ptr + i + 4]];
|
|
res[pos++] = (ushort)code;
|
|
i += 4;
|
|
} else
|
|
res[pos++] = 'u';
|
|
break;
|
|
}
|
|
default:
|
|
res[pos++] = next;
|
|
break;
|
|
}
|
|
} else
|
|
res[pos++] = c;
|
|
}
|
|
return ShortArrayToString(res, 0, pos);
|
|
}
|
|
|
|
long DBL2LONG(double d) {
|
|
union U {
|
|
double d;
|
|
long l;
|
|
} u;
|
|
u.d = d;
|
|
return u.l;
|
|
}
|
|
|
|
// v3.7.0: Branchless hex decode via lookup table
|
|
int HexToDec(uchar c) { return (int)g_hex[c]; }
|
|
|
|
public:
|
|
// v3.7.0: Parse from string (converts to buffer, then calls _DoParse)
|
|
bool Parse(string json_str) {
|
|
if(!g_init)
|
|
InitTables();
|
|
tape_pos = 0;
|
|
sp = 0;
|
|
last_error = JSON_OK;
|
|
|
|
int str_len = StringLen(json_str);
|
|
// v3.7.0: ArrayResize with 3rd param (reserve) to avoid physical reallocation
|
|
if(ArraySize(buffer) < str_len + 8)
|
|
ArrayResize(buffer, str_len + 8, str_len + 4096);
|
|
int wr = StringToCharArray(json_str, buffer, 0, WHOLE_ARRAY, CP_UTF8);
|
|
len = (wr > 0 && buffer[wr - 1] == 0) ? wr - 1 : wr;
|
|
|
|
return _DoParse();
|
|
}
|
|
|
|
// v3.7.0: Parse directly from uchar buffer — zero StringToCharArray overhead
|
|
// Ideal for WebSocket/WebRequest data that arrives as uchar[]
|
|
bool ParseBuffer(uchar &data[], int data_len) {
|
|
if(!g_init)
|
|
InitTables();
|
|
tape_pos = 0;
|
|
sp = 0;
|
|
last_error = JSON_OK;
|
|
|
|
// Copy to internal buffer (safe: caller can modify source after parse)
|
|
if(ArraySize(buffer) < data_len + 8)
|
|
ArrayResize(buffer, data_len + 8, data_len + 4096);
|
|
ArrayCopy(buffer, data, 0, 0, data_len);
|
|
len = data_len;
|
|
// Strip null terminator if present
|
|
if(len > 0 && buffer[len - 1] == 0) len--;
|
|
|
|
return _DoParse();
|
|
}
|
|
|
|
private:
|
|
// v3.7.0: Core parse engine (shared by Parse and ParseBuffer)
|
|
bool _DoParse() {
|
|
if(!Reserve(len * 2 + 1024))
|
|
return false;
|
|
|
|
int cur = 0;
|
|
stack_state[sp] = ST_VAL;
|
|
stack_node[sp] = -1;
|
|
stack_count[sp] = 0;
|
|
sp++;
|
|
int key_ptr = 0;
|
|
int key_len = 0;
|
|
uint key_hash = 0;
|
|
|
|
while(sp > 0) {
|
|
// SAFETY: Ensure tape capacity (auto-grow)
|
|
if(!Reserve(tape_pos + 32)) {
|
|
return false;
|
|
}
|
|
|
|
cur = SkipWS(cur);
|
|
if(cur >= len)
|
|
break;
|
|
|
|
uchar c = buffer[cur];
|
|
int state = stack_state[sp - 1];
|
|
|
|
if(state == ST_VAL) {
|
|
if(sp > 1)
|
|
stack_count[sp - 2]++;
|
|
|
|
// Emit Key if exists
|
|
// v3.7.0: Key tape stores (key_ptr << 32) | (key_len << 24) | (hash & 0xFFFFFF)
|
|
if(key_len > 0) {
|
|
tape[tape_pos++] = ((long)J_KEY << 56);
|
|
tape[tape_pos++] = ((long)key_ptr << 32) | ((long)(key_len & 0xFF) << 24) | (long)(key_hash & 0xFFFFFF);
|
|
key_len = 0;
|
|
}
|
|
|
|
// OPTIMIZATION: Branch Prediction Order (Strings -> Numbers -> Structs)
|
|
if(c == '"') {
|
|
cur++;
|
|
int start = cur;
|
|
int l = ScanString(cur);
|
|
cur += l + 1;
|
|
tape[tape_pos++] = ((long)J_STR << 56) | 2;
|
|
tape[tape_pos++] = ((long)start << 32) | (long)l;
|
|
sp--;
|
|
} else if((c ^ '0') <= 9 || c == '-') { // Bitwise IsDigit (fxsaber)
|
|
if(!ParseNumber(cur, c))
|
|
return false;
|
|
sp--;
|
|
} else if(c == '{') {
|
|
// SAFETY: Stack Overflow Protection
|
|
if(sp >= 512) {
|
|
last_error = JSON_ERR_STACK_OVERFLOW;
|
|
err_pos = cur;
|
|
return false;
|
|
}
|
|
int idx = tape_pos++;
|
|
stack_state[sp - 1] = ST_OBJ;
|
|
stack_node[sp - 1] = idx;
|
|
stack_count[sp - 1] = 0;
|
|
cur++;
|
|
} else if(c == '[') {
|
|
// SAFETY: Stack Overflow Protection
|
|
if(sp >= 512) {
|
|
last_error = JSON_ERR_STACK_OVERFLOW;
|
|
err_pos = cur;
|
|
return false;
|
|
}
|
|
int idx = tape_pos++;
|
|
stack_state[sp - 1] = ST_ARR;
|
|
stack_node[sp - 1] = idx;
|
|
stack_count[sp - 1] = 0;
|
|
cur++;
|
|
} else if(c == 't') {
|
|
cur += 4;
|
|
tape[tape_pos++] = ((long)J_BOOL << 56) | 2; // Fixed: Use 2 slots
|
|
tape[tape_pos++] = 1; // Fixed: Write value slot
|
|
sp--;
|
|
} else if(c == 'f') {
|
|
cur += 5;
|
|
tape[tape_pos++] = ((long)J_BOOL << 56) | 2; // Fixed: Use 2 slots
|
|
tape[tape_pos++] = 0; // Fixed: Write value slot
|
|
sp--;
|
|
} else if(c == 'n') {
|
|
cur += 4;
|
|
tape[tape_pos++] = ((long)J_NULL << 56) | 2; // Fixed: Use 2 slots
|
|
tape[tape_pos++] = 0ULL; // Fixed: Write value slot
|
|
sp--;
|
|
} else {
|
|
last_error = JSON_ERR_INVALID_CHAR;
|
|
err_pos = cur;
|
|
return false;
|
|
}
|
|
} else if(state == ST_OBJ) {
|
|
if(c == '}') {
|
|
int idx = stack_node[sp - 1];
|
|
long size = tape_pos - idx;
|
|
int count = stack_count[sp - 1];
|
|
tape[idx] = ((long)J_OBJ << 56) | ((long)(count & 0xFFFFFF) << 32) |
|
|
(size & 0xFFFFFFFF);
|
|
cur++;
|
|
sp--;
|
|
} else {
|
|
if(c == ',') {
|
|
cur++;
|
|
continue;
|
|
}
|
|
if(c != '"') {
|
|
last_error = JSON_ERR_EXPECTED_KEY;
|
|
err_pos = cur;
|
|
return false;
|
|
}
|
|
cur++;
|
|
key_ptr = cur;
|
|
key_len = ScanString(cur);
|
|
cur += key_len + 1;
|
|
|
|
// v3.6.0: FNV-1a hash unrolled 4x for CPU pipeline efficiency
|
|
key_hash = 2166136261;
|
|
int ki = 0;
|
|
for(; ki + 4 <= key_len; ki += 4) {
|
|
key_hash = (key_hash ^ buffer[key_ptr + ki]) * 16777619;
|
|
key_hash = (key_hash ^ buffer[key_ptr + ki + 1]) * 16777619;
|
|
key_hash = (key_hash ^ buffer[key_ptr + ki + 2]) * 16777619;
|
|
key_hash = (key_hash ^ buffer[key_ptr + ki + 3]) * 16777619;
|
|
}
|
|
for(; ki < key_len; ki++)
|
|
key_hash = (key_hash ^ buffer[key_ptr + ki]) * 16777619;
|
|
|
|
cur = SkipWS(cur);
|
|
if(buffer[cur] != ':') {
|
|
last_error = JSON_ERR_EXPECTED_COLON;
|
|
err_pos = cur;
|
|
return false;
|
|
}
|
|
cur++;
|
|
stack_state[sp] = ST_VAL;
|
|
stack_node[sp] = -1;
|
|
sp++;
|
|
}
|
|
} else if(state == ST_ARR) {
|
|
if(c == ']') {
|
|
int idx = stack_node[sp - 1];
|
|
long size = tape_pos - idx;
|
|
int count = stack_count[sp - 1];
|
|
tape[idx] = ((long)J_ARR << 56) | ((long)(count & 0xFFFFFF) << 32) |
|
|
(size & 0xFFFFFFFF);
|
|
cur++;
|
|
sp--;
|
|
} else {
|
|
if(c == ',') {
|
|
cur++;
|
|
continue;
|
|
}
|
|
stack_state[sp] = ST_VAL;
|
|
stack_node[sp] = -1;
|
|
sp++;
|
|
}
|
|
}
|
|
}
|
|
return (last_error == JSON_OK);
|
|
}
|
|
|
|
public:
|
|
|
|
//-- Serialization Writer
|
|
string Serialize(bool pretty) {
|
|
if(tape_pos == 0)
|
|
return "";
|
|
|
|
uchar out[];
|
|
int cap = len * 2;
|
|
if(cap < 1024)
|
|
cap = 1024;
|
|
ArrayResize(out, cap);
|
|
int pos = 0;
|
|
|
|
if(pretty)
|
|
WriteNodePretty(0, out, pos, cap, 0);
|
|
else
|
|
WriteNode(0, out, pos, cap);
|
|
|
|
return CharArrayToString(out, 0, pos);
|
|
}
|
|
|
|
void WriteNode(int idx, uchar &out[], int &pos, int &cap) {
|
|
if(idx >= tape_pos)
|
|
return;
|
|
int type = GetType(idx);
|
|
|
|
switch (type) {
|
|
case J_NULL:
|
|
// v3.7.0: Direct byte writes (no StringToCharArray overhead)
|
|
CheckCap(4, pos, cap, out);
|
|
out[pos++] = 'n'; out[pos++] = 'u'; out[pos++] = 'l'; out[pos++] = 'l';
|
|
break;
|
|
case J_BOOL:
|
|
if(GetBool(idx)) {
|
|
CheckCap(4, pos, cap, out);
|
|
out[pos++] = 't'; out[pos++] = 'r'; out[pos++] = 'u'; out[pos++] = 'e';
|
|
} else {
|
|
CheckCap(5, pos, cap, out);
|
|
out[pos++] = 'f'; out[pos++] = 'a'; out[pos++] = 'l'; out[pos++] = 's'; out[pos++] = 'e';
|
|
}
|
|
break;
|
|
case J_INT:
|
|
PutRawInteger(GetInt(idx), out, pos, cap);
|
|
break;
|
|
case J_DBL:
|
|
PutRawDouble(idx, out, pos, cap); // Pass idx instead of GetDouble(idx)
|
|
break;
|
|
case J_STR: {
|
|
long data = tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
CheckCap(l + 2, pos, cap, out); // v3.7.0: batched CheckCap for quotes + content
|
|
out[pos++] = '"';
|
|
ArrayCopy(out, buffer, pos, p, l);
|
|
pos += l;
|
|
out[pos++] = '"';
|
|
break;
|
|
}
|
|
case J_ARR: {
|
|
PutChar('[', out, pos, cap);
|
|
int count = GetCount(idx);
|
|
int cur = idx + 1;
|
|
for(int i = 0; i < count; i++) {
|
|
if(i > 0)
|
|
PutChar(',', out, pos, cap);
|
|
WriteNode(cur, out, pos, cap);
|
|
cur += MACRO_GET_STEP(cur);
|
|
}
|
|
PutChar(']', out, pos, cap);
|
|
break;
|
|
}
|
|
case J_OBJ: {
|
|
PutChar('{', out, pos, cap);
|
|
int count = GetCount(idx);
|
|
int cur = idx + 1;
|
|
int emitted = 0;
|
|
while(emitted < count) {
|
|
int t = GetType(cur);
|
|
if(t == J_KEY) {
|
|
if(emitted > 0)
|
|
PutChar(',', out, pos, cap);
|
|
|
|
// v3.7.0: Extract key length from packed tape entry (no scan-for-quote)
|
|
long kp = tape[cur + 1];
|
|
int kptr = (int)(kp >> 32);
|
|
int klen = (int)((kp >> 24) & 0xFF);
|
|
|
|
CheckCap(klen + 3, pos, cap, out); // quotes + colon + key
|
|
out[pos++] = '"';
|
|
ArrayCopy(out, buffer, pos, kptr, klen);
|
|
pos += klen;
|
|
out[pos++] = '"';
|
|
out[pos++] = ':';
|
|
|
|
int val_idx = cur + 2;
|
|
WriteNode(val_idx, out, pos, cap);
|
|
|
|
cur = val_idx + GetStep(val_idx);
|
|
emitted++;
|
|
} else {
|
|
cur++;
|
|
}
|
|
}
|
|
PutChar('}', out, pos, cap);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void WriteNodePretty(int idx, uchar &out[], int &pos, int &cap, int depth) {
|
|
if(idx >= tape_pos)
|
|
return;
|
|
int type = GetType(idx);
|
|
|
|
switch (type) {
|
|
case J_NULL:
|
|
CheckCap(4, pos, cap, out);
|
|
out[pos++] = 'n'; out[pos++] = 'u'; out[pos++] = 'l'; out[pos++] = 'l';
|
|
break;
|
|
case J_BOOL:
|
|
if(GetBool(idx)) {
|
|
CheckCap(4, pos, cap, out);
|
|
out[pos++] = 't'; out[pos++] = 'r'; out[pos++] = 'u'; out[pos++] = 'e';
|
|
} else {
|
|
CheckCap(5, pos, cap, out);
|
|
out[pos++] = 'f'; out[pos++] = 'a'; out[pos++] = 'l'; out[pos++] = 's'; out[pos++] = 'e';
|
|
}
|
|
break;
|
|
case J_INT:
|
|
PutRawInteger(GetInt(idx), out, pos, cap);
|
|
break;
|
|
case J_DBL:
|
|
PutRawDouble(idx, out, pos, cap); // Pass idx instead of GetDouble(idx)
|
|
break;
|
|
case J_STR: {
|
|
long data = tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
CheckCap(l + 2, pos, cap, out);
|
|
out[pos++] = '"';
|
|
ArrayCopy(out, buffer, pos, p, l);
|
|
pos += l;
|
|
out[pos++] = '"';
|
|
break;
|
|
}
|
|
case J_ARR: {
|
|
int count = GetCount(idx);
|
|
if(count == 0) {
|
|
CheckCap(2, pos, cap, out);
|
|
out[pos++] = '['; out[pos++] = ']';
|
|
return;
|
|
}
|
|
|
|
PutChar('[', out, pos, cap);
|
|
PutChar('\n', out, pos, cap);
|
|
|
|
int cur = idx + 1;
|
|
for(int i = 0; i < count; i++) {
|
|
if(i > 0) {
|
|
PutChar(',', out, pos, cap);
|
|
PutChar('\n', out, pos, cap);
|
|
}
|
|
Indent(depth + 1, out, pos, cap);
|
|
WriteNodePretty(cur, out, pos, cap, depth + 1);
|
|
cur += MACRO_GET_STEP(cur);
|
|
}
|
|
PutChar('\n', out, pos, cap);
|
|
Indent(depth, out, pos, cap);
|
|
PutChar(']', out, pos, cap);
|
|
break;
|
|
}
|
|
case J_OBJ: {
|
|
int count = GetCount(idx);
|
|
if(count == 0) {
|
|
CheckCap(2, pos, cap, out);
|
|
out[pos++] = '{'; out[pos++] = '}';
|
|
return;
|
|
}
|
|
|
|
PutChar('{', out, pos, cap);
|
|
PutChar('\n', out, pos, cap);
|
|
|
|
int cur = idx + 1;
|
|
int emitted = 0;
|
|
while(emitted < count) {
|
|
int t = GetType(cur);
|
|
if(t == J_KEY) {
|
|
if(emitted > 0) {
|
|
PutChar(',', out, pos, cap);
|
|
PutChar('\n', out, pos, cap);
|
|
}
|
|
Indent(depth + 1, out, pos, cap);
|
|
|
|
// v3.7.0: Key length from packed tape (no scan-for-quote)
|
|
long kp = tape[cur + 1];
|
|
int kptr = (int)(kp >> 32);
|
|
int klen = (int)((kp >> 24) & 0xFF);
|
|
|
|
CheckCap(klen + 4, pos, cap, out); // quotes + ": "
|
|
out[pos++] = '"';
|
|
ArrayCopy(out, buffer, pos, kptr, klen);
|
|
pos += klen;
|
|
out[pos++] = '"';
|
|
out[pos++] = ':';
|
|
out[pos++] = ' ';
|
|
|
|
int val_idx = cur + 2;
|
|
WriteNodePretty(val_idx, out, pos, cap, depth + 1);
|
|
|
|
cur = val_idx + GetStep(val_idx);
|
|
emitted++;
|
|
} else {
|
|
cur++;
|
|
}
|
|
}
|
|
PutChar('\n', out, pos, cap);
|
|
Indent(depth, out, pos, cap);
|
|
PutChar('}', out, pos, cap);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Indent(int depth, uchar &out[], int &pos, int &cap) {
|
|
CheckCap(depth * 3, pos, cap, out); // 3 spaces per indent
|
|
for(int i = 0; i < depth; i++) {
|
|
out[pos++] = ' ';
|
|
out[pos++] = ' ';
|
|
out[pos++] = ' ';
|
|
}
|
|
}
|
|
|
|
void PutChar(uchar c, uchar &out[], int &pos, int &cap) {
|
|
if(pos >= cap) {
|
|
cap = (int)(cap * 1.5) + 32;
|
|
ArrayResize(out, cap);
|
|
}
|
|
out[pos++] = c;
|
|
}
|
|
|
|
void PutRaw(string s, uchar &out[], int &pos, int &cap) {
|
|
int l = StringLen(s);
|
|
int max_bytes = l * 4 + 1; // UTF-8 pode levar até 4 bytes por char
|
|
CheckCap(max_bytes, pos, cap, out);
|
|
|
|
int written = StringToCharArray(s, out, pos, WHOLE_ARRAY, CP_UTF8);
|
|
if(written > 0 && out[pos + written - 1] == 0) {
|
|
written--; // remove null terminator
|
|
}
|
|
pos += written;
|
|
}
|
|
|
|
// v3.6.0: Digit-pair integer serialization (50% fewer divisions)
|
|
void PutRawInteger(long value, uchar &out[], int &pos, int &cap) {
|
|
if(value == 0) {
|
|
PutChar('0', out, pos, cap);
|
|
return;
|
|
}
|
|
if(value < 0) {
|
|
PutChar('-', out, pos, cap);
|
|
value = -value;
|
|
}
|
|
uchar digits[20];
|
|
int n = 0;
|
|
// Process 2 digits at a time using digit-pair table
|
|
while(value >= 100) {
|
|
int idx = (int)(value % 100) * 2;
|
|
digits[n++] = g_digit_pairs[idx + 1];
|
|
digits[n++] = g_digit_pairs[idx];
|
|
value /= 100;
|
|
}
|
|
// Handle remaining 1-2 digits
|
|
if(value >= 10) {
|
|
int idx = (int)value * 2;
|
|
digits[n++] = g_digit_pairs[idx + 1];
|
|
digits[n++] = g_digit_pairs[idx];
|
|
} else {
|
|
digits[n++] = (uchar)('0' + (int)value);
|
|
}
|
|
CheckCap(n, pos, cap, out);
|
|
for(int j = n - 1; j >= 0; j--)
|
|
out[pos++] = digits[j];
|
|
}
|
|
|
|
// v3.4.0: Zero-alloc double serialization (writes directly to buffer)
|
|
void PutRawDouble(int idx, uchar &out[], int &pos, int &cap) {
|
|
long data = tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
|
|
// Check if the number contains a decimal point and no exponent (e/E)
|
|
// We only want to trim trailing zeros for standard decimals (e.g., 2.340)
|
|
bool has_dot = false;
|
|
bool has_exp = false;
|
|
|
|
for(int i = 0; i < l; i++) {
|
|
uchar c = buffer[p + i];
|
|
if(c == '.') has_dot = true;
|
|
if(c == 'e' || c == 'E') { has_exp = true; break; }
|
|
}
|
|
|
|
// Trim trailing zeros if it's a standard decimal without scientific notation
|
|
if(has_dot && !has_exp) {
|
|
while(l > 0 && buffer[p + l - 1] == '0') {
|
|
l--;
|
|
}
|
|
// If removing zeros leaves us with a trailing decimal point (e.g., "2."), remove it too
|
|
if(l > 0 && buffer[p + l - 1] == '.') {
|
|
l--;
|
|
}
|
|
}
|
|
|
|
// Fast block memory copy of the exact trimmed string slice
|
|
if(l > 0) {
|
|
CheckCap(l, pos, cap, out);
|
|
ArrayCopy(out, buffer, pos, p, l);
|
|
pos += l;
|
|
} else {
|
|
// Fallback fallback just in case it becomes empty
|
|
CheckCap(1, pos, cap, out);
|
|
out[pos++] = '0';
|
|
}
|
|
}
|
|
|
|
void CheckCap(int req, int pos, int &cap, uchar &out[]) {
|
|
if(pos + req >= cap) {
|
|
cap = cap + req + 4096;
|
|
ArrayResize(out, cap);
|
|
}
|
|
}
|
|
|
|
// Branchless GetStep
|
|
int GetStep(int idx) { return (int)(tape[idx] & 0xFFFFFFFF); }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonIterator: Fast Traversal |
|
|
//+------------------------------------------------------------------+
|
|
struct CJsonNode; // Forward Declaration
|
|
|
|
struct CJsonIterator {
|
|
CJsonContext *ctx;
|
|
int cur_idx;
|
|
int end_idx;
|
|
int steps_taken;
|
|
int total_count;
|
|
|
|
bool IsValid() { return (cur_idx < end_idx && steps_taken < total_count); }
|
|
|
|
void Next() {
|
|
if(!IsValid())
|
|
return;
|
|
|
|
int type = ctx.GetType(cur_idx);
|
|
if(type == J_KEY) {
|
|
// Key Node (2 slots) + Value Node (variable)
|
|
// Value Node starts at cur_idx + 2
|
|
int val_idx = cur_idx + 2;
|
|
// Step = 2 + Step of Value
|
|
cur_idx += 2 + ctx.GetStep(val_idx);
|
|
} else {
|
|
// Array Element or Value
|
|
cur_idx += ctx.GetStep(cur_idx);
|
|
}
|
|
steps_taken++;
|
|
}
|
|
|
|
// Forward defined methods
|
|
CJsonNode Val();
|
|
string Key();
|
|
|
|
// Direct Access (Unsafe/Fast)
|
|
double ValueDouble() {
|
|
if(!IsValid())
|
|
return double("nan");
|
|
int target = cur_idx;
|
|
if(((ctx.tape[cur_idx] >> 56) & 0xFF) == J_KEY)
|
|
target = cur_idx + 2;
|
|
|
|
union U {
|
|
double d;
|
|
long l;
|
|
} u;
|
|
u.l = ctx.tape[target + 1];
|
|
return u.d;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonFastDoubleIterator (For double[] only) |
|
|
//+------------------------------------------------------------------+
|
|
struct CJsonFastDoubleIterator {
|
|
CJsonContext *ctx;
|
|
int cur_idx;
|
|
int end_idx;
|
|
|
|
// Minimal check: assumes structure IS double array
|
|
bool IsValid() { return cur_idx < end_idx; }
|
|
|
|
void Next() { cur_idx += 2; } // Fixed stride for doubles
|
|
|
|
double Val() {
|
|
union U {
|
|
double d;
|
|
long l;
|
|
} u;
|
|
u.l = ctx.tape[cur_idx + 1];
|
|
return u.d;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonNode: The Handle |
|
|
//+------------------------------------------------------------------+
|
|
struct CJsonNode {
|
|
CJsonContext *ctx; // Pointer to Engine
|
|
int idx; // Index on Tape
|
|
|
|
//-- Fast Iteration
|
|
CJsonFastDoubleIterator begin_fast_double() {
|
|
CJsonFastDoubleIterator it;
|
|
it.ctx = ctx;
|
|
if(!ctx || idx == -1 || ((ctx.tape[idx] >> 56) & 0xFF) != J_ARR) {
|
|
it.cur_idx = 0;
|
|
it.end_idx = 0;
|
|
return it;
|
|
}
|
|
|
|
long head = ctx.tape[idx];
|
|
it.cur_idx = idx + 1;
|
|
it.end_idx = idx + (int)(head & 0xFFFFFFFF);
|
|
return it;
|
|
}
|
|
|
|
//-- Navigation (Unsafe / Fast Scan)
|
|
// v3.7.0: OPT-5 — Convert key to local byte buffer ONCE (eliminates per-char StringGetCharacter)
|
|
CJsonNode operator[](string key) {
|
|
if(!ctx || idx == -1)
|
|
return GetNull();
|
|
|
|
int klen = StringLen(key);
|
|
if(klen == 0) return GetNull();
|
|
|
|
// Maximize performance: use a fixed stack buffer up to 127 chars to avoid memory manager allocations
|
|
uchar kbuf[128];
|
|
int max_bytes = klen < 127 ? klen + 1 : 128;
|
|
int kbytes = StringToCharArray(key, kbuf, 0, max_bytes, CP_UTF8);
|
|
if(kbytes > 0 && kbuf[kbytes - 1] == 0) kbytes--;
|
|
|
|
// FNV-1a Hash Accumulation
|
|
uint h = 2166136261;
|
|
int ki = 0;
|
|
for(; ki + 4 <= kbytes; ki += 4) {
|
|
h = (h ^ kbuf[ki]) * 16777619;
|
|
h = (h ^ kbuf[ki + 1]) * 16777619;
|
|
h = (h ^ kbuf[ki + 2]) * 16777619;
|
|
h = (h ^ kbuf[ki + 3]) * 16777619;
|
|
}
|
|
for(; ki < kbytes; ki++)
|
|
h = (h ^ kbuf[ki]) * 16777619;
|
|
|
|
long head = ctx.tape[idx];
|
|
if(((head >> 56) & 0xFF) != J_OBJ)
|
|
return GetNull();
|
|
|
|
int end = idx + (int)(head & 0xFFFFFFFF);
|
|
int cur = idx + 1;
|
|
|
|
uint match_packed = ((uint)(kbytes & 0xFF) << 24) | (h & 0xFFFFFF);
|
|
|
|
while(cur < end) {
|
|
if((uint)(ctx.tape[cur + 1] & 0xFFFFFFFF) == match_packed) {
|
|
CJsonNode node;
|
|
node.ctx = ctx;
|
|
node.idx = cur + 2;
|
|
return node;
|
|
}
|
|
cur += 2 + (int)(ctx.tape[cur + 2] & 0xFFFFFFFF);
|
|
}
|
|
return GetNull();
|
|
}
|
|
|
|
CJsonNode operator[](int index) {
|
|
if(!ctx || idx == -1 || ctx.GetType(idx) != J_ARR)
|
|
return GetNull();
|
|
int count = ctx.GetCount(idx);
|
|
if(index < 0 || index >= count)
|
|
return GetNull();
|
|
|
|
int cur = idx + 1;
|
|
for(int i = 0; i < index; i++)
|
|
cur += ctx.GetStep(cur);
|
|
|
|
CJsonNode node;
|
|
node.ctx = ctx;
|
|
node.idx = cur;
|
|
return node;
|
|
}
|
|
|
|
//-- Iteration
|
|
CJsonIterator begin() {
|
|
CJsonIterator it;
|
|
it.ctx = ctx;
|
|
if(!IsValid() ||
|
|
(ctx.GetType(idx) != J_OBJ && ctx.GetType(idx) != J_ARR)) {
|
|
it.cur_idx = 0;
|
|
it.end_idx = 0;
|
|
return it;
|
|
}
|
|
|
|
long head = ctx.tape[idx];
|
|
long size = head & 0xFFFFFFFF;
|
|
|
|
it.cur_idx = idx + 1;
|
|
it.end_idx = idx + (int)size;
|
|
it.total_count = ctx.GetCount(idx);
|
|
it.steps_taken = 0;
|
|
return it;
|
|
}
|
|
|
|
//-- Validation
|
|
bool IsValid() { return (ctx != NULL && idx != -1); }
|
|
bool IsNull() { return (!IsValid() || ctx.GetType(idx) == J_NULL); }
|
|
bool IsString() { return (IsValid() && ctx.GetType(idx) == J_STR); }
|
|
bool IsNumber() { return (IsValid() && (ctx.GetType(idx) == J_INT || ctx.GetType(idx) == J_DBL)); }
|
|
bool IsArray() { return (IsValid() && ctx.GetType(idx) == J_ARR); }
|
|
bool IsObject() { return (IsValid() && ctx.GetType(idx) == J_OBJ); }
|
|
|
|
//-- Extractors
|
|
// v3.7.0: FIX Bug #2 — type-safe ToString (no longer reads adjacent tape slots)
|
|
string ToString() {
|
|
if(!IsValid()) return "";
|
|
switch(ctx.GetType(idx)) {
|
|
case J_STR: return ctx.GetStr(idx);
|
|
case J_BOOL: return ctx.GetBool(idx) ? "true" : "false";
|
|
case J_INT: return IntegerToString(ctx.GetInt(idx));
|
|
case J_DBL: return DoubleToString(ctx.GetDouble(idx), 8);
|
|
case J_NULL: return "null";
|
|
default: return "";
|
|
}
|
|
}
|
|
long ToInt() {
|
|
if(!IsValid())
|
|
return 0;
|
|
int t = ctx.GetType(idx);
|
|
if(t == J_INT)
|
|
return ctx.GetInt(idx);
|
|
if(t == J_DBL)
|
|
return (long)ctx.GetDouble(idx);
|
|
return 0;
|
|
}
|
|
double ToDouble() {
|
|
if(!IsValid())
|
|
return double("nan");
|
|
int t = ctx.GetType(idx);
|
|
if(t == J_DBL) {
|
|
long data = ctx.tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
return StringToDouble(CharArrayToString(ctx.buffer, p, l, CP_UTF8));
|
|
}
|
|
if(t == J_INT)
|
|
return (double)ctx.GetInt(idx);
|
|
return double("nan");
|
|
}
|
|
|
|
//-- Safe Extractors (Default Values)
|
|
// v3.7.0: ToString(def) is strict — returns def for non-string types
|
|
string ToString(string def) {
|
|
if(!IsValid()) return def;
|
|
if(ctx.GetType(idx) == J_STR) return ctx.GetStr(idx);
|
|
return def;
|
|
}
|
|
long ToInt(long def) { return (IsValid() && IsNumber()) ? ToInt() : def; }
|
|
double ToDouble(double def) {
|
|
return (IsValid() && IsNumber()) ? ToDouble() : def;
|
|
}
|
|
bool ToBool(bool def) {
|
|
return (IsValid() && ctx.GetType(idx) == J_BOOL) ? ctx.GetBool(idx) : def;
|
|
}
|
|
|
|
//-- API Helpers
|
|
int Size() {
|
|
if(!IsValid())
|
|
return 0;
|
|
int t = ctx.GetType(idx);
|
|
if(t == J_ARR || t == J_OBJ)
|
|
return ctx.GetCount(idx);
|
|
return 0;
|
|
}
|
|
|
|
bool HasKey(string key) {
|
|
// Minimal overhead reuse
|
|
return this[key].IsValid();
|
|
}
|
|
|
|
int GetKeys(string &dst[]) {
|
|
if(!IsObject()) {
|
|
ArrayResize(dst, 0);
|
|
return 0;
|
|
}
|
|
int count = Size();
|
|
ArrayResize(dst, count);
|
|
|
|
// Manual iteration to avoid iterator overhead if any?
|
|
// Iterator is fast enough.
|
|
CJsonIterator it = begin();
|
|
int i = 0;
|
|
while(it.IsValid() && i < count) {
|
|
dst[i++] = it.Key();
|
|
it.Next();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//-- Efficient String Comparison (No Allocation)
|
|
bool Equals(string val) {
|
|
if(!IsValid() || ctx.GetType(idx) != J_STR)
|
|
return false;
|
|
|
|
long data = ctx.tape[idx + 1];
|
|
int p = (int)(data >> 32);
|
|
int l = (int)(data & 0xFFFFFFFF);
|
|
|
|
if(StringLen(val) != l)
|
|
return false;
|
|
|
|
// Compare char by char
|
|
for(int i = 0; i < l; i++) {
|
|
if(ctx.buffer[p + i] != (uchar)StringGetCharacter(val, i))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-- Serialization of Subtree
|
|
string Serialize() {
|
|
if(!IsValid())
|
|
return "null";
|
|
uchar out[];
|
|
int pos = 0;
|
|
int cap = 1024;
|
|
ArrayResize(out, cap);
|
|
ctx.WriteNode(idx, out, pos, cap);
|
|
return CharArrayToString(out, 0, pos);
|
|
}
|
|
|
|
private:
|
|
CJsonNode GetNull() {
|
|
CJsonNode n;
|
|
n.ctx = NULL;
|
|
n.idx = -1;
|
|
return n;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonIterator Implementation |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode CJsonIterator::Val() {
|
|
if(!IsValid()) {
|
|
CJsonNode n;
|
|
n.ctx = NULL;
|
|
n.idx = -1;
|
|
return n;
|
|
}
|
|
|
|
int type = ctx.GetType(cur_idx);
|
|
if(type == J_KEY) {
|
|
int val_idx = cur_idx + 2;
|
|
CJsonNode n;
|
|
n.ctx = ctx;
|
|
n.idx = val_idx;
|
|
return n;
|
|
}
|
|
CJsonNode n;
|
|
n.ctx = ctx;
|
|
n.idx = cur_idx;
|
|
return n;
|
|
}
|
|
|
|
string CJsonIterator::Key() {
|
|
if(!IsValid())
|
|
return "";
|
|
if(ctx.GetType(cur_idx) == J_KEY) {
|
|
long kp = ctx.tape[cur_idx + 1];
|
|
int kptr = (int)(kp >> 32);
|
|
// v3.7.0: Extract key length from packed tape (no scan-for-quote)
|
|
int len = (int)((kp >> 24) & 0xFF);
|
|
return ctx.Unescape(kptr, len);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonBuilder: Production Grade |
|
|
//| Usage: |
|
|
//| CJsonBuilder b; |
|
|
//| b.Obj().Key("id").Val(1).EndObj(); |
|
|
//| string json = b.Build(); |
|
|
//+------------------------------------------------------------------+
|
|
class CJsonBuilder {
|
|
private:
|
|
uchar m_buf[];
|
|
int m_pos;
|
|
int m_cap;
|
|
bool m_stack_first[64]; // Track if first element in scope
|
|
int m_sp; // Stack pointer
|
|
|
|
public:
|
|
CJsonBuilder(int initial_cap = 1024) {
|
|
m_cap = initial_cap;
|
|
ArrayResize(m_buf, m_cap);
|
|
m_pos = 0;
|
|
m_sp = 0;
|
|
m_stack_first[0] = true;
|
|
}
|
|
|
|
void Clear() {
|
|
m_pos = 0;
|
|
m_sp = 0;
|
|
m_stack_first[0] = true;
|
|
}
|
|
string Build() { return CharArrayToString(m_buf, 0, m_pos, CP_UTF8); }
|
|
|
|
//-- Controls
|
|
CJsonBuilder *Obj() {
|
|
Comma();
|
|
Put('{');
|
|
if(m_sp < 63) {
|
|
m_sp++;
|
|
m_stack_first[m_sp] = true;
|
|
}
|
|
return &this;
|
|
}
|
|
|
|
CJsonBuilder *EndObj() {
|
|
if(m_sp > 0)
|
|
m_sp--;
|
|
Put('}');
|
|
m_stack_first[m_sp] = false; // Closed object is a value, next needs comma
|
|
return &this;
|
|
}
|
|
|
|
CJsonBuilder *Arr() {
|
|
Comma();
|
|
Put('[');
|
|
if(m_sp < 63) {
|
|
m_sp++;
|
|
m_stack_first[m_sp] = true;
|
|
}
|
|
return &this;
|
|
}
|
|
|
|
CJsonBuilder *EndArr() {
|
|
if(m_sp > 0)
|
|
m_sp--;
|
|
Put(']');
|
|
m_stack_first[m_sp] = false;
|
|
return &this;
|
|
}
|
|
|
|
//-- Values
|
|
CJsonBuilder *Key(string k) {
|
|
Comma();
|
|
PutEncodedStr(k);
|
|
Put(':');
|
|
// Key is the start of a pair. The value follows immediately.
|
|
// So we fake "first=true" so the next Val() doesn't output a comma.
|
|
m_stack_first[m_sp] = true;
|
|
return &this;
|
|
}
|
|
|
|
CJsonBuilder *Val(string v) {
|
|
Comma();
|
|
PutEncodedStr(v);
|
|
return &this;
|
|
}
|
|
CJsonBuilder *Val(int v) {
|
|
Comma();
|
|
PutRaw(IntegerToString(v));
|
|
return &this;
|
|
}
|
|
CJsonBuilder *Val(long v) {
|
|
Comma();
|
|
PutRaw(IntegerToString(v));
|
|
return &this;
|
|
}
|
|
CJsonBuilder *Val(double v) {
|
|
Comma();
|
|
PutRaw(DoubleToString(v));
|
|
return &this;
|
|
}
|
|
CJsonBuilder *Val(bool v) {
|
|
Comma();
|
|
PutRaw(v ? "true" : "false");
|
|
return &this;
|
|
}
|
|
CJsonBuilder *ValidJson(string fragment) {
|
|
Comma();
|
|
PutRaw(fragment);
|
|
return &this;
|
|
}
|
|
CJsonBuilder *Null() {
|
|
Comma();
|
|
PutRaw("null");
|
|
return &this;
|
|
}
|
|
|
|
private:
|
|
void Comma() {
|
|
if(!m_stack_first[m_sp])
|
|
Put(',');
|
|
m_stack_first[m_sp] = false;
|
|
}
|
|
|
|
void Put(uchar c) {
|
|
if(m_pos >= m_cap)
|
|
Expand(1);
|
|
m_buf[m_pos++] = c;
|
|
}
|
|
|
|
void PutRaw(string s) {
|
|
int l = StringLen(s);
|
|
int max_bytes = l * 4 + 1; // UTF-8 can take up to 4 bytes per char
|
|
if(m_pos + max_bytes >= m_cap)
|
|
Expand(max_bytes);
|
|
|
|
int written = StringToCharArray(s, m_buf, m_pos, WHOLE_ARRAY, CP_UTF8);
|
|
if(written > 0 && m_buf[m_pos + written - 1] == 0) {
|
|
written--; // remove null terminator
|
|
}
|
|
m_pos += written;
|
|
}
|
|
|
|
void PutEncodedStr(string s) {
|
|
Put('"');
|
|
int l = StringLen(s);
|
|
if(m_pos + l + 128 >= m_cap)
|
|
Expand(l + 128); // Heuristic
|
|
|
|
for(int i = 0; i < l; i++) {
|
|
ushort c = StringGetCharacter(s, i);
|
|
// Fast path for safe chars
|
|
if(c >= 32 && c != '"' && c != '\\' && c < 127) {
|
|
m_buf[m_pos++] = (uchar)c;
|
|
continue;
|
|
}
|
|
|
|
// Escaping
|
|
if(c == '"') {
|
|
Put('\\');
|
|
Put('"');
|
|
} else if(c == '\\') {
|
|
Put('\\');
|
|
Put('\\');
|
|
} else if(c == '\\b') {
|
|
Put('\\');
|
|
Put('b');
|
|
} else if(c == '\f') {
|
|
Put('\\');
|
|
Put('f');
|
|
} else if(c == '\n') {
|
|
Put('\\');
|
|
Put('n');
|
|
} else if(c == '\r') {
|
|
Put('\\');
|
|
Put('r');
|
|
} else if(c == '\t') {
|
|
Put('\\');
|
|
Put('t');
|
|
} else if(c < 32) {
|
|
// Hex escape \u00xx
|
|
Put('\\');
|
|
Put('u');
|
|
Put('0');
|
|
Put('0');
|
|
Put(HexChar((c >> 4) & 0xF));
|
|
Put(HexChar(c & 0xF));
|
|
} else {
|
|
// Unicode (> 127).
|
|
// Option 1: Output raw UTF-8 bytes (Standard JSON allows UTF-8).
|
|
// Option 2: Escape \uXXXX (Safe but larger).
|
|
// We choose Option 1 (UTF-8) for speed and modern compat.
|
|
// We need to convert this single char to UTF-8 bytes.
|
|
// Simplest is to let MQL5 convert sub-string, but that's slow.
|
|
// FAST HACK: If it's just one char, use StringToCharArray on it.
|
|
uchar temp[];
|
|
string one = ShortToString(c);
|
|
int tlen = StringToCharArray(one, temp, 0, WHOLE_ARRAY, CP_UTF8);
|
|
if(tlen > 0 && temp[tlen - 1] == 0)
|
|
tlen--;
|
|
if(m_pos + tlen > m_cap)
|
|
Expand(tlen);
|
|
ArrayCopy(m_buf, temp, m_pos, 0, tlen);
|
|
m_pos += tlen;
|
|
}
|
|
}
|
|
Put('"');
|
|
}
|
|
|
|
uchar HexChar(int v) { return (uchar)(v < 10 ? '0' + v : 'a' + (v - 10)); }
|
|
void Expand(int add) { m_cap += add + 4096; ArrayResize(m_buf, m_cap); }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJson: The Document Root (Wrapper) |
|
|
//| Usage: |
|
|
//| CJson json; |
|
|
//| if(json.Parse(str)) { |
|
|
//| string val = json["key"].ToString(); |
|
|
//| } |
|
|
//+------------------------------------------------------------------+
|
|
class CJson {
|
|
CJsonContext ctx;
|
|
|
|
public:
|
|
bool Parse(string json) { return ctx.Parse(json); }
|
|
|
|
// Root Access
|
|
CJsonNode GetRoot() {
|
|
if(ctx.tape_pos == 0) {
|
|
CJsonNode n;
|
|
n.ctx = NULL;
|
|
n.idx = -1;
|
|
return n;
|
|
}
|
|
CJsonNode n;
|
|
n.ctx = &ctx;
|
|
n.idx = 0;
|
|
return n;
|
|
}
|
|
|
|
// Direct Access via Root
|
|
CJsonNode operator[](string key) { return GetRoot()[key]; }
|
|
CJsonNode operator[](int index) { return GetRoot()[index]; }
|
|
|
|
string Serialize(bool pretty = false) { return ctx.Serialize(pretty); }
|
|
|
|
// Error Info
|
|
int GetLastError() { return ctx.last_error; }
|
|
void GetErrorPos(int &line, int &col) { ctx.GetErrorLocation(ctx.err_pos, line, col); }
|
|
};
|
|
//+------------------------------------------------------------------+ |