//+------------------------------------------------------------------+ //| JsonParser.mqh | //| Copyright 2026, Niquel Mendoza. | //| https://www.mql5.com/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Niquel Mendoza." #property link "https://www.mql5.com/" #property strict #ifndef JSONPARSERBYLEO_SRC_JSONPARSER_MQH #define JSONPARSERBYLEO_SRC_JSONPARSER_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "JsonDefines.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ namespace TSN { //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct CJsonNode; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CJsonParser { public: //--- long m_cinta[]; uchar m_json[]; //--- int m_cinta_pos; //--- int m_len; // Tamaño a leer de m_json int m_offset; // Desde donde compienza m_pos private: //--- int m_pos; // pos en json int m_cinta_reserve; //--- int m_stak_num[512]; int m_stak_pos[512]; char m_stak_state[512]; int m_stak_size; int m_stak_curr; int m_stak_curr_state; //--- ENUM_TSN_JSON_LAST_ERR m_last_err; uchar m_next; //--- bool m_next_has_k; //--- void ProccesDecimalPart(double val); public: CJsonParser(void) : m_cinta_reserve(0), m_len(0), m_offset(0) {} ~CJsonParser(void) {} //--- // Para data raw se puede usar el json uchar directamtne __forceinline void CalcLen() { m_len = ArraySize(m_json); } inline void Assing(const string& json); void AssingFile(const string& file_name, const bool comon_flag); bool Parse(); //--- __forceinline ENUM_TSN_JSON_LAST_ERR LastErr() const { return m_last_err; } //--- string Unescape(const int start, const int end) const; int Unescape(const int start, const int end, uchar& res[]) const; static int HexToDec(const uchar c); //--- __forceinline int GetStep(const int idx) const; __forceinline int GetStepPure(const int idx) const; //--- void GetLastErrorLocation(int& col, int& line) const; //--- void PrintCintaTypes() const; //--- CJsonNode GetRoot(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ inline void CJsonParser::Assing(const string& json) { m_len = StringToCharArray(json, m_json); m_offset = 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CJsonParser::AssingFile(const string& file_name, const bool comon_flag) { //--- m_len = (int)FileLoad(file_name, m_json, (comon_flag ? FILE_COMMON : 0)); m_offset = 0; //--- if(m_len >= 2) { // UTF16 BE if(m_json[0] == 0xFF && m_json[1] == 0xFE) { const int n = (m_len - 2) >> 1; for(int i = 0; i < n; i++) m_json[i] = m_json[2 + (i * 2)]; // byte bajo (el char real) m_len = ArrayResize(m_json, n); } // UTF16 LE else if(m_json[0] == 0xFE && m_json[1] == 0xFF) { const int n = (m_len - 2) >> 1; for(int i = 0; i < n; i++) m_json[i] = m_json[3 + (i * 2)]; // byte alto (invertido) m_len = ArrayResize(m_json, n); } } else if(m_len >= 3 && m_json[0] == 0xEF && m_json[1] == 0xBB && m_json[2] == 0xBF) { // UTF-8 BOM m_offset = 3; // simplemente saltear } //--- //Print(CharArrayToString(m_json)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #define TSN_JSON_NEXT \ while(m_pos < m_len) \ { \ const uchar c = m_json[m_pos]; \ if(c < 33) \ { \ m_pos++; \ continue; \ } \ if(c > JSON_MAX_VALUE_C) \ { \ m_last_err = TSN_JSON_ERR_INVALID_CHAR; \ return false; \ } \ const uchar locked = m_next; \ m_next = g_table_json_tokens[c - 34]; \ const int f = g_json_to_f[m_next];\ if(f != 0 && (f & g_json_expected[m_stak_curr_state][locked]) == 0)\ { \ m_last_err= TSN_JSON_ERR_MALFORDMED_JSON; \ return false; \ } \ break; \ } //--- #define TSN_JSON_RESERVAR(N) \ if(m_cinta_pos + N > m_cinta_reserve) \ { \ m_cinta_reserve += (m_cinta_reserve << 1) + N; \ if(ArrayResize(m_cinta, m_cinta_reserve, m_cinta_reserve) == -1) \ { \ m_last_err=TSN_JSON_ERR_OVERFLOW_IN_CINTA_RESIZE; \ return false; \ } \ } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CJsonParser::ProccesDecimalPart(double val) { //--- if(m_pos < m_len && m_json[m_pos] == '.') // Parte decimal { //--- m_pos++; long frac_int = 0; int frac_digits = 0; //--- for(; m_pos < m_len; m_pos++) { const uchar c = m_json[m_pos]; if(IsNotDigit(c)) break; frac_int = frac_int * 10 + (c - '0'); frac_digits++; } if(frac_digits <= 18) val += frac_int * g_Exp10[frac_digits]; else { double frac = (double)frac_int; for(int k = 0; k < frac_digits; k++) frac *= 0.1; val += frac; } } //--- if(m_pos < m_len && (m_json[m_pos] == 'e' || m_json[m_pos] == 'E')) // e or E { //--- m_pos++; int exp_sign = 1; if(m_pos < m_len && m_json[m_pos] == '-') { exp_sign = -1; m_pos++; } else if(m_pos < m_len && m_json[m_pos] == '+') { m_pos++; } //--- int exp_val = 0; for(; m_pos < m_len; m_pos++) { const uchar c = m_json[m_pos]; if(IsNotDigit(c)) break; exp_val = exp_val * 10 + (c - '0'); } //--- if(exp_val <= 18) { if(exp_sign == 1) val *= g_Pow10[exp_val]; else val *= g_Exp10[exp_val]; } else { double p = 1.0; for(int k = 0; k < exp_val; k++) p *= 10.0; if(exp_sign == 1) val *= p; else val /= p; } } //--- static BitInterpreter bit; bit.double_value = val; m_cinta[m_cinta_pos++] = JSON_VTYPE_REAL; m_cinta[m_cinta_pos++] = bit.long_value; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CJsonParser::Parse() { //--- m_stak_size = 0; m_stak_curr_state = -1; m_stak_curr = -1; m_next_has_k = false; m_last_err = TSN_JSON_NOT_ERR; m_next = TSN_JSON_TOK_INVALID; m_pos = m_offset; m_cinta_pos = 0; const int expected = m_len * 2 + 128; if(expected > m_cinta_reserve) { m_cinta_reserve = expected; ArrayResize(m_cinta, m_cinta_reserve, m_cinta_reserve); } //--- Inicial while(m_pos < m_len) { const uchar c = m_json[m_pos]; if(c < 33) { m_pos++; continue; } if(c > JSON_MAX_VALUE_C) { m_last_err = TSN_JSON_ERR_INVALID_CHAR; return false; } m_next = g_table_json_tokens[c - 34]; if(m_next < TSN_JSON_TOK_LLAVE_INI || m_next > TSN_JSON_TOK_COR_INI) { m_last_err = TSN_JSON_ERR_EXPECTED_OBJ_ARR; return false; } break; } //--- Bucle while(true) { switch(m_next) { case TSN_JSON_TOK_NUMBER: { //--- //TSN_JSON_RESERVAR(2) //--- Parte 1... long v = 0; int sign = 1; //--- Validamos signo if(m_json[m_pos] == '-') { sign = -1; m_pos++; } //--- while(m_pos < m_len) { const uchar ch = m_json[m_pos]; if(IsDigit(ch)) { v = v * 10 + (ch - '0'); m_pos++; } else if(ch == '.' || ch == 'e' || ch == 'E') { ProccesDecimalPart((double)v); break; } else { m_cinta[m_cinta_pos++] = JSON_VTYPE_INTEGER; m_cinta[m_cinta_pos++] = v * sign; break; // Fin } } //--- no hago el m_pos++ dado que ahora mismo estamo en el seg char TSN_JSON_NEXT break; } case TSN_JSON_TOK_STRING: { //--- //TSN_JSON_RESERVAR(2) //--- const int start = ++m_pos; if(m_next_has_k) { // Modifcamos nuestro m_next actual (esta en string TSN_JSON_TOK_STRING) m_next = TSN_JSON_TOK_KEY; m_cinta[m_cinta_pos] = JSON_VTYPE_KEY; uint key_hash = 2166136261; while(m_pos < m_len) { //--- if(m_json[m_pos] == '"') { //PrintFormat("Key: '%s'", CharArrayToString(m_json, start, ((m_pos - 1) - start) + 1)); m_cinta[m_cinta_pos++] |= long(key_hash) << TSN_JSON_BIT_STR_START_HASH; m_cinta[m_cinta_pos++] = long(m_pos - 1) | long(start) << TSN_JSON_BIT_STR_START_END; break; } //--- key_hash ^= uint(m_json[m_pos]); key_hash *= 16777619; //--- if(m_json[m_pos] == '\\') { m_pos += 2; if(m_pos + 1 < m_len) { key_hash ^= uint(m_json[m_pos + 1]); key_hash *= 16777619; } continue; } //--- m_pos++; } } else { m_cinta[m_cinta_pos++] = JSON_VTYPE_STRING; while(m_pos < m_len) { if(m_json[m_pos] == '\\') { m_pos += 2; continue; } //--- if(m_json[m_pos] == '"') { m_cinta[m_cinta_pos++] = long(m_pos - 1) | long(start) << TSN_JSON_BIT_STR_START_END; break; } //--- m_pos++; } } //--- if(m_json[m_pos++] != '"') { m_last_err = TSN_JSON_ERR_STRING_NOT_CLOSED; return false; } //--- TSN_JSON_NEXT break; } case TSN_JSON_TOK_BOOL_TRUE: { //TSN_JSON_RESERVAR(1) // t r u e W // [-4] [-3] [-2] [-1] [0] if((m_pos += 4) >= m_len || m_json[m_pos - 3] != 'r' || m_json[m_pos - 2] != 'u' || m_json[m_pos - 1] != 'e') { m_last_err = TSN_JSON_ERR_MALFORMED_TRUE; return false; } // 2 | 1 << 8 = 258 // JSON_VTYPE_BOOLEAN | long(1) << TSN_JSON_BIT_START_ENDTYPE m_cinta[m_cinta_pos++] = 258; //--- TSN_JSON_NEXT break; } case TSN_JSON_TOK_BOOL_FALSE: { //TSN_JSON_RESERVAR(1) // f a l s e W // [] [-4] [-3] [-2 ] [-1] [0] if((m_pos += 5) >= m_len || m_json[m_pos - 4] != 'a' || m_json[m_pos - 3] != 'l' || m_json[m_pos - 2] != 's' || m_json[m_pos - 1] != 'e') { m_last_err = TSN_JSON_ERR_MALFORMED_FALSE; return false; } m_cinta[m_cinta_pos++] = JSON_VTYPE_BOOLEAN; //--- TSN_JSON_NEXT break; } case TSN_JSON_TOK_NULL: { //TSN_JSON_RESERVAR(1) // n u l l W // [-4][-3][-2][-1][] if((m_pos += 4) >= m_len || m_json[m_pos - 3] != 'u' || m_json[m_pos - 2] != 'l' || m_json[m_pos - 1] != 'l') { m_last_err = TSN_JSON_ERR_MALFORMED_NULL; return false; } m_cinta[m_cinta_pos++] = JSON_VTYPE_NULL; //--- TSN_JSON_NEXT break; } case TSN_JSON_TOK_COMMA: { if(m_stak_curr_state == TSN_JSON_CTX_IN_OBJ) { m_next_has_k = true; } //--- m_stak_num[m_stak_curr]++; //--- m_pos++; //--- TSN_JSON_NEXT break; } case TSN_JSON_TOK_LLAVE_INI: { //TSN_JSON_RESERVAR(1) m_cinta[m_cinta_pos] = JSON_VTYPE_OBJ; //--- m_next_has_k = true; //--- m_stak_curr = m_stak_size++; m_stak_curr_state = TSN_JSON_CTX_IN_OBJ; m_stak_state[m_stak_curr] = TSN_JSON_CTX_IN_OBJ; m_stak_num[m_stak_curr] = 0; m_stak_pos[m_stak_curr] = m_cinta_pos++; //--- m_cinta[m_cinta_pos++] = m_pos++; //--- TSN_JSON_NEXT //--- break; } case TSN_JSON_TOK_COR_INI: { //TSN_JSON_RESERVAR(1) m_cinta[m_cinta_pos] = JSON_VTYPE_ARR; //--- m_next_has_k = false; //--- m_stak_curr = m_stak_size++; m_stak_curr_state = TSN_JSON_CTX_IN_ARR; m_stak_state[m_stak_curr] = TSN_JSON_CTX_IN_ARR; m_stak_num[m_stak_curr] = 0; m_stak_pos[m_stak_curr] = m_cinta_pos++; //--- m_cinta[m_cinta_pos++] = m_pos++; //--- TSN_JSON_NEXT //--- break; } case TSN_JSON_TOK_LLAVE_END: case TSN_JSON_TOK_COR_END: { //--- // 10 11 12 13 if(m_cinta_pos > m_stak_pos[m_stak_curr] + 2) // [a][a][v][c] { m_stak_num[m_stak_curr]++; } //--- const int lp = m_stak_pos[m_stak_curr]; m_cinta[lp] |= long(m_stak_num[m_stak_curr]) << TSN_JSON_BIT_START_ENDTYPE | long(m_cinta_pos - lp) << TSN_JSON_BIT_START_NUM_C; m_cinta[lp + 1] |= long(m_pos) << TSN_JSON_BIT_END_T; //--- m_stak_size = m_stak_curr--; //--- if(m_stak_size == 0) { return true; } else { m_stak_curr_state = m_stak_state[m_stak_curr]; } //--- m_pos++; //--- TSN_JSON_NEXT //--- break; } case TSN_JSON_TOK_FIN_KEY: { m_next_has_k = false; //--- m_pos++; //--- TSN_JSON_NEXT //--- break; } default: { m_last_err = TSN_JSON_ERR_INVALID_CHAR; return false; } } } //--- m_last_err = TSN_JSON_ERR_JSON_NOT_FINISH; //--- return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ static int CJsonParser::HexToDec(const uchar c) { if(c >= '0' && c <= '9') return c - '0'; if(c >= 'a' && c <= 'f') return c - 'a' + 10; if(c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CJsonParser::Unescape(const int start, const int end, uchar& res[]) const { //--- ArrayResize(res, (end - start) + 1); int pos = 0; //--- for(int i = start; i <= end; i++) { if(m_json[i] == '\\' && i + 1 <= end) { i++; const uchar next = m_json[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 <= end) { const uint code = (HexToDec(m_json[i + 1]) << 12) | (HexToDec(m_json[i + 2]) << 8) | (HexToDec(m_json[i + 3]) << 4) | HexToDec(m_json[i + 4]); i += 4; //--- encode to UTF-8 if(pos + 4 >= ArraySize(res)) ArrayResize(res, pos + 64); if(code < 0x80) { res[pos++] = uchar(code); } else if(code < 0x800) { res[pos++] = uchar(0xC0 | (code >> 6)); res[pos++] = uchar(0x80 | (code & 0x3F)); } else { res[pos++] = uchar(0xE0 | (code >> 12)); res[pos++] = uchar(0x80 | ((code >> 6) & 0x3F)); res[pos++] = uchar(0x80 | (code & 0x3F)); } } else res[pos++] = 'u'; break; } default: res[pos++] = next; break; } } else res[pos++] = m_json[i]; } //--- return ArrayResize(res, pos); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string CJsonParser::Unescape(const int start, const int end) const { //--- string res = ""; StringSetLength(res, (end - start) + 1); int pos = 0; //--- for(int i = start; i <= end; i++) { if(m_json[i] == '\\' && i + 1 <= end) { i++; const uchar next = m_json[i]; switch(next) { case '"': res.SetChar(pos++, '"'); break; case '\\': res.SetChar(pos++, '\\'); break; case '/': res.SetChar(pos++, '/'); break; case 'b': res.SetChar(pos++, '\x08'); break; case 'f': res.SetChar(pos++, '\f'); break; case 'n': res.SetChar(pos++, '\n'); break; case 'r': res.SetChar(pos++, '\r'); break; case 't': res.SetChar(pos++, '\t'); break; case 'u': { // \uFFFF if(i + 4 <= end) { const int code = (HexToDec(m_json[i + 1]) << 12) | (HexToDec(m_json[i + 2]) << 8) | (HexToDec(m_json[i + 3]) << 4) | HexToDec(m_json[i + 4]); res.SetChar(pos++, (ushort)code); i += 4; } else res.SetChar(pos++, 'u'); break; } default: res.SetChar(pos++, next); break; } } else res.SetChar(pos++, m_json[i]); } //--- res.Truncate(pos); //--- return res; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ __forceinline int CJsonParser::GetStepPure(const int idx) const { const int tipo = int(m_cinta[idx] & B'1111'); switch(tipo) { case JSON_VTYPE_INTEGER: return 2; case JSON_VTYPE_REAL: return 2; case JSON_VTYPE_BOOLEAN: return 1; case JSON_VTYPE_STRING: return 2; case JSON_VTYPE_NULL: return 1; case JSON_VTYPE_ARR: return 2; case JSON_VTYPE_OBJ: return 2; case JSON_VTYPE_KEY: return 2; } return 1; } //+------------------------------------------------------------------+ //| GetStep: posiciones que ocupa el nodo idx en la cinta | //+------------------------------------------------------------------+ __forceinline int CJsonParser::GetStep(const int idx) const { switch(int(m_cinta[idx] & 0xFF)) { case JSON_VTYPE_INTEGER: return 2; case JSON_VTYPE_REAL: return 2; case JSON_VTYPE_BOOLEAN: return 1; case JSON_VTYPE_STRING: return 2; case JSON_VTYPE_NULL: return 1; case JSON_VTYPE_ARR: return int(m_cinta[idx] >> TSN_JSON_BIT_START_NUM_C); case JSON_VTYPE_OBJ: return int(m_cinta[idx] >> TSN_JSON_BIT_START_NUM_C); case JSON_VTYPE_KEY: return 2; } return 1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CJsonNode CJsonParser::GetRoot() { if(m_cinta_pos == 0) return EMPTY_JSON_NODE; return CJsonNode(&this, 0, GetStep(0)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CJsonParser::PrintCintaTypes() const { int p = 0; while(p < m_cinta_pos) { const ENUM_JSON_VTYPE tipo = ENUM_JSON_VTYPE(m_cinta[p] & 0xFF); string text = "[" + (string)p + "] " + EnumToString(tipo) + " | "; //--- switch(tipo) { case JSON_VTYPE_INTEGER: text += "val=" + (string)m_cinta[p + 1]; break; case JSON_VTYPE_REAL: { static BitInterpreter un; un.long_value = m_cinta[p + 1]; text += "val=" + (string)un.double_value; break; } case JSON_VTYPE_BOOLEAN: text += "val=" + (string)(bool)((m_cinta[p] >> TSN_JSON_BIT_START_ENDTYPE) & 1); break; case JSON_VTYPE_STRING: { const int s = int(m_cinta[p + 1] >> TSN_JSON_BIT_STR_START_END); const int e = int(m_cinta[p + 1] & 0xFFFFFFFF); text += "start=" + (string)s + " end=" + (string)e + " val=\"" + CharArrayToString(m_json, s, (e - s) + 1) + "\""; break; } case JSON_VTYPE_NULL: text += "null"; break; case JSON_VTYPE_ARR: case JSON_VTYPE_OBJ: { const int count = int((m_cinta[p] >> TSN_JSON_BIT_START_ENDTYPE) & 0xFFFFFF); const int num_c = int(m_cinta[p] >> TSN_JSON_BIT_START_NUM_C); const int raw_s = int(m_cinta[p + 1] & 0xFFFFFFFF); const int raw_e = int(m_cinta[p + 1] >> TSN_JSON_BIT_END_T); text += "count=" + (string)count + " num_c=" + (string)num_c + " raw_start=" + (string)raw_s + " raw_end=" + (string)raw_e; break; } case JSON_VTYPE_KEY: { const int s = int(m_cinta[p + 1] >> TSN_JSON_BIT_STR_START_END); const int e = int(m_cinta[p + 1] & 0xFFFFFFFF); const uint hash = uint(m_cinta[p] >> TSN_JSON_BIT_STR_START_HASH); text += "hash=" + (string)hash + " key=\"" + CharArrayToString(m_json, s, (e - s) + 1) + "\""; break; } } Print(text); p += GetStepPure(p); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CJsonParser::GetLastErrorLocation(int &col, int &line) const { col = 1; line = 1; //Print(CharArrayToString(m_json, 0, m_pos)); for(int i = 0; i <= m_pos && i < m_len; i++) { if(m_json[i] == '\n') { line++; col = 1; } else col++; } } } #endif // JSONPARSERBYLEO_SRC_JSONPARSER_MQH //+------------------------------------------------------------------+