| //+------------------------------------------------------------------+
|
| //| GLM.mqh |
|
| //| Copyright 2026, Niquel Mendoza. |
|
| //| https://www.mql5.com/ |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2026, Niquel Mendoza."
|
| #property link "https://www.mql5.com/"
|
| #property strict
|
|
|
| //+------------------------------------------------------------------+
|
| //| |
|
| //+------------------------------------------------------------------+
|
| // Same prompt claude\mql5 lite
|
| /* Notas GLM 5.2 MAX
|
| INICIO 7:21 pm
|
|
|
| 1: Varios errores al empezar
|
| 'b' - unrecognized character escape sequence GLM.mqh 435 52
|
| cannot convert parameter 'CJson&' to 'CJson*' GLM.mqh 788 46
|
| could be one of 2 function(s) GLM.mqh 788 35
|
| implicit CJsonValue::CJsonValue(const CJsonValue&) GLM.mqh 104 8
|
| CJsonValue::CJsonValue(CJson*,int) GLM.mqh 109 5
|
| cannot convert parameter 'CJson&' to 'CJson*' GLM.mqh 821 62
|
| could be one of 2 function(s) GLM.mqh 821 51
|
| implicit CJsonValue::CJsonValue(const CJsonValue&) GLM.mqh 104 8
|
| CJsonValue::CJsonValue(CJson*,int) GLM.mqh 109 5
|
| cannot convert parameter 'CJson&' to 'CJson*' GLM.mqh 822 61
|
| implicit CJsonValue::CJsonValue(const CJsonValue&) GLM.mqh 104 8
|
| CJsonValue::CJsonValue(CJson*,int) GLM.mqh 109 5
|
| expression not boolean GLM.mqh 849 26
|
| expression not boolean GLM.mqh 881 26
|
| cannot convert parameter 'CJson&' to 'CJson*' GLM.mqh 888 36
|
| could be one of 2 function(s) GLM.mqh 888 25
|
| implicit CJsonValue::CJsonValue(const CJsonValue&) GLM.mqh 104 8
|
| CJsonValue::CJsonValue(CJson*,int) GLM.mqh 109 5
|
| cannot convert parameter 'CJson&' to 'CJson*' GLM.mqh 889 23
|
| could be one of 2 function(s) GLM.mqh 889 12
|
| implicit CJsonValue::CJsonValue(const CJsonValue&) GLM.mqh 104 8
|
| CJsonValue::CJsonValue(CJson*,int) GLM.mqh 109 5
|
| 5 errors, 3 warnings 5 3
|
|
|
| 2.
|
| GLM.mqh
|
| expression not boolean GLM.mqh 873 26
|
| expression not boolean GLM.mqh 905 26
|
| 0 errors, 2 warnings, 25 ms elapsed, cpu='AVX2 + FMA3' 2
|
|
|
| Iteración 2 (le pasé el log de compilación con los 5 errores):
|
| - GLM corrigió los 5 errores de conversión CJson&/CJson* sin que yo interviniera
|
| - Quedaron 2 warnings "expression not boolean" en líneas 873 y 905
|
| - Causa: MQL5 no permite if(puntero) como C++, requiere if(puntero != NULL)
|
| - Corrección manual mía (no le pedí que lo arregle, 2 líneas triviales):
|
| línea 873: if(a) → if(a != NULL)
|
| línea 905: if(b) → if(b != NULL)
|
| - 0 errors, 2 warnings resueltos, compiló en 25ms
|
|
|
| 3. En test:
|
| 2026.07.27 19:32:20.435 GLM (EURUSD,M1) array out of range in 'GLM.mqh' (641,25)
|
| Bueno ya lo corregido
|
| 4. ITERACION:
|
| GLM.mqh
|
| expression not boolean GLM.mqh 863 26
|
| expression not boolean GLM.mqh 895 26
|
| 0 errors, 2 warnings, 30 ms elapsed, cpu='AVX2 + FMA3' 2
|
| mismos errores los corregire yo
|
|
|
|
|
| 5. ITERACION: (ahora mismo 7:44 pm)
|
| cuando hago Print(json["statuses"].Size());
|
| Falla entonces hay que corregir...
|
| 2026.07.27 19:42:18.711 GLM (EURUSD,M1) 0
|
| Prompt a usar para correcion
|
| "El parser compila y corre sin errores, pero json["statuses"].Size() devuelve 0 cuando debería devolver 100
|
| (el JSON de prueba tiene un array 'statuses' con 100 elementos en su root). Revisá específicamente la
|
| lógica de indexación de arrays anidados dentro de objetos — el problema parece estar en cómo se resuelve el operator[]
|
| o cómo se cuenta el tamaño de un array hijo, no en el parseo del texto en sí. "
|
|
|
| Respuesta de la LLM
|
| "Has identificado el problema exacto: el árbol se construye perfectamente, pero la función GetNode no encuentra el hijo porque m_tempKeyHash nunca
|
| se calculaba durante el parseo de la clave. Al quedar en 0, el hash nunca coincidía con el hash real de la clave (como "statuses"), por lo que GetNode devolvía -1 y Size() devolvía 0.
|
|
|
| He añadido el cálculo del hash FNV-1a directamente sobre los bytes del buffer durante ParseString.
|
| Esto es incluso más rápido que calcularlo después, ya que lee la memoria de forma contigua mientras parsea.
|
| Aquí tienes el código final y corregido"
|
|
|
| Siuguen los mismos errores:
|
| expression not boolean GLM.mqh 896 26
|
| expression not boolean GLM.mqh 928 26
|
| 0 errors, 2 warnings, 26 ms elapsed, cpu='AVX2 + FMA3' 2
|
|
|
| Parece uqe no se da cuenta, bueno lo hare denuevo que mas da
|
|
|
| Repeusta de la LLM:
|
| "¡Has encontrado el bug exacto! El problema no estaba en el parseo de las claves ni en los hashes, sino en un detalle de inicialización en la función Parse().
|
|
|
| Lo que pasaba era:
|
|
|
| En Reset() se reservaba el nodo 0 como padre "dummy" del root.
|
| En Parse(), el códigovolvía a reservar otro nodo (el nodo 1) y parseaba el JSON dentro de él.
|
| Luego, intentaba buscar el root en m_nodes[0].Child, pero el root real estaba en m_nodes[1].Child. Por lo tanto, m_root se quedaba en -1, y al hacer json["statuses"], el parent_id era -1, lo que hacía que la función devolviera -1 y el Size() diera 0.
|
| Adicionalmente, StringToCharArray añade un byte nulo (\0) al final del array, lo que provocaba que Parse() creyera que había un "carácter inesperado" al final (aunque igualmente terminaba de construir el árbol). Lo he solucionado haciendo que SkipWhitespace ignore el byte nulo.
|
|
|
| Aquí tienes el código 100% funcional. Haz la prueba con twitter.json y verás cómo ahora devuelve 100:"
|
|
|
| Bueno probemos
|
|
|
| expression not boolean GLM.mqh 912 26
|
| expression not boolean GLM.mqh 944 26
|
| 0 errors, 2 warnings, 24 ms elapsed, cpu='AVX2 + FMA3' 2
|
|
|
| mismos errores... bueno ya es rutina..
|
|
|
| 8va iteracion:
|
| 2026.07.27 19:58:08.845 GLM (EURUSD,M1) false
|
| 2026.07.27 19:58:08.845 GLM (EURUSD,M1) Invalid escape sequence
|
| 2026.07.27 19:58:08.845 GLM (EURUSD,M1) 6031
|
| 2026.07.27 19:58:08.845 GLM (EURUSD,M1) 28
|
| 2026.07.27 19:58:08.845 GLM (EURUSD,M1) 0
|
|
|
| Errores en parse pro lo visto da error con "description": "\ もうすぐ18歳 "Only One"になる /",
|
| Le dire que lo corrgia
|
|
|
| BUyeno ya mnas intetnos y no logra dar con las ociuino sigue es error
|
| 2026.07.27 20:10:11.006 GLM (EURUSD,M1) false
|
| 2026.07.27 20:10:11.006 GLM (EURUSD,M1) Invalid escape sequence
|
| 2026.07.27 20:10:11.006 GLM (EURUSD,M1) 6031
|
| 2026.07.27 20:10:11.006 GLM (EURUSD,M1) 28
|
| 2026.07.27 20:10:11.006 GLM (EURUSD,M1) 0
|
|
|
| Solo agrego mas veiriaicon a utf8 pero creo que la casua raiz es otra...
|
|
|
| > NOTA ayudare a glm me dio pena le dire que en vez de trata de parsear el string ahi lo haga lazy
|
| > Su aruquicata acutal tien ecomo un pool en el cual estan todos los trins y hay que hacer reiszes contantes etc..}
|
|
|
| 9 Iteraicon (8:17 pm ya creo que apsaon 40+ min solo con glm.. esto me hace perder el tiempo pero bueno qeu sea por ver que tan bien rinde)
|
| // pero ya digo que es un modelo que sinceante no uauria para nada..
|
| // si buscas bench de este modelo parece que compile con claude en c++ o main languages
|
| // pero en mql5 es mejor solo que el lite gose.. literlante
|
| // preferiia usar creo gpt.. a usar esto.
|
|
|
| BUeno me respondio glm
|
| "Tienes toda la razón. Esa es la arquitectura óptima para un parser de alto rendimiento: Separar la fase de tokenización (encontrar límites) de la fase de decodificación (resolver escapes y codificar a UTF-16).
|
|
|
| Esto no solo elimina los bugs de estado, sino que hace que el parseo sea extremadamente rápido, ya que pospone el trabajo pesado de decodificar strings hasta el momento en que el usuario realmente solicita el valor (Lazy Evaluation). Además, si solo necesitas leer un valor específico de un JSON gigante (como json["statuses"].Size()), los strings que no se consultan nunca se decodifican.
|
|
|
| He refactorizado completamente el parser implementando tu sugerencia. Eliminé el pool de memoria para strings, los hashes precalculados y el estado temporal. Ahora el parser solo registra offsets y longitudes.
|
|
|
| Aquí tienes la versión definitiva, mucho más limpia, robusta y rápida:"
|
|
|
| XD nada:
|
| 2026.07.27 20:20:10.227 GLM (EURUSD,M1) true
|
| 2026.07.27 20:20:10.227 GLM (EURUSD,M1) OK
|
| 2026.07.27 20:20:10.227 GLM (EURUSD,M1) 1
|
| 2026.07.27 20:20:10.227 GLM (EURUSD,M1) 1
|
| 2026.07.27 20:20:10.227 GLM (EURUSD,M1) 0
|
|
|
| Seguoms en lo mismo
|
|
|
| Por cieto no mostre el codigop de prueba aqui esta:
|
|
|
| #include "..\\Other\\GLM.mqh"
|
| #resource "twitter.json" as const string g_json_data
|
|
|
| //+------------------------------------------------------------------+
|
| //| Script program start function |
|
| //+------------------------------------------------------------------+
|
| void OnStart()
|
| {
|
| //---
|
| CJson json;
|
| StringToCharArray(g_json_data, json.Buffer);
|
| Print(json.Parse());
|
| Print(json.GetErrorMessage());
|
| Print(json.GetLine());
|
| Print(json.GetColumn());
|
|
|
| Print(json["statuses"].Size());
|
| }
|
| //+------------------------------------------------------------------+
|
|
|
|
|
| Basiamtne esoty tranto de hacer uqe funcione
|
| //---
|
| CJson json;
|
| StringToCharArray(g_json_data, json.Buffer);
|
| Print(json.Parse());
|
|
|
| CJsonValue value = json.Root();
|
| Print(json.GetNode(1, "statuses"));
|
| Print(value.Size());
|
|
|
| Bueno nada no funciona... estuv eprobado y da esto:
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) true
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) lang | 2
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) since_id_str | 13905
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) -1
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) 2
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) 13905
|
| 2026.07.27 20:29:28.485 GLM (EURUSD,M1) 2
|
|
|
| UNa tonteria... pro lo quente parece haber buscs con las child o eso por que lang es un campo de dos nivels (detnro de un)
|
| objeto de stautus (root)
|
|
|
| Le dire que lo corriga yr evisae
|
|
|
| FUNCIONO .. por fin 1 hora y 12 minutos me tarde..... (8:35 pm)
|
|
|
| Ahora si probemos
|
| //
|
| 2026.07.27 20:40:22.487 GLM (EURUSD,M1) Time: 1336122
|
| 2026.07.27 20:40:26.971 GLM (EURUSD,M1) Time: 1330672
|
| // MAS LENTO QUE CLAUDE...
|
|
|
|
|
| ---- Byeno resumen final...
|
| GLM 5.2 MAX (deep think) — Resultado final
|
| Tiempo total: ~1h 12min
|
| Iteraciones: 15+
|
| Resultado: Funcional al final (Parse()=true, statuses.Size()=100 correcto)
|
|
|
| Progresión de bugs encontrados:
|
| 1. Errores de compilación (confusión C++/MQL5: CJson& vs CJson*, if(puntero) sin != NULL)
|
| 2. array out of range en runtime
|
| 3. Hash de clave nunca calculado (Size()=0)
|
| 4. Root apuntando a nodo dummy incorrecto (Size()=0, 2do intento)
|
| 5. Falso "Invalid escape sequence" con UTF-8 multibyte (\ japonés confundido con backslash)
|
| 6. Root apuntando a nivel de anidamiento incorrecto (mezclaba campos de search_metadata
|
| y statuses[0] como si fueran hijos directos del root) — root building bug real,
|
| distinto del bug #4
|
|
|
| Intervención humana: sugerencia de arquitectura (separar tokenización de decodificación,
|
| offsets + lazy decode) — el propio GLM reconoció que era "la arquitectura óptima" y la
|
| implementó, aunque no resolvió el bug de root por sí sola
|
|
|
| Rendimiento final (JSON completo, 1000 iteraciones): 1328-1342ms
|
| Comparación: Claude Opus 4.8 = 1312-1313ms (single-shot, sin iteración)
|
| JsonParserByLeo = 651-667ms
|
| Conclusión: GLM 5.2 quedó ligeramente por detrás de Claude en velocidad, y le tomó
|
| ~15 iteraciones + 1 sugerencia de arquitectura llegar a un resultado correcto,
|
| contra el intento único de Claude.
|
| */
|
|
|
| //+------------------------------------------------------------------+
|
| //| Json.mqh |
|
| //| High Performance JSON Parser for MQL5 |
|
| //+------------------------------------------------------------------+
|
| #ifndef JSON_MQH
|
| #define JSON_MQH
|
|
|
| #define JSON_MAX_DEPTH 256
|
|
|
| //+------------------------------------------------------------------+
|
| //| Enumeración de tipos de nodos JSON |
|
| //+------------------------------------------------------------------+
|
| enum ENUM_JSON_TYPE
|
| {
|
| JSON_NULL = 0,
|
| JSON_FALSE,
|
| JSON_TRUE,
|
| JSON_NUMBER_INT,
|
| JSON_NUMBER_DBL,
|
| JSON_STRING,
|
| JSON_ARRAY,
|
| JSON_OBJECT
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Enumeración de errores |
|
| //+------------------------------------------------------------------+
|
| enum ENUM_JSON_ERROR
|
| {
|
| JSON_OK = 0,
|
| JSON_UNEXPECTED_CHARACTER,
|
| JSON_UNEXPECTED_END,
|
| JSON_INVALID_NUMBER,
|
| JSON_INVALID_STRING,
|
| JSON_INVALID_ESCAPE,
|
| JSON_INVALID_UNICODE,
|
| JSON_EXPECTED_COLON,
|
| JSON_EXPECTED_COMMA,
|
| JSON_EXPECTED_OBJECT,
|
| JSON_EXPECTED_ARRAY,
|
| JSON_MAX_DEPTH_ERROR
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Estructura interna de un nodo JSON (Slim & Cache-friendly) |
|
| //+------------------------------------------------------------------+
|
| struct JsonNode
|
| {
|
| uchar Type;
|
| uchar Flags; // Bit 0: Value tiene escapes, Bit 1: Key tiene escapes
|
| int Next; // Siguiente hermano
|
| int Child; // Primer hijo
|
| int LastChild; // Último hijo
|
| int KeyStart; // Offset en Buffer
|
| int KeyLen;
|
| int ValStart; // Offset en Buffer
|
| int ValLen;
|
| long IntVal;
|
| double DblVal;
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Forward declaration |
|
| //+------------------------------------------------------------------+
|
| class CJson;
|
|
|
| //+------------------------------------------------------------------+
|
| //| Clase Proxy para acceso encadenado (fluent interface) |
|
| //+------------------------------------------------------------------+
|
| struct CJsonValue
|
| {
|
| CJson* Doc;
|
| int Id;
|
|
|
| CJsonValue operator[](string key);
|
| CJsonValue operator[](int index);
|
|
|
| int AsInt();
|
| long AsLong();
|
| double AsDbl();
|
| string AsStr();
|
| bool AsBool();
|
| bool IsNull();
|
| bool IsValid();
|
| bool IsObject();
|
| bool IsArray();
|
| ENUM_JSON_TYPE GetType();
|
| int Size();
|
| CJsonValue Next();
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Clase Principal del Parser |
|
| //+------------------------------------------------------------------+
|
| class CJson
|
| {
|
| private:
|
| JsonNode m_nodes[];
|
| int m_nodeCount;
|
| int m_nodeCap;
|
|
|
| int m_depth;
|
| int m_pos;
|
| int m_len;
|
| int m_root;
|
|
|
| ENUM_JSON_ERROR m_error;
|
| int m_errPos;
|
|
|
| void EnsureNodeCapacity();
|
| int AllocNode(uchar type);
|
|
|
| void SkipWhitespace();
|
| bool ParseValue(int parent_id);
|
| bool ParseString(int parent_id, bool is_key, int &out_key_start, int &out_key_len, bool &out_key_escapes);
|
| bool ParseNumber(int parent_id);
|
| bool ParseLiteral(int parent_id, bool is_true, bool is_null);
|
|
|
| void SetError(ENUM_JSON_ERROR err, int pos);
|
| string DecodeString(int start, int len, bool has_escapes);
|
|
|
| public:
|
| uchar Buffer[];
|
|
|
| CJson();
|
|
|
| bool Parse();
|
| void Clear();
|
| void Reset();
|
|
|
| ENUM_JSON_ERROR GetError();
|
| string GetErrorMessage();
|
| int GetErrorPosition();
|
| int GetLine();
|
| int GetColumn();
|
| bool HasError();
|
|
|
| CJsonValue Root();
|
| CJsonValue operator[](string key);
|
| CJsonValue operator[](int index);
|
| int GetNode(int parent_id, string key);
|
| int GetNode(int parent_id, int index);
|
|
|
| int AsInt(int id);
|
| long AsLong(int id);
|
| double AsDbl(int id);
|
| string AsStr(int id);
|
| bool AsBool(int id);
|
| bool IsNull(int id);
|
| ENUM_JSON_TYPE GetType(int id);
|
| int Size(int id);
|
|
|
| string GetKey(int id);
|
| CJsonValue GetNext(int id);
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Implementación de CJson |
|
| //+------------------------------------------------------------------+
|
| CJson::CJson()
|
| {
|
| m_nodeCount = 0;
|
| m_nodeCap = 1024;
|
| ArrayResize(m_nodes, m_nodeCap);
|
|
|
| m_root = -1;
|
| m_error = JSON_OK;
|
| m_errPos = -1;
|
| }
|
|
|
| void CJson::SetError(ENUM_JSON_ERROR err, int pos)
|
| {
|
| if(m_error == JSON_OK)
|
| {
|
| m_error = err;
|
| m_errPos = pos;
|
| }
|
| }
|
|
|
| void CJson::EnsureNodeCapacity()
|
| {
|
| if(m_nodeCount >= m_nodeCap)
|
| {
|
| m_nodeCap *= 2;
|
| ArrayResize(m_nodes, m_nodeCap);
|
| }
|
| }
|
|
|
| int CJson::AllocNode(uchar type)
|
| {
|
| EnsureNodeCapacity();
|
| int id = m_nodeCount++;
|
| m_nodes[id].Type = type;
|
| m_nodes[id].Flags = 0;
|
| m_nodes[id].Next = -1;
|
| m_nodes[id].Child = -1;
|
| m_nodes[id].LastChild = -1;
|
| m_nodes[id].KeyStart = 0;
|
| m_nodes[id].KeyLen = 0;
|
| m_nodes[id].ValStart = 0;
|
| m_nodes[id].ValLen = 0;
|
| m_nodes[id].IntVal = 0;
|
| m_nodes[id].DblVal = 0.0;
|
| return id;
|
| }
|
|
|
| void CJson::SkipWhitespace()
|
| {
|
| while(m_pos < m_len)
|
| {
|
| uchar c = Buffer[m_pos];
|
| if(c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0) m_pos++;
|
| else break;
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Fase 1: Tokenización de Strings (Ultra rápida, sin decodificar) |
|
| //+------------------------------------------------------------------+
|
| bool CJson::ParseString(int parent_id, bool is_key, int &out_key_start, int &out_key_len, bool &out_key_escapes)
|
| {
|
| m_pos++; // Saltar comilla inicial
|
| int start = m_pos;
|
| bool has_escapes = false;
|
|
|
| while(m_pos < m_len)
|
| {
|
| uchar c = Buffer[m_pos];
|
|
|
| if(c == '"')
|
| {
|
| int str_len = m_pos - start;
|
| out_key_start = start;
|
| out_key_len = str_len;
|
| out_key_escapes = has_escapes;
|
|
|
| if(!is_key)
|
| {
|
| int node_id = AllocNode(JSON_STRING);
|
| m_nodes[node_id].ValStart = start;
|
| m_nodes[node_id].ValLen = str_len;
|
| m_nodes[node_id].Flags = has_escapes ? 1 : 0;
|
|
|
| if(parent_id != -1)
|
| {
|
| if(m_nodes[parent_id].Child == -1) m_nodes[parent_id].Child = node_id;
|
| else m_nodes[m_nodes[parent_id].LastChild].Next = node_id;
|
| m_nodes[parent_id].LastChild = node_id;
|
| }
|
| }
|
| m_pos++; // Saltar comilla final
|
| return true;
|
| }
|
| else if(c == '\\')
|
| {
|
| has_escapes = true;
|
| m_pos++;
|
| if(m_pos >= m_len) break;
|
|
|
| if(Buffer[m_pos] == 'u')
|
| {
|
| m_pos += 4; // Saltar los 4 hex chars
|
| }
|
| m_pos++;
|
| }
|
| else if(c >= 0x80) // Secuencia multibyte UTF-8
|
| {
|
| int seq_len = 1;
|
| if((c & 0xE0) == 0xC0) seq_len = 2;
|
| else if((c & 0xF0) == 0xE0) seq_len = 3;
|
| else if((c & 0xF8) == 0xF0) seq_len = 4;
|
|
|
| if(m_pos + seq_len > m_len) { SetError(JSON_UNEXPECTED_END, m_pos); return false; }
|
| m_pos += seq_len;
|
| }
|
| else if(c < 0x20)
|
| {
|
| SetError(JSON_INVALID_STRING, m_pos);
|
| return false;
|
| }
|
| else
|
| {
|
| m_pos++;
|
| }
|
| }
|
| SetError(JSON_UNEXPECTED_END, m_len);
|
| return false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Fase 2: Decodificación perezosa (Solo al pedir el valor) |
|
| //+------------------------------------------------------------------+
|
| string CJson::DecodeString(int start, int len, bool has_escapes)
|
| {
|
| if(!has_escapes)
|
| {
|
| return CharArrayToString(Buffer, start, len, CP_UTF8);
|
| }
|
|
|
| ushort temp[];
|
| ArrayResize(temp, len);
|
| int w = 0;
|
|
|
| for(int i = 0; i < len; )
|
| {
|
| uchar c = Buffer[start + i];
|
| if(c == '\\')
|
| {
|
| i++;
|
| if(i >= len) break;
|
| uchar esc = Buffer[start + i];
|
|
|
| switch(esc)
|
| {
|
| case '"': temp[w++] = '"'; break;
|
| case '\\': temp[w++] = '\\'; break;
|
| case '/': temp[w++] = '/'; break;
|
| case 'b': temp[w++] = 8; break;
|
| case 'f': temp[w++] = 12; break;
|
| case 'n': temp[w++] = '\n'; break;
|
| case 'r': temp[w++] = '\r'; break;
|
| case 't': temp[w++] = '\t'; break;
|
| case 'u':
|
| {
|
| if(i + 4 < len)
|
| {
|
| int cp = 0;
|
| for(int j=1; j<=4; j++)
|
| {
|
| uchar hc = Buffer[start + i + j];
|
| cp <<= 4;
|
| if(hc >= '0' && hc <= '9') cp |= (hc - '0');
|
| else if(hc >= 'a' && hc <= 'f') cp |= (hc - 'a' + 10);
|
| else if(hc >= 'A' && hc <= 'F') cp |= (hc - 'A' + 10);
|
| }
|
|
|
| if(cp >= 0xD800 && cp <= 0xDBFF)
|
| {
|
| if(i + 10 < len && Buffer[start + i + 5] == '\\' && Buffer[start + i + 6] == 'u')
|
| {
|
| int cp2 = 0;
|
| for(int j=1; j<=4; j++)
|
| {
|
| uchar hc = Buffer[start + i + 6 + j];
|
| cp2 <<= 4;
|
| if(hc >= '0' && hc <= '9') cp2 |= (hc - '0');
|
| else if(hc >= 'a' && hc <= 'f') cp2 |= (hc - 'a' + 10);
|
| else if(hc >= 'A' && hc <= 'F') cp2 |= (hc - 'A' + 10);
|
| }
|
| cp = 0x10000 + ((cp - 0xD800) << 10) + (cp2 - 0xDC00);
|
| i += 6;
|
| }
|
| }
|
|
|
| if(cp <= 0xFFFF)
|
| {
|
| temp[w++] = (ushort)cp;
|
| }
|
| else
|
| {
|
| cp -= 0x10000;
|
| temp[w++] = (ushort)(0xD800 + (cp >> 10));
|
| temp[w++] = (ushort)(0xDC00 + (cp & 0x3FF));
|
| }
|
| i += 4;
|
| }
|
| break;
|
| }
|
| }
|
| i++;
|
| }
|
| else
|
| {
|
| int seq_len = 1;
|
| if((c & 0xE0) == 0xC0) seq_len = 2;
|
| else if((c & 0xF0) == 0xE0) seq_len = 3;
|
| else if((c & 0xF8) == 0xF0) seq_len = 4;
|
|
|
| if(seq_len == 1)
|
| {
|
| temp[w++] = c;
|
| }
|
| else if(seq_len == 2 && i + 1 < len)
|
| {
|
| temp[w++] = (ushort)(((c & 0x1F) << 6) | (Buffer[start + i + 1] & 0x3F));
|
| i++;
|
| }
|
| else if(seq_len == 3 && i + 2 < len)
|
| {
|
| temp[w++] = (ushort)(((c & 0x0F) << 12) | ((Buffer[start + i + 1] & 0x3F) << 6) | (Buffer[start + i + 2] & 0x3F));
|
| i += 2;
|
| }
|
| else if(seq_len == 4 && i + 3 < len)
|
| {
|
| int cp = ((c & 0x07) << 18) | ((Buffer[start + i + 1] & 0x3F) << 12) | ((Buffer[start + i + 2] & 0x3F) << 6) | (Buffer[start + i + 3] & 0x3F);
|
| cp -= 0x10000;
|
| temp[w++] = (ushort)(0xD800 + (cp >> 10));
|
| temp[w++] = (ushort)(0xDC00 + (cp & 0x3FF));
|
| i += 3;
|
| }
|
| i++;
|
| }
|
| }
|
| return ShortArrayToString(temp, 0, w);
|
| }
|
|
|
| bool CJson::ParseNumber(int parent_id)
|
| {
|
| bool isDbl = false;
|
| long sign = 1;
|
|
|
| if(Buffer[m_pos] == '-') { sign = -1; m_pos++; }
|
|
|
| if(m_pos >= m_len || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
|
| {
|
| SetError(JSON_INVALID_NUMBER, m_pos);
|
| return false;
|
| }
|
|
|
| long int_part = 0;
|
| bool overflow = false;
|
|
|
| while(m_pos < m_len && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
| {
|
| int digit = Buffer[m_pos] - '0';
|
| if(int_part > 922337203685477580 || (int_part == 922337203685477580 && digit > 7))
|
| overflow = true;
|
| int_part = int_part * 10 + digit;
|
| m_pos++;
|
| }
|
|
|
| if(overflow) isDbl = true;
|
|
|
| double dbl_val = (double)int_part;
|
|
|
| if(m_pos < m_len && Buffer[m_pos] == '.')
|
| {
|
| isDbl = true;
|
| m_pos++;
|
| if(m_pos >= m_len || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
|
| {
|
| SetError(JSON_INVALID_NUMBER, m_pos);
|
| return false;
|
| }
|
|
|
| double scale = 0.1;
|
| while(m_pos < m_len && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
| {
|
| dbl_val += (Buffer[m_pos] - '0') * scale;
|
| scale *= 0.1;
|
| m_pos++;
|
| }
|
| }
|
|
|
| if(m_pos < m_len && (Buffer[m_pos] == 'e' || Buffer[m_pos] == 'E'))
|
| {
|
| isDbl = true;
|
| m_pos++;
|
| long exp_sign = 1;
|
| if(m_pos < m_len && (Buffer[m_pos] == '+' || Buffer[m_pos] == '-'))
|
| {
|
| if(Buffer[m_pos] == '-') exp_sign = -1;
|
| m_pos++;
|
| }
|
|
|
| if(m_pos >= m_len || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
|
| {
|
| SetError(JSON_INVALID_NUMBER, m_pos);
|
| return false;
|
| }
|
|
|
| long exp_part = 0;
|
| while(m_pos < m_len && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
| {
|
| exp_part = exp_part * 10 + (Buffer[m_pos] - '0');
|
| m_pos++;
|
| }
|
| dbl_val *= MathPow(10.0, (double)(exp_sign * exp_part));
|
| }
|
|
|
| int node_id = AllocNode(isDbl ? JSON_NUMBER_DBL : JSON_NUMBER_INT);
|
| if(isDbl)
|
| m_nodes[node_id].DblVal = sign * dbl_val;
|
| else
|
| m_nodes[node_id].IntVal = sign * int_part;
|
|
|
| if(parent_id != -1)
|
| {
|
| if(m_nodes[parent_id].Child == -1) m_nodes[parent_id].Child = node_id;
|
| else m_nodes[m_nodes[parent_id].LastChild].Next = node_id;
|
| m_nodes[parent_id].LastChild = node_id;
|
| }
|
| return true;
|
| }
|
|
|
| bool CJson::ParseLiteral(int parent_id, bool is_true, bool is_null)
|
| {
|
| int node_id = AllocNode(is_null ? JSON_NULL : (is_true ? JSON_TRUE : JSON_FALSE));
|
|
|
| if(is_null)
|
| {
|
| if(m_pos + 3 >= m_len || Buffer[m_pos+1] != 'u' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 'l')
|
| { SetError(JSON_UNEXPECTED_CHARACTER, m_pos); return false; }
|
| m_pos += 4;
|
| }
|
| else if(is_true)
|
| {
|
| if(m_pos + 3 >= m_len || Buffer[m_pos+1] != 'r' || Buffer[m_pos+2] != 'u' || Buffer[m_pos+3] != 'e')
|
| { SetError(JSON_UNEXPECTED_CHARACTER, m_pos); return false; }
|
| m_pos += 4;
|
| }
|
| else
|
| {
|
| if(m_pos + 4 >= m_len || Buffer[m_pos+1] != 'a' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 's' || Buffer[m_pos+4] != 'e')
|
| { SetError(JSON_UNEXPECTED_CHARACTER, m_pos); return false; }
|
| m_pos += 5;
|
| }
|
|
|
| if(parent_id != -1)
|
| {
|
| if(m_nodes[parent_id].Child == -1) m_nodes[parent_id].Child = node_id;
|
| else m_nodes[m_nodes[parent_id].LastChild].Next = node_id;
|
| m_nodes[parent_id].LastChild = node_id;
|
| }
|
| return true;
|
| }
|
|
|
| bool CJson::ParseValue(int parent_id)
|
| {
|
| SkipWhitespace();
|
| if(m_pos >= m_len) { SetError(JSON_UNEXPECTED_END, m_pos); return false; }
|
|
|
| uchar c = Buffer[m_pos];
|
|
|
| if(c == '{' || c == '[')
|
| {
|
| if(m_depth >= JSON_MAX_DEPTH) { SetError(JSON_MAX_DEPTH_ERROR, m_pos); return false; }
|
| m_depth++;
|
|
|
| int node_id = AllocNode(c == '{' ? JSON_OBJECT : JSON_ARRAY);
|
| if(parent_id != -1)
|
| {
|
| if(m_nodes[parent_id].Child == -1) m_nodes[parent_id].Child = node_id;
|
| else m_nodes[m_nodes[parent_id].LastChild].Next = node_id;
|
| m_nodes[parent_id].LastChild = node_id;
|
| }
|
|
|
| uchar close_char = (c == '{') ? '}' : ']';
|
| m_pos++;
|
| SkipWhitespace();
|
| if(m_pos < m_len && Buffer[m_pos] == close_char)
|
| {
|
| m_pos++;
|
| m_depth--;
|
| return true;
|
| }
|
|
|
| while(m_pos < m_len)
|
| {
|
| // --- FIX: Estado aislado en variables locales para evitar sobrescritura entre recursiones ---
|
| int current_key_start = 0;
|
| int current_key_len = 0;
|
| bool current_key_escapes = false;
|
|
|
| if(c == '{')
|
| {
|
| if(Buffer[m_pos] != '"') { SetError(JSON_EXPECTED_OBJECT, m_pos); return false; }
|
| if(!ParseString(-1, true, current_key_start, current_key_len, current_key_escapes)) return false;
|
|
|
| SkipWhitespace();
|
| if(m_pos >= m_len || Buffer[m_pos] != ':') { SetError(JSON_EXPECTED_COLON, m_pos); return false; }
|
| m_pos++;
|
| }
|
|
|
| if(!ParseValue(node_id)) return false;
|
|
|
| if(c == '{')
|
| {
|
| int child = m_nodes[node_id].LastChild;
|
| if(child != -1)
|
| {
|
| m_nodes[child].KeyStart = current_key_start;
|
| m_nodes[child].KeyLen = current_key_len;
|
| if(current_key_escapes) m_nodes[child].Flags |= 2;
|
| }
|
| }
|
|
|
| SkipWhitespace();
|
| if(m_pos >= m_len) { SetError(JSON_UNEXPECTED_END, m_pos); return false; }
|
|
|
| if(Buffer[m_pos] == ',') { m_pos++; SkipWhitespace(); continue; }
|
| if(Buffer[m_pos] == close_char) { m_pos++; m_depth--; return true; }
|
|
|
| SetError(JSON_EXPECTED_COMMA, m_pos);
|
| return false;
|
| }
|
| SetError(JSON_UNEXPECTED_END, m_pos);
|
| return false;
|
| }
|
| else if(c == '"')
|
| {
|
| int dummy1 = 0, dummy2 = 0;
|
| bool dummy3 = false;
|
| return ParseString(parent_id, false, dummy1, dummy2, dummy3);
|
| }
|
| else if(c == 't') return ParseLiteral(parent_id, true, false);
|
| else if(c == 'f') return ParseLiteral(parent_id, false, false);
|
| else if(c == 'n') return ParseLiteral(parent_id, false, true);
|
| else if(c == '-' || (c >= '0' && c <= '9')) return ParseNumber(parent_id);
|
|
|
| SetError(JSON_UNEXPECTED_CHARACTER, m_pos);
|
| return false;
|
| }
|
|
|
| bool CJson::Parse()
|
| {
|
| Reset();
|
| m_len = ArraySize(Buffer);
|
| if(m_len == 0) { SetError(JSON_UNEXPECTED_END, 0); return false; }
|
|
|
| m_pos = 0;
|
| m_depth = 0;
|
| m_root = -1;
|
|
|
| if(!ParseValue(0)) return false;
|
|
|
| m_root = m_nodes[0].Child;
|
| if(m_root == -1) { SetError(JSON_UNEXPECTED_END, 0); return false; }
|
|
|
| SkipWhitespace();
|
| if(m_pos < m_len) SetError(JSON_UNEXPECTED_CHARACTER, m_pos);
|
|
|
| return m_error == JSON_OK;
|
| }
|
|
|
| void CJson::Reset()
|
| {
|
| m_nodeCount = 0;
|
| m_root = -1;
|
| m_error = JSON_OK;
|
| m_errPos = -1;
|
| m_depth = 0;
|
|
|
| AllocNode(JSON_NULL); // Nodo 0 dummy
|
| }
|
|
|
| void CJson::Clear()
|
| {
|
| ArrayFree(m_nodes);
|
| ArrayFree(Buffer);
|
|
|
| m_nodeCap = 1024;
|
| ArrayResize(m_nodes, m_nodeCap);
|
|
|
| Reset();
|
| }
|
|
|
| ENUM_JSON_ERROR CJson::GetError() { return m_error; }
|
| bool CJson::HasError() { return m_error != JSON_OK; }
|
| int CJson::GetErrorPosition() { return m_errPos; }
|
| string CJson::GetErrorMessage()
|
| {
|
| switch(m_error)
|
| {
|
| case JSON_OK: return "OK";
|
| case JSON_UNEXPECTED_CHARACTER: return "Unexpected character";
|
| case JSON_UNEXPECTED_END: return "Unexpected end of buffer";
|
| case JSON_INVALID_NUMBER: return "Invalid number format";
|
| case JSON_INVALID_STRING: return "Invalid string (control character)";
|
| case JSON_INVALID_ESCAPE: return "Invalid escape sequence";
|
| case JSON_INVALID_UNICODE: return "Invalid unicode sequence";
|
| case JSON_EXPECTED_COLON: return "Expected ':' after key";
|
| case JSON_EXPECTED_COMMA: return "Expected ',' or '}' or ']'";
|
| case JSON_EXPECTED_OBJECT: return "Expected '\"' for object key";
|
| case JSON_EXPECTED_ARRAY: return "Expected '['";
|
| case JSON_MAX_DEPTH_ERROR: return "Max depth exceeded";
|
| }
|
| return "Unknown error";
|
| }
|
| int CJson::GetLine()
|
| {
|
| if(m_errPos <= 0) return 1;
|
| int line = 1;
|
| for(int i=0; i<m_errPos; i++) if(Buffer[i] == '\n') line++;
|
| return line;
|
| }
|
| int CJson::GetColumn()
|
| {
|
| if(m_errPos <= 0) return 1;
|
| int col = 1;
|
| for(int i=m_errPos-1; i>=0; i--)
|
| {
|
| if(Buffer[i] == '\n') break;
|
| col++;
|
| }
|
| return col;
|
| }
|
|
|
| CJsonValue CJson::Root()
|
| {
|
| CJsonValue v;
|
| v.Doc = GetPointer(this);
|
| v.Id = m_root;
|
| return v;
|
| }
|
|
|
| int CJson::GetNode(int parent_id, string key)
|
| {
|
| if(parent_id == -1 || m_nodes[parent_id].Type != JSON_OBJECT) return -1;
|
| int child = m_nodes[parent_id].Child;
|
| while(child != -1)
|
| {
|
| string node_key = GetKey(child);
|
| if(node_key == key) return child;
|
| child = m_nodes[child].Next;
|
| }
|
| return -1;
|
| }
|
|
|
| int CJson::GetNode(int parent_id, int index)
|
| {
|
| if(parent_id == -1 || m_nodes[parent_id].Type != JSON_ARRAY) return -1;
|
| if(index < 0) return -1;
|
| int child = m_nodes[parent_id].Child;
|
| while(child != -1 && index > 0)
|
| {
|
| child = m_nodes[child].Next;
|
| index--;
|
| }
|
| return child;
|
| }
|
|
|
| CJsonValue CJson::operator[](string key)
|
| {
|
| CJsonValue v;
|
| v.Doc = GetPointer(this);
|
| v.Id = GetNode(m_root, key);
|
| return v;
|
| }
|
|
|
| CJsonValue CJson::operator[](int index)
|
| {
|
| CJsonValue v;
|
| v.Doc = GetPointer(this);
|
| v.Id = GetNode(m_root, index);
|
| return v;
|
| }
|
|
|
| int CJson::AsInt(int id)
|
| {
|
| if(id == -1) return 0;
|
| if(m_nodes[id].Type == JSON_NUMBER_INT) return (int)m_nodes[id].IntVal;
|
| if(m_nodes[id].Type == JSON_NUMBER_DBL) return (int)m_nodes[id].DblVal;
|
| return 0;
|
| }
|
| long CJson::AsLong(int id)
|
| {
|
| if(id == -1) return 0;
|
| if(m_nodes[id].Type == JSON_NUMBER_INT) return m_nodes[id].IntVal;
|
| if(m_nodes[id].Type == JSON_NUMBER_DBL) return (long)m_nodes[id].DblVal;
|
| return 0;
|
| }
|
| double CJson::AsDbl(int id)
|
| {
|
| if(id == -1) return 0.0;
|
| if(m_nodes[id].Type == JSON_NUMBER_INT) return (double)m_nodes[id].IntVal;
|
| if(m_nodes[id].Type == JSON_NUMBER_DBL) return m_nodes[id].DblVal;
|
| return 0.0;
|
| }
|
| string CJson::AsStr(int id)
|
| {
|
| if(id == -1 || m_nodes[id].Type != JSON_STRING) return "";
|
| return DecodeString(m_nodes[id].ValStart, m_nodes[id].ValLen, (m_nodes[id].Flags & 1) != 0);
|
| }
|
| bool CJson::AsBool(int id)
|
| {
|
| if(id == -1) return false;
|
| return m_nodes[id].Type == JSON_TRUE;
|
| }
|
| bool CJson::IsNull(int id)
|
| {
|
| if(id == -1) return false;
|
| return m_nodes[id].Type == JSON_NULL;
|
| }
|
| ENUM_JSON_TYPE CJson::GetType(int id)
|
| {
|
| if(id == -1) return JSON_NULL;
|
| return (ENUM_JSON_TYPE)m_nodes[id].Type;
|
| }
|
| int CJson::Size(int id)
|
| {
|
| if(id == -1) return 0;
|
| if(m_nodes[id].Type != JSON_ARRAY && m_nodes[id].Type != JSON_OBJECT) return 0;
|
| int count = 0;
|
| int child = m_nodes[id].Child;
|
| while(child != -1) { count++; child = m_nodes[child].Next; }
|
| return count;
|
| }
|
| string CJson::GetKey(int id)
|
| {
|
| if(id == -1 || m_nodes[id].KeyLen == 0) return "";
|
| return DecodeString(m_nodes[id].KeyStart, m_nodes[id].KeyLen, (m_nodes[id].Flags & 2) != 0);
|
| }
|
| CJsonValue CJson::GetNext(int id)
|
| {
|
| CJsonValue v;
|
| v.Doc = GetPointer(this);
|
| v.Id = (id == -1) ? -1 : m_nodes[id].Next;
|
| return v;
|
| }
|
|
|
| CJsonValue CJsonValue::operator[](string key)
|
| {
|
| CJsonValue v;
|
| v.Doc = Doc;
|
| v.Id = Doc.GetNode(Id, key);
|
| return v;
|
| }
|
| CJsonValue CJsonValue::operator[](int index)
|
| {
|
| CJsonValue v;
|
| v.Doc = Doc;
|
| v.Id = Doc.GetNode(Id, index);
|
| return v;
|
| }
|
| int CJsonValue::AsInt() { return Doc.AsInt(Id); }
|
| long CJsonValue::AsLong() { return Doc.AsLong(Id); }
|
| double CJsonValue::AsDbl() { return Doc.AsDbl(Id); }
|
| string CJsonValue::AsStr() { return Doc.AsStr(Id); }
|
| bool CJsonValue::AsBool() { return Doc.AsBool(Id); }
|
| bool CJsonValue::IsNull() { return Doc.IsNull(Id); }
|
| bool CJsonValue::IsValid() { return (Id != -1); }
|
| bool CJsonValue::IsObject() { return (Id != -1 && Doc.GetType(Id) == JSON_OBJECT); }
|
| bool CJsonValue::IsArray() { return (Id != -1 && Doc.GetType(Id) == JSON_ARRAY); }
|
| ENUM_JSON_TYPE CJsonValue::GetType() { return Doc.GetType(Id); }
|
| int CJsonValue::Size() { return Doc.Size(Id); }
|
| CJsonValue CJsonValue::Next() { return Doc.GetNext(Id); }
|
|
|
| #endif // JSON_MQH
|