779 righe
Nessun fine linea
25 KiB
MQL5
779 righe
Nessun fine linea
25 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 Turbo
|
|
--
|
|
Ahora mismo voy a por el primer intentento
|
|
92 errores abismal... me da que los modelos chinos en MQL5 son pesimos
|
|
peores que chat gpt.. volvamos a iterar...
|
|
'CJson' - unexpected token, probably type is missing? MinMax.mqh 84 5
|
|
'*' - semicolon expected MinMax.mqh 84 17
|
|
declaration without type MinMax.mqh 105 15
|
|
comma expected MinMax.mqh 105 21
|
|
.... etc..
|
|
Se confunden mucho con c++ cosa qeu a este punto claude ya raramente le pase y a gpt tambien
|
|
pero los modelos chinos siguen con esa piedra de confundirmse demasiado con c++
|
|
Claude y gpt si se confuncnde ero es raro..
|
|
--
|
|
Bueno nada me da que no sabe mucho de mql5 sigue con eso
|
|
f (len == (uint)k_len)
|
|
{
|
|
bool match = true;
|
|
for (uint i = 0; i < len; i++)
|
|
{
|
|
if (m_json->Buffer[start + i] != key_bytes[i])
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match)
|
|
return CJsonNode(m_json, m_json->Tokens[child].next);
|
|
}
|
|
child = m_json->Tokens[m_json->Tokens[child].next].next;
|
|
}
|
|
|
|
Creo qeu aqui le dejo se supone que yo como usuario no se mucho MQL5
|
|
y es la ia que me debe de guiar a mi.... yo solo doy ideas maqueto todo y la ia lo pasa a codigo..
|
|
Por ahora creo que GLM se queda corto.. me reuqca a gpt en 2024.. cuando
|
|
tenia ese tipo de errores abismalmes
|
|
Bueno dejare el codigo igual que me dio si es que lo quieren revisar..
|
|
No creo que lo testee ya ni vale la pena..
|
|
*/
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------+
|
|
//| Json.mqh |
|
|
//| High Performance JSON Parser |
|
|
//| 100% Pure MQL5 (No DLL)|
|
|
//+------------------------------------------------------------------+
|
|
#property strict
|
|
|
|
//--- Tipos de nodos JSON
|
|
enum JsonType
|
|
{
|
|
JSON_UNDEFINED = 0,
|
|
JSON_OBJECT = 1,
|
|
JSON_ARRAY = 2,
|
|
JSON_STRING = 3,
|
|
JSON_NUMBER = 4,
|
|
JSON_TRUE = 5,
|
|
JSON_FALSE = 6,
|
|
JSON_NULL = 7
|
|
};
|
|
|
|
//--- Códigos de error
|
|
enum JsonError
|
|
{
|
|
JSON_OK = 0,
|
|
JSON_UNEXPECTED_CHARACTER = 1,
|
|
JSON_UNEXPECTED_END = 2,
|
|
JSON_INVALID_NUMBER = 3,
|
|
JSON_INVALID_STRING = 4,
|
|
JSON_INVALID_ESCAPE = 5,
|
|
JSON_INVALID_UNICODE = 6,
|
|
JSON_EXPECTED_COLON = 7,
|
|
JSON_EXPECTED_OBJECT = 8,
|
|
JSON_EXPECTED_ARRAY = 9,
|
|
JSON_MAX_DEPTH = 10,
|
|
JSON_TRAILING_GARBAGE = 11
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Estructura de Token (16 bytes) - Altamente cacheable |
|
|
//+------------------------------------------------------------------+
|
|
struct JsonToken
|
|
{
|
|
ushort type; // JsonType
|
|
uint start; // Índice en Buffer[]
|
|
uint end; // Índice en Buffer[]
|
|
uint next; // Índice del siguiente hermano (0 si no hay)
|
|
uint first_child; // Índice del primer hijo (0 si es hoja)
|
|
uint size; // Número de elementos (hijos) en objetos y arrays
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Frame para la pila manual de análisis |
|
|
//+------------------------------------------------------------------+
|
|
struct StackFrame
|
|
{
|
|
uint token_idx;
|
|
uint last_child_idx;
|
|
bool is_object;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clase Nodo (Proxy para acceso a datos) |
|
|
//+------------------------------------------------------------------+
|
|
class CJsonNode
|
|
{
|
|
private:
|
|
CJson *m_json;
|
|
uint m_idx;
|
|
JsonType m_type;
|
|
|
|
// Convierte 4 chars hexadecimales a un ushort
|
|
ushort HexToShort(const uchar &buf[], uint pos) const
|
|
{
|
|
ushort val = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
uchar c = buf[pos + i];
|
|
val <<= 4;
|
|
if (c >= '0' && c <= '9') val += (ushort)(c - '0');
|
|
else if (c >= 'a' && c <= 'f') val += (ushort)(10 + c - 'a');
|
|
else if (c >= 'A' && c <= 'F') val += (ushort)(10 + c - 'A');
|
|
}
|
|
return val;
|
|
}
|
|
|
|
public:
|
|
CJsonNode() : m_json(NULL), m_idx(0), m_type(JSON_UNDEFINED) {}
|
|
CJsonNode(CJson *json, uint idx);
|
|
|
|
bool IsValid() const { return m_idx > 0 && m_type != JSON_UNDEFINED; }
|
|
JsonType GetType() const { return m_type; }
|
|
bool IsObject() const { return m_type == JSON_OBJECT; }
|
|
bool IsArray() const { return m_type == JSON_ARRAY; }
|
|
bool IsString() const { return m_type == JSON_STRING; }
|
|
bool IsNumber() const { return m_type == JSON_NUMBER; }
|
|
bool IsBool() const { return m_type == JSON_TRUE || m_type == JSON_FALSE; }
|
|
bool IsNull() const { return m_type == JSON_NULL; }
|
|
uint Size() const { return IsValid() ? m_json.m_tokens[m_idx].size : 0; }
|
|
uint Count() const { return Size(); }
|
|
|
|
string AsString() const;
|
|
long AsLong() const;
|
|
double AsDouble() const;
|
|
bool AsBool() const;
|
|
int AsInt() const { return (int)AsLong(); }
|
|
|
|
CJsonNode operator[](const string key) const;
|
|
CJsonNode operator[](const uint index) const;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clase Principal del Parser |
|
|
//+------------------------------------------------------------------+
|
|
class CJson
|
|
{
|
|
private:
|
|
JsonToken m_tokens[];
|
|
StackFrame m_stack[];
|
|
int m_stack_top;
|
|
uint m_token_count;
|
|
uint m_last_child_idx;
|
|
|
|
JsonError m_error;
|
|
uint m_error_pos;
|
|
|
|
void SkipWhitespace(uint &pos);
|
|
void ParseString(uint &pos);
|
|
void ParseNumber(uint &pos);
|
|
bool MatchLiteral(uint &pos, const string literal, int len);
|
|
void ParseValue(uint &pos);
|
|
|
|
uint AllocToken(ushort type, uint start, uint end);
|
|
|
|
public:
|
|
uchar Buffer[]; // Expuesto público para Zero-Copy
|
|
|
|
CJson();
|
|
~CJson();
|
|
|
|
bool Parse();
|
|
void Reset();
|
|
void Clear();
|
|
|
|
// Información de errores
|
|
JsonError GetError() const { return m_error; }
|
|
uint GetErrorPosition() const { return m_error_pos; }
|
|
string GetErrorMessage() const;
|
|
int GetLine();
|
|
int GetColumn();
|
|
bool HasError() const { return m_error != JSON_OK; }
|
|
|
|
// Acceso a la raíz
|
|
CJsonNode GetRoot();
|
|
CJsonNode operator[](const string key) { return GetRoot()[key]; }
|
|
CJsonNode operator[](const uint index) { return GetRoot()[index]; }
|
|
|
|
// Utilidades rápidas de la raíz
|
|
bool IsObject() const { return m_token_count > 0 && m_tokens[0].type == JSON_OBJECT; }
|
|
bool IsArray() const { return m_token_count > 0 && m_tokens[0].type == JSON_ARRAY; }
|
|
uint Size() const { return m_token_count > 0 ? m_tokens[0].size : 0; }
|
|
JsonType GetType() const { return m_token_count > 0 ? (JsonType)m_tokens[0].type : JSON_UNDEFINED; }
|
|
|
|
// Amigas
|
|
friend class CJsonNode;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Implementación CJsonNode |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode::CJsonNode(CJson *json, uint idx)
|
|
{
|
|
m_json = json;
|
|
m_idx = idx;
|
|
if (m_json != NULL && m_idx > 0 && m_idx < m_json.m_token_count)
|
|
m_type = (JsonType)m_json.m_tokens[m_idx].type;
|
|
else
|
|
m_type = JSON_UNDEFINED;
|
|
}
|
|
|
|
string CJsonNode::AsString() const
|
|
{
|
|
if (!IsValid() || m_type != JSON_STRING) return "";
|
|
|
|
uint start = m_json.m_tokens[m_idx].start + 1;
|
|
uint end = m_json.m_tokens[m_idx].end - 1;
|
|
uint len = end - start;
|
|
|
|
if (len == 0) return "";
|
|
|
|
// Optimización rápida: Si no hay barras invertidas, copiar directo sin procesar
|
|
bool has_escape = false;
|
|
for (uint i = start; i < end; i++)
|
|
{
|
|
if (m_json.Buffer[i] == '\\') { has_escape = true; break; }
|
|
}
|
|
|
|
if (!has_escape)
|
|
return CharArrayToString(m_json.Buffer, start, len, CP_UTF8);
|
|
|
|
// Procesamiento de escapes (Obligatorio para estándar JSON)
|
|
uchar tmp[];
|
|
ArrayResize(tmp, len);
|
|
uint j = 0;
|
|
|
|
for (uint i = start; i < end; i++)
|
|
{
|
|
uchar c = m_json.Buffer[i];
|
|
if (c == '\\')
|
|
{
|
|
i++;
|
|
uchar esc = m_json.Buffer[i];
|
|
switch(esc)
|
|
{
|
|
case '"': tmp[j++] = '"'; break;
|
|
case '\\': tmp[j++] = '\\'; break;
|
|
case '/': tmp[j++] = '/'; break;
|
|
case 'b': tmp[j++] = 0x08; break;
|
|
case 'f': tmp[j++] = 0x0C; break;
|
|
case 'n': tmp[j++] = 0x0A; break;
|
|
case 'r': tmp[j++] = 0x0D; break;
|
|
case 't': tmp[j++] = 0x09; break;
|
|
case 'u':
|
|
ushort cp = HexToShort(m_json.Buffer, i + 1);
|
|
i += 4;
|
|
// Soporte básico para Surrogate Pairs UTF-16
|
|
if (cp >= 0xD800 && cp <= 0xDBFF && i + 2 < end && m_json.Buffer[i] == '\\' && m_json.Buffer[i+1] == 'u')
|
|
{
|
|
ushort cp2 = HexToShort(m_json.Buffer, i + 2);
|
|
if (cp2 >= 0xDC00 && cp2 <= 0xDFFF)
|
|
{
|
|
cp = 0x10000 + (((cp - 0xD800) << 10) | (cp2 - 0xDC00));
|
|
i += 6;
|
|
}
|
|
}
|
|
// Codificar a UTF-8
|
|
if (cp < 0x80) {
|
|
tmp[j++] = (uchar)cp;
|
|
} else if (cp < 0x800) {
|
|
tmp[j++] = (uchar)(0xC0 | (cp >> 6));
|
|
tmp[j++] = (uchar)(0x80 | (cp & 0x3F));
|
|
} else if (cp <= 0xFFFF) {
|
|
tmp[j++] = (uchar)(0xE0 | (cp >> 12));
|
|
tmp[j++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
|
|
tmp[j++] = (uchar)(0x80 | (cp & 0x3F));
|
|
} else {
|
|
tmp[j++] = (uchar)(0xF0 | (cp >> 18));
|
|
tmp[j++] = (uchar)(0x80 | ((cp >> 12) & 0x3F));
|
|
tmp[j++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
|
|
tmp[j++] = (uchar)(0x80 | (cp & 0x3F));
|
|
}
|
|
break;
|
|
default: tmp[j++] = esc; break; // No debería llegar aquí si el parser es estricto
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tmp[j++] = c;
|
|
}
|
|
}
|
|
ArrayResize(tmp, j);
|
|
return CharArrayToString(tmp, 0, j, CP_UTF8);
|
|
}
|
|
|
|
long CJsonNode::AsLong() const
|
|
{
|
|
if (!IsValid()) return 0;
|
|
if (m_type == JSON_TRUE) return 1;
|
|
if (m_type == JSON_FALSE || m_type == JSON_NULL) return 0;
|
|
|
|
uint len = m_json.m_tokens[m_idx].end - m_json.m_tokens[m_idx].start;
|
|
string s = CharArrayToString(m_json.Buffer, m_json.m_tokens[m_idx].start, len, CP_UTF8);
|
|
return StringToInteger(s);
|
|
}
|
|
|
|
double CJsonNode::AsDouble() const
|
|
{
|
|
if (!IsValid()) return 0.0;
|
|
if (m_type == JSON_TRUE) return 1.0;
|
|
if (m_type == JSON_FALSE || m_type == JSON_NULL) return 0.0;
|
|
|
|
uint len = m_json.m_tokens[m_idx].end - m_json.m_tokens[m_idx].start;
|
|
string s = CharArrayToString(m_json.Buffer, m_json.m_tokens[m_idx].start, len, CP_UTF8);
|
|
return StringToDouble(s);
|
|
}
|
|
|
|
bool CJsonNode::AsBool() const
|
|
{
|
|
if (!IsValid()) return false;
|
|
if (m_type == JSON_TRUE) return true;
|
|
if (m_type == JSON_FALSE) return false;
|
|
if (m_type == JSON_NUMBER) return AsDouble() != 0.0;
|
|
if (m_type == JSON_STRING) return m_json.m_tokens[m_idx].end - m_json.m_tokens[m_idx].start > 2; // String no vacío
|
|
return false;
|
|
}
|
|
|
|
CJsonNode CJsonNode::operator[](const string key) const
|
|
{
|
|
if (!IsValid() || m_type != JSON_OBJECT) return CJsonNode(m_json, 0);
|
|
|
|
uint child = m_json.m_tokens[m_idx].first_child;
|
|
|
|
// Convertir clave a UTF-8 bytes una sola vez para comparación ultra-rápida
|
|
uchar key_bytes[];
|
|
StringToCharArray(key, key_bytes, 0, WHOLE_ARRAY, CP_UTF8);
|
|
int k_len = ArraySize(key_bytes) - 1; // StringToCharArray añade null terminator
|
|
|
|
while (child != 0)
|
|
{
|
|
uint start = m_json.m_tokens[child].start + 1;
|
|
uint end = m_json.m_tokens[child].end - 1;
|
|
uint len = end - start;
|
|
|
|
if (len == (uint)k_len)
|
|
{
|
|
bool match = true;
|
|
for (uint i = 0; i < len; i++)
|
|
{
|
|
if (m_json.Buffer[start + i] != key_bytes[i])
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match)
|
|
return CJsonNode(m_json, m_json.m_tokens[child].next); // Retorna el valor
|
|
}
|
|
// Saltar al siguiente par (Valor.next = siguiente Clave)
|
|
child = m_json.m_tokens[m_json.m_tokens[child].next].next;
|
|
}
|
|
return CJsonNode(m_json, 0); // No encontrado
|
|
}
|
|
|
|
CJsonNode CJsonNode::operator[](const uint index) const
|
|
{
|
|
if (!IsValid() || m_type != JSON_ARRAY) return CJsonNode(m_json, 0);
|
|
|
|
uint child = m_json.m_tokens[m_idx].first_child;
|
|
uint count = 0;
|
|
|
|
while (child != 0)
|
|
{
|
|
if (count == index) return CJsonNode(m_json, child);
|
|
child = m_json.m_tokens[child].next;
|
|
count++;
|
|
}
|
|
return CJsonNode(m_json, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Implementación CJson |
|
|
//+------------------------------------------------------------------+
|
|
CJson::CJson()
|
|
{
|
|
m_error = JSON_OK;
|
|
m_error_pos = 0;
|
|
m_token_count = 0;
|
|
m_stack_top = 0;
|
|
m_last_child_idx = 0;
|
|
|
|
ArrayResize(m_tokens, 1024);
|
|
ArrayResize(m_stack, 64);
|
|
ArrayResize(Buffer, 0);
|
|
}
|
|
|
|
CJson::~CJson()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void CJson::Reset()
|
|
{
|
|
// No liberamos memoria, solo reiniciamos contadores para reutilizar buffers (Arena style)
|
|
m_token_count = 0;
|
|
m_stack_top = 0;
|
|
m_last_child_idx = 0;
|
|
m_error = JSON_OK;
|
|
m_error_pos = 0;
|
|
}
|
|
|
|
void CJson::Clear()
|
|
{
|
|
ArrayFree(m_tokens);
|
|
ArrayFree(m_stack);
|
|
ArrayFree(Buffer);
|
|
Reset();
|
|
}
|
|
|
|
void CJson::SkipWhitespace(uint &pos)
|
|
{
|
|
uint len = ArraySize(Buffer);
|
|
while (pos < len)
|
|
{
|
|
uchar c = Buffer[pos];
|
|
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
bool CJson::MatchLiteral(uint &pos, const string literal, int len)
|
|
{
|
|
uint buf_len = ArraySize(Buffer);
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
if (pos >= buf_len || Buffer[pos++] != (uchar)StringGetCharacter(literal, i))
|
|
{
|
|
m_error = JSON_UNEXPECTED_CHARACTER;
|
|
m_error_pos = pos - 1;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CJson::ParseString(uint &pos)
|
|
{
|
|
uint len = ArraySize(Buffer);
|
|
pos++; // Skip opening quote
|
|
while (pos < len)
|
|
{
|
|
uchar c = Buffer[pos++];
|
|
if (c == '"') return; // Fin de string encontrado
|
|
|
|
if (c == '\\')
|
|
{
|
|
if (pos >= len) { m_error = JSON_INVALID_STRING; m_error_pos = pos; return; }
|
|
uchar esc = Buffer[pos++];
|
|
if (esc == 'u')
|
|
{
|
|
if (pos + 3 >= len) { m_error = JSON_INVALID_UNICODE; m_error_pos = pos; return; }
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
uchar h = Buffer[pos++];
|
|
if (!((h >= '0' && h <= '9') || (h >= 'a' && h <= 'f') || (h >= 'A' && h <= 'F')))
|
|
{
|
|
m_error = JSON_INVALID_UNICODE;
|
|
m_error_pos = pos - 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (esc != '"' && esc != '\\' && esc != '/' && esc != 'b' && esc != 'f' && esc != 'n' && esc != 'r' && esc != 't')
|
|
{
|
|
m_error = JSON_INVALID_ESCAPE;
|
|
m_error_pos = pos - 1;
|
|
return;
|
|
}
|
|
}
|
|
// Nota: Se omiten validaciones de caracteres de control (0x00-0x1F) para maximizar velocidad.
|
|
}
|
|
m_error = JSON_INVALID_STRING;
|
|
m_error_pos = pos;
|
|
}
|
|
|
|
void CJson::ParseNumber(uint &pos)
|
|
{
|
|
uint len = ArraySize(Buffer);
|
|
if (Buffer[pos] == '-') pos++;
|
|
|
|
if (pos < len && Buffer[pos] == '0')
|
|
{
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
if (pos >= len || Buffer[pos] < '1' || Buffer[pos] > '9') { m_error = JSON_INVALID_NUMBER; m_error_pos = pos; return; }
|
|
while (pos < len && Buffer[pos] >= '0' && Buffer[pos] <= '9') pos++;
|
|
}
|
|
|
|
if (pos < len && Buffer[pos] == '.')
|
|
{
|
|
pos++;
|
|
if (pos >= len || Buffer[pos] < '0' || Buffer[pos] > '9') { m_error = JSON_INVALID_NUMBER; m_error_pos = pos; return; }
|
|
while (pos < len && Buffer[pos] >= '0' && Buffer[pos] <= '9') pos++;
|
|
}
|
|
|
|
if (pos < len && (Buffer[pos] == 'e' || Buffer[pos] == 'E'))
|
|
{
|
|
pos++;
|
|
if (pos < len && (Buffer[pos] == '+' || Buffer[pos] == '-')) pos++;
|
|
if (pos >= len || Buffer[pos] < '0' || Buffer[pos] > '9') { m_error = JSON_INVALID_NUMBER; m_error_pos = pos; return; }
|
|
while (pos < len && Buffer[pos] >= '0' && Buffer[pos] <= '9') pos++;
|
|
}
|
|
}
|
|
|
|
uint CJson::AllocToken(ushort type, uint start, uint end)
|
|
{
|
|
// Pre-asignación dinámica amortizada O(1)
|
|
if (m_token_count >= ArraySize(m_tokens))
|
|
ArrayResize(m_tokens, ArraySize(m_tokens) * 2);
|
|
|
|
uint idx = m_token_count++;
|
|
m_tokens[idx].type = type;
|
|
m_tokens[idx].start = start;
|
|
m_tokens[idx].end = end;
|
|
m_tokens[idx].next = 0;
|
|
m_tokens[idx].first_child = 0;
|
|
m_tokens[idx].size = 0;
|
|
|
|
if (m_stack_top > 0)
|
|
{
|
|
StackFrame &frame = m_stack[m_stack_top - 1];
|
|
if (m_tokens[frame.token_idx].first_child == 0)
|
|
m_tokens[frame.token_idx].first_child = idx;
|
|
else
|
|
m_tokens[frame.last_child_idx].next = idx;
|
|
|
|
frame.last_child_idx = idx;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
void CJson::ParseValue(uint &pos)
|
|
{
|
|
uchar c = Buffer[pos];
|
|
uint start = pos;
|
|
|
|
if (c == '"')
|
|
{
|
|
ParseString(pos);
|
|
if (m_error != JSON_OK) return;
|
|
AllocToken(JSON_STRING, start, pos);
|
|
}
|
|
else if (c == '{')
|
|
{
|
|
uint tok_idx = AllocToken(JSON_OBJECT, start, 0);
|
|
pos++;
|
|
if (m_stack_top >= ArraySize(m_stack)) ArrayResize(m_stack, m_stack_top * 2);
|
|
|
|
m_stack[m_stack_top].token_idx = tok_idx;
|
|
m_stack[m_stack_top].last_child_idx = 0;
|
|
m_stack[m_stack_top].is_object = true;
|
|
m_stack_top++;
|
|
}
|
|
else if (c == '[')
|
|
{
|
|
uint tok_idx = AllocToken(JSON_ARRAY, start, 0);
|
|
pos++;
|
|
if (m_stack_top >= ArraySize(m_stack)) ArrayResize(m_stack, m_stack_top * 2);
|
|
|
|
m_stack[m_stack_top].token_idx = tok_idx;
|
|
m_stack[m_stack_top].last_child_idx = 0;
|
|
m_stack[m_stack_top].is_object = false;
|
|
m_stack_top++;
|
|
}
|
|
else if (c == 't')
|
|
{
|
|
if (!MatchLiteral(pos, "true", 4)) return;
|
|
AllocToken(JSON_TRUE, start, pos);
|
|
}
|
|
else if (c == 'f')
|
|
{
|
|
if (!MatchLiteral(pos, "false", 5)) return;
|
|
AllocToken(JSON_FALSE, start, pos);
|
|
}
|
|
else if (c == 'n')
|
|
{
|
|
if (!MatchLiteral(pos, "null", 4)) return;
|
|
AllocToken(JSON_NULL, start, pos);
|
|
}
|
|
else if (c == '-' || (c >= '0' && c <= '9'))
|
|
{
|
|
ParseNumber(pos);
|
|
if (m_error != JSON_OK) return;
|
|
AllocToken(JSON_NUMBER, start, pos);
|
|
}
|
|
else
|
|
{
|
|
m_error = JSON_UNEXPECTED_CHARACTER;
|
|
m_error_pos = pos;
|
|
}
|
|
}
|
|
|
|
bool CJson::Parse()
|
|
{
|
|
Reset();
|
|
uint len = ArraySize(Buffer);
|
|
if (len == 0) { m_error = JSON_UNEXPECTED_END; m_error_pos = 0; return false; }
|
|
|
|
uint pos = 0;
|
|
|
|
// Saltar BOM (Byte Order Mark) UTF-8 si existe
|
|
if (len >= 3 && Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) pos = 3;
|
|
|
|
SkipWhitespace(pos);
|
|
if (pos >= len) { m_error = JSON_UNEXPECTED_END; m_error_pos = pos; return false; }
|
|
|
|
uchar c = Buffer[pos];
|
|
if (c != '{' && c != '[')
|
|
{
|
|
m_error = (c == '"' || c == '-' || (c >= '0' && c <= '9') || c == 't' || c == 'f' || c == 'n') ? JSON_EXPECTED_OBJECT : JSON_UNEXPECTED_CHARACTER;
|
|
m_error_pos = pos;
|
|
return false;
|
|
}
|
|
|
|
ParseValue(pos);
|
|
if (m_error != JSON_OK) return false;
|
|
|
|
bool expect_key = (m_tokens[0].type == JSON_OBJECT);
|
|
|
|
while (m_stack_top > 0 && m_error == JSON_OK)
|
|
{
|
|
SkipWhitespace(pos);
|
|
if (pos >= len) { m_error = JSON_UNEXPECTED_END; m_error_pos = pos; break; }
|
|
|
|
c = Buffer[pos];
|
|
StackFrame &frame = m_stack[m_stack_top - 1];
|
|
|
|
if (frame.is_object)
|
|
{
|
|
if (c == '}')
|
|
{
|
|
m_tokens[frame.token_idx].end = pos + 1;
|
|
m_stack_top--;
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (expect_key)
|
|
{
|
|
if (c != '"') { m_error = JSON_EXPECTED_OBJECT; m_error_pos = pos; break; }
|
|
uint start = pos;
|
|
ParseString(pos);
|
|
if (m_error != JSON_OK) break;
|
|
AllocToken(JSON_STRING, start, pos);
|
|
expect_key = false;
|
|
}
|
|
else
|
|
{
|
|
if (c != ':') { m_error = JSON_EXPECTED_COLON; m_error_pos = pos; break; }
|
|
pos++;
|
|
SkipWhitespace(pos);
|
|
if (pos >= len) { m_error = JSON_UNEXPECTED_END; m_error_pos = pos; break; }
|
|
|
|
ParseValue(pos);
|
|
if (m_error != JSON_OK) break;
|
|
|
|
m_tokens[frame.token_idx].size++;
|
|
expect_key = true;
|
|
|
|
SkipWhitespace(pos);
|
|
if (pos < len && Buffer[pos] == ',') pos++;
|
|
}
|
|
}
|
|
else // Array
|
|
{
|
|
if (c == ']')
|
|
{
|
|
m_tokens[frame.token_idx].end = pos + 1;
|
|
m_stack_top--;
|
|
pos++;
|
|
continue;
|
|
}
|
|
ParseValue(pos);
|
|
if (m_error != JSON_OK) break;
|
|
|
|
m_tokens[frame.token_idx].size++;
|
|
|
|
SkipWhitespace(pos);
|
|
if (pos < len && Buffer[pos] == ',') pos++;
|
|
}
|
|
}
|
|
|
|
if (m_error == JSON_OK)
|
|
{
|
|
SkipWhitespace(pos);
|
|
if (pos < len) { m_error = JSON_TRAILING_GARBAGE; m_error_pos = pos; }
|
|
}
|
|
|
|
return m_error == JSON_OK;
|
|
}
|
|
|
|
CJsonNode CJson::GetRoot()
|
|
{
|
|
if (m_token_count == 0) return CJsonNode(this, 0);
|
|
return CJsonNode(this, 0);
|
|
}
|
|
|
|
string CJson::GetErrorMessage() const
|
|
{
|
|
switch(m_error)
|
|
{
|
|
case JSON_OK: return "No error";
|
|
case JSON_UNEXPECTED_CHARACTER:return "Unexpected character";
|
|
case JSON_UNEXPECTED_END: return "Unexpected end of data";
|
|
case JSON_INVALID_NUMBER: return "Invalid number format";
|
|
case JSON_INVALID_STRING: return "Invalid string format";
|
|
case JSON_INVALID_ESCAPE: return "Invalid escape sequence";
|
|
case JSON_INVALID_UNICODE: return "Invalid Unicode escape sequence";
|
|
case JSON_EXPECTED_COLON: return "Expected colon ':'";
|
|
case JSON_EXPECTED_OBJECT: return "Expected object '{' or '['";
|
|
case JSON_EXPECTED_ARRAY: return "Expected array '['";
|
|
case JSON_MAX_DEPTH: return "Maximum nesting depth exceeded";
|
|
case JSON_TRAILING_GARBAGE: return "Unexpected data after JSON document";
|
|
default: return "Unknown error";
|
|
}
|
|
}
|
|
|
|
int CJson::GetLine()
|
|
{
|
|
if (m_error == JSON_OK) return 0;
|
|
int line = 1;
|
|
uint limit = MathMin(m_error_pos, ArraySize(Buffer));
|
|
for (uint i = 0; i < limit; i++)
|
|
{
|
|
if (Buffer[i] == '\n') line++;
|
|
else if (Buffer[i] == '\r' && i + 1 < limit && Buffer[i+1] == '\n') i++; // Skip \r\n
|
|
}
|
|
return line;
|
|
}
|
|
|
|
int CJson::GetColumn()
|
|
{
|
|
if (m_error == JSON_OK) return 0;
|
|
int col = 1;
|
|
uint limit = MathMin(m_error_pos, ArraySize(Buffer));
|
|
for (uint i = limit; i > 0; i--)
|
|
{
|
|
uchar c = Buffer[i-1];
|
|
if (c == '\n') break;
|
|
if (c == '\r') break;
|
|
col++;
|
|
}
|
|
return col;
|
|
} |