//+------------------------------------------------------------------+ //| 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.0 | //+------------------------------------------------------------------+ #property copyright "AIToolkit Framework" #property version "3.70" #property strict //+------------------------------------------------------------------+ //| 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 // 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); } 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; } bool GetBool(int idx) { return (tape[idx] & 1) == 1; } 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; while (ptr + 8 <= len) { ulong word = Load64(ptr); ulong x_q = word ^ SWAR_QUOTE; ulong x_s = word ^ SWAR_SLASH; ulong z_q = (x_q - SWAR_LO) & ~x_q & SWAR_HI; ulong z_s = (x_s - SWAR_LO) & ~x_s & SWAR_HI; if (z_q != 0 || z_s != 0) break; ptr += 8; } 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) { long int_val; c ^= '0'; // XOR-first: digits become 0-9, '.' becomes 0x1E, '-' becomes 0x1D if (this.GetInteger(int_val, cur, c)) { // Check for decimal point: '.' ^ '0' == 0x1E if (c == ('.' ^ '0')) { if (++cur < len) { c = buffer[cur] ^ '0'; if (c <= 9) { const int frac_start = cur; long frac_int; if (this.GetInteger(frac_int, cur, c)) { int frac_digits = cur - frac_start; double val; // Combine integer + fractional parts if (int_val > 0) val = int_val + frac_int * g_Exp10[frac_digits]; else if (int_val < 0) val = int_val - frac_int * g_Exp10[frac_digits]; else val = frac_int * g_Exp10[frac_digits]; // 0.xxx // Check for exponent: 'e'^'0'==0x55, 'E'^'0'==0x75 if (c == ('e' ^ '0') || c == ('E' ^ '0')) return this.SetExp(val, cur, c); else { tape[tape_pos++] = ((long)J_DBL << 56) | 2; tape[tape_pos++] = DBL2LONG(val); return true; } } } } } // Check for exponent without decimal else if (c == ('e' ^ '0') || c == ('E' ^ '0')) return this.SetExp((double)int_val, cur, c); 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(); } public: // 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) | 1; sp--; } else if (c == 'f') { cur += 5; tape[tape_pos++] = ((long)J_BOOL << 56); // v3.7.0: FIX — false = bit 0 clear sp--; } else if (c == 'n') { cur += 4; tape[tape_pos++] = ((long)J_NULL << 56) | 1; 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(GetDouble(idx), out, pos, cap); 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 += GetStep(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(GetDouble(idx), out, pos, cap); 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 += GetStep(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(double value, uchar &out[], int &pos, int &cap) { if (!MathIsValidNumber(value)) { PutRaw("null", out, pos, cap); return; } if (value == 0.0) { PutRaw("0.0", out, pos, cap); return; } if (value < 0.0) { PutChar('-', out, pos, cap); value = -value; } long int_part = (long)value; PutRawInteger(int_part, out, pos, cap); PutChar('.', out, pos, cap); double frac = value - (double)int_part; uchar frac_digits[8]; int n = 0; for (int k = 0; k < 8; k++) { frac *= 10.0; int d = (int)frac; frac -= d; frac_digits[n++] = (uchar)('0' + d); } while (n > 1 && frac_digits[n - 1] == '0') n--; CheckCap(n, pos, cap, out); for (int k = 0; k < n; k++) out[pos++] = frac_digits[k]; } 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(); // 1. Convert key to bytes + hash in a single pass int klen = StringLen(key); uchar kbuf[]; ArrayResize(kbuf, klen + 1); StringToCharArray(key, kbuf, 0, klen + 1, CP_UTF8); // Actual byte length may differ from StringLen for UTF-8 int kbytes = klen; // For ASCII keys (vast majority), these are equal 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; // 2. Scan (Unsafe Assumption: Object Structure is Valid) long head = ctx.tape[idx]; if (((head >> 56) & 0xFF) != J_OBJ) return GetNull(); long size = head & 0xFFFFFFFF; int end = idx + (int)size; int cur = idx + 1; // Skip Header // v3.7.0: Match on 24-bit hash + 8-bit length for faster rejection uint match_packed = ((uint)(kbytes & 0xFF) << 24) | (h & 0xFFFFFF); while (cur < end) { // Check packed (len + hash) match in one comparison if ((uint)(ctx.tape[cur + 1] & 0xFFFFFFFF) == match_packed) { // Found! Return Value (Key is 2 slots) CJsonNode node; node.ctx = ctx; node.idx = cur + 2; return node; } // Branchless Leap 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) return ctx.GetDouble(idx); 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 == '\x08') { 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 { public: CJsonContext ctx; bool Parse(string json) { return ctx.Parse(json); } // Esta funcion ya asume que el user lleno el buffer (modificas) // Buffer // len bool ParseData() { ctx.tape_pos = 0; ctx.sp = 0; ctx.last_error = JSON_OK; return ctx._DoParse(); } // 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); } }; //+------------------------------------------------------------------+