1578 lines
54 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| MinMax.mqh |
| |||
//| Copyright 2026, Niquel Mendoza. |
| |||
//| https://www.mql5.com/ |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "Copyright 2026, Niquel Mendoza."
| |||
#property link "https://www.mql5.com/"
| |||
#property strict
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
/*
| |||
Otro modelo de ia descarto creo qeu casi todos estos modelos chinos
| |||
Se confunden mucho con c++ como lo dije cosa que gpt\claude ya lo superarn en 2025 por ahi..
| |||
Arpatri de ahi los errores ya eran conocimiento no torpes
| |||
.. Aqui sigue pasando lo mismo con MinMax 3 .... usa referencias cuando en MQL5
| |||
Eso no existe.. (solo paso por funcionfuera de eso no..)
| |||
Bueno igual aqui lo dejo..
| |||
*/
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Json.mqh |
| |||
//| High-Performance JSON Parser for MQL5 |
| |||
//| 100% Pure MQL5 - No DLLs |
| |||
//+------------------------------------------------------------------+
| |||
//| Features: |
| |||
//| - Single-pass iterative parser (no recursion) |
| |||
//| - Arena-allocated node array (cache-friendly, contiguous) |
| |||
//| - Decoded-string arena (zero-copy access via offsets) |
| |||
//| - Manual parse stack with bounded depth |
| |||
//| - Branchless fast-path string scan via lookup table |
| |||
//| - Cached number/integer/bool values per node |
| |||
//| - Zero-copy input: user owns the source buffer |
| |||
//| - Full JSON spec: objects, arrays, strings (with all escapes), |
| |||
//| numbers (int / frac / exp), true / false / null |
| |||
//| - Full UTF-8 / \uXXXX / surrogate-pair support |
| |||
//+------------------------------------------------------------------+
| |||
//| Usage example: |
| |||
//| #include "Json.mqh" |
| |||
//| |
| |||
//| uchar raw[] = "{\"symbol\":\"EURUSD\",\"bid\":1.0850}"; |
| |||
//| CJson js; |
| |||
//| ArrayCopy(js.Buffer, raw); // zero-copy, or assign directly |
| |||
//| if(js.Parse()) |
| |||
//| { |
| |||
//| Print(js["symbol"].ToString()); // "EURUSD" |
| |||
//| Print(js["bid"].ToDouble()); // 1.0850 |
| |||
//| js["ticks"][5].ToDouble(); // chained access |
| |||
//| } |
| |||
//| else |
| |||
//| Print("Error: ", js.GetErrorMessage(), |
| |||
//| " at line ", js.GetLine(), |
| |||
//| " col ", js.GetColumn()); |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "Mavis Agent"
| |||
#property version "1.00"
| |||
#property strict
| |||
| |||
#ifndef JSON_MQH_INCLUDED
| |||
#define JSON_MQH_INCLUDED
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Tunable parameters (override with -D flag at compile time) |
| |||
//+------------------------------------------------------------------+
| |||
#ifndef JSON_MAX_DEPTH
| |||
#define JSON_MAX_DEPTH 256
| |||
#endif
| |||
| |||
#ifndef JSON_INITIAL_NODES
| |||
#define JSON_INITIAL_NODES 128
| |||
#endif
| |||
| |||
#ifndef JSON_INITIAL_STRINGS
| |||
#define JSON_INITIAL_STRINGS 4096
| |||
#endif
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Public JSON node types |
| |||
//+------------------------------------------------------------------+
| |||
enum ENUM_JSON_TYPE
| |||
{
| |||
JSON_TYPE_INVALID = 0,
| |||
JSON_TYPE_NULL = 1,
| |||
JSON_TYPE_BOOL = 2,
| |||
JSON_TYPE_NUMBER = 3,
| |||
JSON_TYPE_STRING = 4,
| |||
JSON_TYPE_ARRAY = 5,
| |||
JSON_TYPE_OBJECT = 6
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Public error codes (return value of GetError) |
| |||
//+------------------------------------------------------------------+
| |||
enum ENUM_JSON_ERROR
| |||
{
| |||
JSON_OK = 0,
| |||
JSON_ERR_EMPTY = -1,
| |||
JSON_ERR_UNEXPECTED_CHAR = -2,
| |||
JSON_ERR_UNEXPECTED_END = -3,
| |||
JSON_ERR_INVALID_NUMBER = -4,
| |||
JSON_ERR_INVALID_STRING = -5,
| |||
JSON_ERR_INVALID_ESCAPE = -6,
| |||
JSON_ERR_INVALID_UNICODE = -7,
| |||
JSON_ERR_EXPECTED_COLON = -8,
| |||
JSON_ERR_EXPECTED_COMMA = -9,
| |||
JSON_ERR_EXPECTED_KEY = -10,
| |||
JSON_ERR_EXPECTED_VALUE = -11,
| |||
JSON_ERR_MAX_DEPTH = -12,
| |||
JSON_ERR_INVALID_TOKEN = -13,
| |||
JSON_ERR_TRAILING_DATA = -14,
| |||
JSON_ERR_INVALID_ROOT = -15,
| |||
JSON_ERR_OUT_OF_MEMORY = -16,
| |||
JSON_ERR_INVALID_INDEX = -17
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Internal: parse state machine |
| |||
//+------------------------------------------------------------------+
| |||
enum ENUM_PARSE_STATE
| |||
{
| |||
ST_EXPECT_VALUE = 0, // inside array / right after [ / right after :
| |||
ST_EXPECT_KEY_OR_END = 1, // inside object, right after { or ,
| |||
ST_EXPECT_COLON = 2, // inside object, right after key
| |||
ST_EXPECT_VALUE_OR_END = 3, // inside array, right after [ or ,
| |||
ST_EXPECT_COMMA_OR_END = 4 // inside any container, right after a value
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Internal: compact node structure |
| |||
//| Intentionally kept small to maximize cache friendliness. |
| |||
//+------------------------------------------------------------------+
| |||
struct JsonNode
| |||
{
| |||
char type; // ENUM_JSON_TYPE
| |||
uchar flags; // bit 0: has cached number, bit 1: has cached int
| |||
int parent; // parent index (-1 for root)
| |||
int next; // next sibling index (-1 if none)
| |||
int child; // first child index (-1 if none)
| |||
int name_off; // offset of key in Strings[] (-1 if none)
| |||
int name_len; // length of key in bytes
| |||
int val_off; // offset of value in Strings[] (or in Buffer for raw numbers)
| |||
int val_len; // length of value
| |||
double num_value; // cached numeric value (numbers)
| |||
long int_value; // cached integer value (numbers)
| |||
bool bool_value; // cached bool value
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Internal: parse stack frame |
| |||
//+------------------------------------------------------------------+
| |||
struct JsonFrame
| |||
{
| |||
int parent_node; // index of container node in Nodes[]
| |||
int last_child; // last child appended (for O(1) sibling link)
| |||
char state; // ENUM_PARSE_STATE
| |||
char container; // JSON_TYPE_ARRAY or JSON_TYPE_OBJECT
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Forward declarations |
| |||
//+------------------------------------------------------------------+
| |||
class CJson;
| |||
class CJsonNode;
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Branchless lookup table for the hot string scan loop. |
| |||
//| Cells with value 1 force the loop to stop. |
| |||
//+------------------------------------------------------------------+
| |||
uchar g_json_qtbl[256];
| |||
bool g_json_qtbl_ready = false;
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| One-time table initialization. |
| |||
//+------------------------------------------------------------------+
| |||
void JsonInitTables()
| |||
{
| |||
if(g_json_qtbl_ready)
| |||
return;
| |||
ArrayInitialize(g_json_qtbl, 0);
| |||
g_json_qtbl['"'] = 1;
| |||
g_json_qtbl['\\'] = 1;
| |||
g_json_qtbl_ready = true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| CJsonNode - non-owning view of a node inside a CJson document. |
| |||
//| Supports chainable operator[]. |
| |||
//+------------------------------------------------------------------+
| |||
class CJsonNode
| |||
{
| |||
private:
| |||
CJson *m_owner; // owning CJson (must outlive the view)
| |||
int m_index; // node index in m_owner.Nodes[] (-1 = invalid)
| |||
| |||
public:
| |||
CJsonNode(): m_owner(NULL), m_index(-1) {}
| |||
CJsonNode(CJson *owner, int index): m_owner(owner), m_index(index) {}
| |||
| |||
// Validity / type queries
| |||
bool IsValid() const;
| |||
bool IsObject() const;
| |||
bool IsArray() const;
| |||
bool IsString() const;
| |||
bool IsNumber() const;
| |||
bool IsBool() const;
| |||
bool IsNull() const;
| |||
ENUM_JSON_TYPE GetType() const;
| |||
| |||
// Container queries
| |||
int Size() const;
| |||
int Count() const { return Size(); }
| |||
bool Empty() const;
| |||
bool Exists(const string key) const;
| |||
| |||
// Value accessors (with default values)
| |||
string ToString(const string def = "") const;
| |||
double ToDouble(const double def = 0.0) const;
| |||
long ToInteger(const long def = 0) const;
| |||
bool ToBool(const bool def = false) const;
| |||
| |||
// Key / parent / siblings
| |||
string Key() const;
| |||
CJsonNode Parent() const;
| |||
CJsonNode FirstChild() const;
| |||
CJsonNode NextSibling() const;
| |||
CJsonNode GetChild(int i) const;
| |||
| |||
// Chainable navigation
| |||
CJsonNode operator[](const string key) const;
| |||
CJsonNode operator[](int index) const;
| |||
| |||
// Internal access (used by CJson)
| |||
int RawIndex() const { return m_index; }
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| CJson - high-performance JSON parser / DOM. |
| |||
//+------------------------------------------------------------------+
| |||
class CJson
| |||
{
| |||
public:
| |||
//--- Public input buffer; the user fills this directly (zero copy).
| |||
//--- The parser does NOT free Buffer - the caller owns its lifetime.
| |||
//--- Use ArrayFree(json.Buffer) explicitly if the buffer was created
| |||
//--- locally and is no longer needed.
| |||
uchar Buffer[];
| |||
| |||
//--- Construction / destruction
| |||
CJson();
| |||
~CJson();
| |||
| |||
//--- Main API
| |||
bool Parse();
| |||
void Clear(); // release all storage
| |||
void Reset(); // reinitialize for reuse (keeps allocations)
| |||
| |||
//--- Error reporting
| |||
bool HasError() const { return m_last_error != JSON_OK; }
| |||
int GetError() const { return (int)m_last_error; }
| |||
string GetErrorMessage() const;
| |||
int GetErrorPosition()const { return m_error_pos; }
| |||
int GetLine() const { return m_error_line; }
| |||
int GetColumn() const { return m_error_col; }
| |||
| |||
//--- Root type queries
| |||
bool IsObject() const;
| |||
bool IsArray() const;
| |||
bool IsString() const;
| |||
bool IsNumber() const;
| |||
bool IsBool() const;
| |||
bool IsNull() const;
| |||
ENUM_JSON_TYPE GetType() const;
| |||
| |||
//--- Root container queries
| |||
int Size() const;
| |||
int Count() const { return Size(); }
| |||
bool Exists(const string key) const;
| |||
| |||
//--- Root value accessors
| |||
string ToString(const string def = "") const;
| |||
double ToDouble(const double def = 0.0) const;
| |||
long ToInteger(const long def = 0) const;
| |||
bool ToBool(const bool def = false) const;
| |||
| |||
//--- Root navigation
| |||
CJsonNode operator[](const string key);
| |||
CJsonNode operator[](int index);
| |||
CJsonNode GetRoot() { return CJsonNode((CJson*)GetPointer(this), m_root); }
| |||
| |||
//--- Statistics (for diagnostics / benchmarks)
| |||
int NodeCount() const { return m_nodes_count; }
| |||
int StringBytes() const { return m_strings_len; }
| |||
int CapacityNodes() const { return m_nodes_cap; }
| |||
| |||
private:
| |||
//--- Node arena (flat, cache-friendly)
| |||
JsonNode m_nodes[];
| |||
int m_nodes_count;
| |||
int m_nodes_cap;
| |||
| |||
//--- Decoded-string arena
| |||
uchar m_strings[];
| |||
int m_strings_len;
| |||
int m_strings_cap;
| |||
| |||
//--- Parse state
| |||
int m_root;
| |||
int m_last_error;
| |||
int m_error_pos;
| |||
int m_error_line;
| |||
int m_error_col;
| |||
int m_pos;
| |||
int m_line;
| |||
int m_col;
| |||
| |||
//--- Internal helpers
| |||
int AllocNode();
| |||
bool ReserveNodes(int need);
| |||
bool ReserveStrings(int need);
| |||
int AppendString(const uchar &src[], int off, int len);
| |||
int AppendChar(uchar b);
| |||
int AppendCharAt(int pos, uchar b);
| |||
int AddChild(int parent, int prev_sibling, char type);
| |||
| |||
int SkipWs(int p);
| |||
int ParseValueInto(int node);
| |||
int ParseStringAt(int start, int &out_off, int &out_len);
| |||
int ParseNumberAt(int start, double &out_num, long &out_int, int &out_len);
| |||
int ParseLiteralAt(int start, const uchar &lit[], int lit_len);
| |||
int DecodeHex4(int p, int &codepoint);
| |||
int EncodeUtf8(int codepoint, int dst_pos);
| |||
bool SetError(int err, int pos);
| |||
int NodeFindKey(int parent, const string key) const;
| |||
bool KeyMatch(const JsonNode &cn, const string key) const;
| |||
| |||
//--- Friends
| |||
friend class CJsonNode;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| CJsonNode - implementation |
| |||
//+------------------------------------------------------------------+
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Check whether this view is valid (refers to a real node) |
| |||
//+------------------------------------------------------------------+
| |||
bool CJsonNode::IsValid() const
| |||
{
| |||
return (m_owner != NULL && m_index >= 0 && m_index < m_owner.m_nodes_count);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Type queries |
| |||
//+------------------------------------------------------------------+
| |||
ENUM_JSON_TYPE CJsonNode::GetType() const
| |||
{
| |||
if(!IsValid())
| |||
return JSON_TYPE_INVALID;
| |||
return (ENUM_JSON_TYPE)m_owner.m_nodes[m_index].type;
| |||
}
| |||
| |||
bool CJsonNode::IsObject() const { return GetType() == JSON_TYPE_OBJECT; }
| |||
bool CJsonNode::IsArray() const { return GetType() == JSON_TYPE_ARRAY; }
| |||
bool CJsonNode::IsString() const { return GetType() == JSON_TYPE_STRING; }
| |||
bool CJsonNode::IsNumber() const { return GetType() == JSON_TYPE_NUMBER; }
| |||
bool CJsonNode::IsBool() const { return GetType() == JSON_TYPE_BOOL; }
| |||
bool CJsonNode::IsNull() const { return GetType() == JSON_TYPE_NULL; }
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Number of children (objects/arrays) or 1 (scalars) |
| |||
//+------------------------------------------------------------------+
| |||
int CJsonNode::Size() const
| |||
{
| |||
if(!IsValid())
| |||
return 0;
| |||
int c = m_owner.m_nodes[m_index].child;
| |||
if(c < 0)
| |||
return 0;
| |||
int n = 0;
| |||
while(c >= 0)
| |||
{
| |||
n++;
| |||
c = m_owner.m_nodes[c].next;
| |||
}
| |||
return n;
| |||
}
| |||
| |||
bool CJsonNode::Empty() const { return Size() == 0; }
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Check existence of a key in an object |
| |||
//+------------------------------------------------------------------+
| |||
bool CJsonNode::Exists(const string key) const
| |||
{
| |||
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_OBJECT)
| |||
return false;
| |||
return m_owner.NodeFindKey(m_index, key) >= 0;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Return the key (object context) or "" (array/root context) |
| |||
//+------------------------------------------------------------------+
| |||
string CJsonNode::Key() const
| |||
{
| |||
if(!IsValid())
| |||
return "";
| |||
const JsonNode &n = m_owner.m_nodes[m_index];
| |||
if(n.name_off < 0 || n.name_len <= 0)
| |||
return "";
| |||
return CharArrayToString(m_owner.m_strings, n.name_off, n.name_len, CP_UTF8);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Parent / sibling navigation |
| |||
//+------------------------------------------------------------------+
| |||
CJsonNode CJsonNode::Parent() const
| |||
{
| |||
if(!IsValid())
| |||
return CJsonNode();
| |||
int p = m_owner.m_nodes[m_index].parent;
| |||
if(p < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, p);
| |||
}
| |||
| |||
CJsonNode CJsonNode::FirstChild() const
| |||
{
| |||
if(!IsValid())
| |||
return CJsonNode();
| |||
int c = m_owner.m_nodes[m_index].child;
| |||
if(c < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, c);
| |||
}
| |||
| |||
CJsonNode CJsonNode::NextSibling() const
| |||
{
| |||
if(!IsValid())
| |||
return CJsonNode();
| |||
int n = m_owner.m_nodes[m_index].next;
| |||
if(n < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, n);
| |||
}
| |||
| |||
CJsonNode CJsonNode::GetChild(int i) const
| |||
{
| |||
if(!IsValid() || i < 0)
| |||
return CJsonNode();
| |||
int c = m_owner.m_nodes[m_index].child;
| |||
while(c >= 0 && i > 0)
| |||
{
| |||
c = m_owner.m_nodes[c].next;
| |||
i--;
| |||
}
| |||
if(c < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, c);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get string value. For numbers, returns the raw source text. |
| |||
//+------------------------------------------------------------------+
| |||
string CJsonNode::ToString(const string def) const
| |||
{
| |||
if(!IsValid())
| |||
return def;
| |||
const JsonNode &n = m_owner.m_nodes[m_index];
| |||
if(n.type == JSON_TYPE_STRING)
| |||
{
| |||
if(n.val_len <= 0)
| |||
return "";
| |||
return CharArrayToString(m_owner.m_strings, n.val_off, n.val_len, CP_UTF8);
| |||
}
| |||
if(n.type == JSON_TYPE_NUMBER)
| |||
{
| |||
if(n.val_len <= 0)
| |||
return def;
| |||
return CharArrayToString(m_owner.Buffer, n.val_off, n.val_len);
| |||
}
| |||
return def;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get double value |
| |||
//+------------------------------------------------------------------+
| |||
double CJsonNode::ToDouble(const double def) const
| |||
{
| |||
if(!IsValid())
| |||
return def;
| |||
const JsonNode &n = m_owner.m_nodes[m_index];
| |||
if(n.type != JSON_TYPE_NUMBER)
| |||
return def;
| |||
return n.num_value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get integer value |
| |||
//+------------------------------------------------------------------+
| |||
long CJsonNode::ToInteger(const long def) const
| |||
{
| |||
if(!IsValid())
| |||
return def;
| |||
const JsonNode &n = m_owner.m_nodes[m_index];
| |||
if(n.type != JSON_TYPE_NUMBER)
| |||
return def;
| |||
return n.int_value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Get bool value |
| |||
//+------------------------------------------------------------------+
| |||
bool CJsonNode::ToBool(const bool def) const
| |||
{
| |||
if(!IsValid())
| |||
return def;
| |||
const JsonNode &n = m_owner.m_nodes[m_index];
| |||
if(n.type != JSON_TYPE_BOOL)
| |||
return def;
| |||
return n.bool_value;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| operator[] by key (object) |
| |||
//+------------------------------------------------------------------+
| |||
CJsonNode CJsonNode::operator[](const string key) const
| |||
{
| |||
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_OBJECT)
| |||
return CJsonNode();
| |||
int found = m_owner.NodeFindKey(m_index, key);
| |||
if(found < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, found);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| operator[] by index (array) |
| |||
//+------------------------------------------------------------------+
| |||
CJsonNode CJsonNode::operator[](int index) const
| |||
{
| |||
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_ARRAY || index < 0)
| |||
return CJsonNode();
| |||
int c = m_owner.m_nodes[m_index].child;
| |||
while(c >= 0 && index > 0)
| |||
{
| |||
c = m_owner.m_nodes[c].next;
| |||
index--;
| |||
}
| |||
if(c < 0)
| |||
return CJsonNode();
| |||
return CJsonNode(m_owner, c);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| CJson - implementation |
| |||
//+------------------------------------------------------------------+
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Constructor |
| |||
//+------------------------------------------------------------------+
| |||
CJson::CJson()
| |||
: m_nodes_count(0),
| |||
m_nodes_cap(0),
| |||
m_strings_len(0),
| |||
m_strings_cap(0),
| |||
m_root(-1),
| |||
m_last_error(JSON_OK),
| |||
m_error_pos(0),
| |||
m_error_line(0),
| |||
m_error_col(0),
| |||
m_pos(0),
| |||
m_line(1),
| |||
m_col(1)
| |||
{
| |||
JsonInitTables();
| |||
ReserveNodes(JSON_INITIAL_NODES);
| |||
ReserveStrings(JSON_INITIAL_STRINGS);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Destructor |
| |||
//+------------------------------------------------------------------+
| |||
CJson::~CJson()
| |||
{
| |||
Clear();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Release all storage |
| |||
//| Note: Buffer is the user-owned input buffer and is never freed |
| |||
//| by the parser. Use ArrayFree(json.Buffer) explicitly if needed. |
| |||
//+------------------------------------------------------------------+
| |||
void CJson::Clear()
| |||
{
| |||
ArrayFree(m_nodes);
| |||
ArrayFree(m_strings);
| |||
m_nodes_count = 0;
| |||
m_nodes_cap = 0;
| |||
m_strings_len = 0;
| |||
m_strings_cap = 0;
| |||
m_root = -1;
| |||
m_last_error = JSON_OK;
| |||
m_error_pos = 0;
| |||
m_error_line = 0;
| |||
m_error_col = 0;
| |||
m_pos = 0;
| |||
m_line = 1;
| |||
m_col = 1;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Reinitialize but keep allocations |
| |||
//+------------------------------------------------------------------+
| |||
void CJson::Reset()
| |||
{
| |||
m_nodes_count = 0;
| |||
m_strings_len = 0;
| |||
m_root = -1;
| |||
m_last_error = JSON_OK;
| |||
m_error_pos = 0;
| |||
m_error_line = 0;
| |||
m_error_col = 0;
| |||
m_pos = 0;
| |||
m_line = 1;
| |||
m_col = 1;
| |||
// Do not release Buffer; user owns it.
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Reserve capacity in the node arena |
| |||
//+------------------------------------------------------------------+
| |||
bool CJson::ReserveNodes(int need)
| |||
{
| |||
if(need <= m_nodes_cap)
| |||
return true;
| |||
int new_cap = (m_nodes_cap == 0) ? JSON_INITIAL_NODES : m_nodes_cap;
| |||
while(new_cap < need) new_cap *= 2;
| |||
if(ArraySize(m_nodes) >= new_cap)
| |||
{
| |||
m_nodes_cap = ArraySize(m_nodes);
| |||
return true;
| |||
}
| |||
ArrayResize(m_nodes, new_cap);
| |||
m_nodes_cap = ArraySize(m_nodes);
| |||
return (m_nodes_cap >= need);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Reserve capacity in the string arena |
| |||
//+------------------------------------------------------------------+
| |||
bool CJson::ReserveStrings(int need)
| |||
{
| |||
if(need <= m_strings_cap)
| |||
return true;
| |||
int new_cap = (m_strings_cap == 0) ? JSON_INITIAL_STRINGS : m_strings_cap;
| |||
while(new_cap < need) new_cap *= 2;
| |||
if(ArraySize(m_strings) >= new_cap)
| |||
{
| |||
m_strings_cap = ArraySize(m_strings);
| |||
return true;
| |||
}
| |||
ArrayResize(m_strings, new_cap);
| |||
m_strings_cap = ArraySize(m_strings);
| |||
return (m_strings_cap >= need);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Allocate one node from the arena |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::AllocNode()
| |||
{
| |||
if(!ReserveNodes(m_nodes_count + 1))
| |||
return -1;
| |||
int idx = m_nodes_count++;
| |||
JsonNode &n = m_nodes[idx];
| |||
n.type = JSON_TYPE_INVALID;
| |||
n.flags = 0;
| |||
n.parent = -1;
| |||
n.next = -1;
| |||
n.child = -1;
| |||
n.name_off = -1;
| |||
n.name_len = 0;
| |||
n.val_off = -1;
| |||
n.val_len = 0;
| |||
n.num_value = 0.0;
| |||
n.int_value = 0;
| |||
n.bool_value= false;
| |||
return idx;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Append bytes to the string arena |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::AppendString(const uchar &src[], int off, int len)
| |||
{
| |||
if(len <= 0)
| |||
return m_strings_len;
| |||
if(!ReserveStrings(m_strings_len + len))
| |||
return -1;
| |||
int dst = m_strings_len;
| |||
for(int i = 0; i < len; i++)
| |||
m_strings[dst + i] = src[off + i];
| |||
m_strings_len = dst + len;
| |||
return dst;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Append a single byte to the string arena |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::AppendChar(uchar b)
| |||
{
| |||
if(!ReserveStrings(m_strings_len + 1))
| |||
return -1;
| |||
int dst = m_strings_len;
| |||
m_strings[dst] = b;
| |||
m_strings_len = dst + 1;
| |||
return dst;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Overwrite a single byte in the string arena |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::AppendCharAt(int pos, uchar b)
| |||
{
| |||
if(pos < 0 || pos >= m_strings_len)
| |||
return -1;
| |||
m_strings[pos] = b;
| |||
return pos;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Append a child to a container node. |
| |||
//| prev_sibling is the tail of the sibling chain (-1 for first). |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::AddChild(int parent, int prev_sibling, char type)
| |||
{
| |||
int child = AllocNode();
| |||
if(child < 0)
| |||
return -1;
| |||
JsonNode &c = m_nodes[child];
| |||
c.type = type;
| |||
c.parent = parent;
| |||
c.next = -1;
| |||
if(prev_sibling < 0)
| |||
m_nodes[parent].child = child;
| |||
else
| |||
m_nodes[prev_sibling].next = child;
| |||
return child;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Skip ASCII whitespace. |
| |||
//| Line / column are computed lazily on error in SetError. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::SkipWs(int p)
| |||
{
| |||
const int len = ArraySize(Buffer);
| |||
while(p < len)
| |||
{
| |||
uchar c = Buffer[p];
| |||
if(c == ' ' || c == '\t') { p++; continue; }
| |||
if(c == '\n') { p++; continue; }
| |||
if(c == '\r')
| |||
{
| |||
p++;
| |||
if(p < len && Buffer[p] == '\n') p++;
| |||
continue;
| |||
}
| |||
break;
| |||
}
| |||
return p;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Set the error and return a code |
| |||
//+------------------------------------------------------------------+
| |||
bool CJson::SetError(int err, int pos)
| |||
{
| |||
m_last_error = err;
| |||
m_error_pos = pos;
| |||
m_error_line = 1;
| |||
m_error_col = 1;
| |||
// Compute line/column by scanning from start to pos.
| |||
for(int i = 0; i < pos && i < ArraySize(Buffer); i++)
| |||
{
| |||
uchar c = Buffer[i];
| |||
if(c == '\n')
| |||
{
| |||
m_error_line++;
| |||
m_error_col = 1;
| |||
}
| |||
else if(c != '\r')
| |||
{
| |||
m_error_col++;
| |||
}
| |||
}
| |||
return false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Parse a JSON literal (true / false / null) |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::ParseLiteralAt(int start, const uchar &lit[], int lit_len)
| |||
{
| |||
const int len = ArraySize(Buffer);
| |||
if(start + lit_len > len)
| |||
return SetError(JSON_ERR_UNEXPECTED_END, start);
| |||
for(int i = 0; i < lit_len; i++)
| |||
{
| |||
if(Buffer[start + i] != lit[i])
| |||
return SetError(JSON_ERR_INVALID_TOKEN, start);
| |||
}
| |||
m_pos = start + lit_len;
| |||
return JSON_OK;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Decode 4 hex digits at position p. |
| |||
//| On success returns JSON_OK and stores the codepoint. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::DecodeHex4(int p, int &codepoint)
| |||
{
| |||
const int len = ArraySize(Buffer);
| |||
if(p + 4 > len)
| |||
return SetError(JSON_ERR_UNEXPECTED_END, p);
| |||
int v = 0;
| |||
for(int i = 0; i < 4; i++)
| |||
{
| |||
uchar c = Buffer[p + i];
| |||
int d;
| |||
if(c >= '0' && c <= '9') d = c - '0';
| |||
else if(c >= 'a' && c <= 'f') d = c - 'a' + 10;
| |||
else if(c >= 'A' && c <= 'F') d = c - 'A' + 10;
| |||
else return SetError(JSON_ERR_INVALID_UNICODE, p);
| |||
v = (v << 4) | d;
| |||
}
| |||
codepoint = v;
| |||
return JSON_OK;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Encode a Unicode codepoint as UTF-8 starting at dst_pos. |
| |||
//| Returns the number of bytes written. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::EncodeUtf8(int cp, int dst_pos)
| |||
{
| |||
if(!ReserveStrings(dst_pos + 4))
| |||
return -1;
| |||
int n = 0;
| |||
if(cp < 0x80)
| |||
{
| |||
m_strings[dst_pos + 0] = (uchar)cp;
| |||
n = 1;
| |||
}
| |||
else if(cp < 0x800)
| |||
{
| |||
m_strings[dst_pos + 0] = (uchar)(0xC0 | (cp >> 6));
| |||
m_strings[dst_pos + 1] = (uchar)(0x80 | (cp & 0x3F));
| |||
n = 2;
| |||
}
| |||
else if(cp < 0x10000)
| |||
{
| |||
m_strings[dst_pos + 0] = (uchar)(0xE0 | (cp >> 12));
| |||
m_strings[dst_pos + 1] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
| |||
m_strings[dst_pos + 2] = (uchar)(0x80 | (cp & 0x3F));
| |||
n = 3;
| |||
}
| |||
else
| |||
{
| |||
m_strings[dst_pos + 0] = (uchar)(0xF0 | (cp >> 18));
| |||
m_strings[dst_pos + 1] = (uchar)(0x80 | ((cp >> 12) & 0x3F));
| |||
m_strings[dst_pos + 2] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
| |||
m_strings[dst_pos + 3] = (uchar)(0x80 | (cp & 0x3F));
| |||
n = 4;
| |||
}
| |||
if(dst_pos + n > m_strings_len)
| |||
m_strings_len = dst_pos + n;
| |||
return n;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Parse a JSON string. |
| |||
//| Decodes the string into m_strings[]; the node references it |
| |||
//| via out_off / out_len. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::ParseStringAt(int start, int &out_off, int &out_len)
| |||
{
| |||
const int len = ArraySize(Buffer);
| |||
if(start >= len || Buffer[start] != '"')
| |||
return SetError(JSON_ERR_INVALID_STRING, start);
| |||
int p = start + 1; // current scan position in Buffer
| |||
int src_pos = p; // start of the current unescaped span
| |||
out_off = -1;
| |||
out_len = 0;
| |||
| |||
while(true)
| |||
{
| |||
// Branchless scan to next " or \.
| |||
while(p < len && g_json_qtbl[Buffer[p]] == 0) p++;
| |||
if(p >= len)
| |||
return SetError(JSON_ERR_UNEXPECTED_END, p);
| |||
uchar c = Buffer[p];
| |||
if(c == '"')
| |||
{
| |||
// Closing quote: flush the final unescaped span.
| |||
if(p > src_pos)
| |||
{
| |||
int r = AppendString(Buffer, src_pos, p - src_pos);
| |||
if(r < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
if(out_off < 0) out_off = r;
| |||
}
| |||
else if(out_off < 0)
| |||
{
| |||
// Empty string: remember the current arena end.
| |||
out_off = m_strings_len;
| |||
}
| |||
out_len = m_strings_len - out_off;
| |||
m_pos = p + 1; // past closing quote
| |||
return JSON_OK;
| |||
}
| |||
// c == '\\' : flush the unescaped span, then handle the escape.
| |||
if(p + 1 >= len)
| |||
return SetError(JSON_ERR_UNEXPECTED_END, p + 1);
| |||
if(p > src_pos)
| |||
{
| |||
int r = AppendString(Buffer, src_pos, p - src_pos);
| |||
if(r < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
if(out_off < 0) out_off = r;
| |||
}
| |||
else if(out_off < 0)
| |||
{
| |||
out_off = m_strings_len;
| |||
}
| |||
uchar e = Buffer[p + 1];
| |||
int consumed = 2;
| |||
if(e == '"') { if(AppendChar('"') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == '\\') { if(AppendChar('\\') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == '/') { if(AppendChar('/') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 'b') { if(AppendChar(0x08) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 'f') { if(AppendChar(0x0C) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 'n') { if(AppendChar(0x0A) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 'r') { if(AppendChar(0x0D) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 't') { if(AppendChar(0x09) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
| |||
else if(e == 'u')
| |||
{
| |||
int cp;
| |||
int err = DecodeHex4(p + 2, cp);
| |||
if(err != JSON_OK) return err;
| |||
consumed = 6;
| |||
if(cp >= 0xD800 && cp <= 0xDBFF)
| |||
{
| |||
if(p + 12 > len || Buffer[p + 6] != '\\' || Buffer[p + 7] != 'u')
| |||
return SetError(JSON_ERR_INVALID_UNICODE, p);
| |||
int cp2;
| |||
err = DecodeHex4(p + 8, cp2);
| |||
if(err != JSON_OK) return err;
| |||
if(cp2 < 0xDC00 || cp2 > 0xDFFF)
| |||
return SetError(JSON_ERR_INVALID_UNICODE, p);
| |||
cp = 0x10000 + ((cp - 0xD800) << 10) + (cp2 - 0xDC00);
| |||
consumed = 12;
| |||
}
| |||
else if(cp >= 0xDC00 && cp <= 0xDFFF)
| |||
{
| |||
return SetError(JSON_ERR_INVALID_UNICODE, p);
| |||
}
| |||
int dst = m_strings_len;
| |||
int n = EncodeUtf8(cp, dst);
| |||
if(n < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
}
| |||
else
| |||
{
| |||
return SetError(JSON_ERR_INVALID_ESCAPE, p + 1);
| |||
}
| |||
p += consumed;
| |||
src_pos = p; // next unescaped span starts here
| |||
}
| |||
return JSON_OK;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Parse a JSON number. |
| |||
//| Uses a single pass: collects the byte range, then converts. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::ParseNumberAt(int start, double &out_num, long &out_int, int &out_len)
| |||
{
| |||
const int len = ArraySize(Buffer);
| |||
int p = start;
| |||
// Optional sign
| |||
if(p < len && Buffer[p] == '-') p++;
| |||
if(p >= len) return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
// Integer part: 0 or [1-9][0-9]* (no leading zeros)
| |||
if(p >= len) return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
if(Buffer[p] == '0')
| |||
{
| |||
p++;
| |||
// Reject leading zeros like "01", "02.5"
| |||
if(p < len && Buffer[p] >= '0' && Buffer[p] <= '9')
| |||
return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
}
| |||
else if(Buffer[p] >= '1' && Buffer[p] <= '9')
| |||
{
| |||
p++;
| |||
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
| |||
}
| |||
else
| |||
return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
// Fraction
| |||
bool is_int = true;
| |||
if(p < len && Buffer[p] == '.')
| |||
{
| |||
is_int = false;
| |||
p++;
| |||
if(p >= len || Buffer[p] < '0' || Buffer[p] > '9')
| |||
return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
| |||
}
| |||
// Exponent
| |||
if(p < len && (Buffer[p] == 'e' || Buffer[p] == 'E'))
| |||
{
| |||
is_int = false;
| |||
p++;
| |||
if(p < len && (Buffer[p] == '+' || Buffer[p] == '-')) p++;
| |||
if(p >= len || Buffer[p] < '0' || Buffer[p] > '9')
| |||
return SetError(JSON_ERR_INVALID_NUMBER, p);
| |||
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
| |||
}
| |||
// Build substring and convert.
| |||
int n = p - start;
| |||
if(n <= 0) return SetError(JSON_ERR_INVALID_NUMBER, start);
| |||
string s = CharArrayToString(Buffer, start, n);
| |||
out_num = StringToDouble(s);
| |||
out_int = (long)StringToInteger(s);
| |||
out_len = n;
| |||
m_pos = p;
| |||
return JSON_OK;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Parse one value (object / array / string / number / bool / null) |
| |||
//| into the given node. |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::ParseValueInto(int node)
| |||
{
| |||
int p = SkipWs(m_pos);
| |||
m_pos = p;
| |||
const int len = ArraySize(Buffer);
| |||
if(p >= len) return SetError(JSON_ERR_UNEXPECTED_END, p);
| |||
uchar c = Buffer[p];
| |||
| |||
if(c == '{')
| |||
{
| |||
m_nodes[node].type = JSON_TYPE_OBJECT;
| |||
m_nodes[node].child = -1;
| |||
m_pos = p + 1;
| |||
return JSON_OK;
| |||
}
| |||
if(c == '[')
| |||
{
| |||
m_nodes[node].type = JSON_TYPE_ARRAY;
| |||
m_nodes[node].child = -1;
| |||
m_pos = p + 1;
| |||
return JSON_OK;
| |||
}
| |||
if(c == '"')
| |||
{
| |||
int off, len_out;
| |||
int err = ParseStringAt(p, off, len_out);
| |||
if(err != JSON_OK) return err;
| |||
m_nodes[node].type = JSON_TYPE_STRING;
| |||
m_nodes[node].val_off = off;
| |||
m_nodes[node].val_len = len_out;
| |||
return JSON_OK;
| |||
}
| |||
if(c == 't')
| |||
{
| |||
uchar lit[4];
| |||
lit[0]='t'; lit[1]='r'; lit[2]='u'; lit[3]='e';
| |||
int err = ParseLiteralAt(p, lit, 4);
| |||
if(err != JSON_OK) return err;
| |||
m_nodes[node].type = JSON_TYPE_BOOL;
| |||
m_nodes[node].bool_value = true;
| |||
m_nodes[node].val_len = 4;
| |||
return JSON_OK;
| |||
}
| |||
if(c == 'f')
| |||
{
| |||
uchar lit[5];
| |||
lit[0]='f'; lit[1]='a'; lit[2]='l'; lit[3]='s'; lit[4]='e';
| |||
int err = ParseLiteralAt(p, lit, 5);
| |||
if(err != JSON_OK) return err;
| |||
m_nodes[node].type = JSON_TYPE_BOOL;
| |||
m_nodes[node].bool_value = false;
| |||
m_nodes[node].val_len = 5;
| |||
return JSON_OK;
| |||
}
| |||
if(c == 'n')
| |||
{
| |||
uchar lit[4];
| |||
lit[0]='n'; lit[1]='u'; lit[2]='l'; lit[3]='l';
| |||
int err = ParseLiteralAt(p, lit, 4);
| |||
if(err != JSON_OK) return err;
| |||
m_nodes[node].type = JSON_TYPE_NULL;
| |||
m_nodes[node].val_len = 4;
| |||
return JSON_OK;
| |||
}
| |||
if(c == '-' || (c >= '0' && c <= '9'))
| |||
{
| |||
double num;
| |||
long iv;
| |||
int nlen;
| |||
int err = ParseNumberAt(p, num, iv, nlen);
| |||
if(err != JSON_OK) return err;
| |||
m_nodes[node].type = JSON_TYPE_NUMBER;
| |||
m_nodes[node].num_value = num;
| |||
m_nodes[node].int_value = iv;
| |||
// For numbers we keep the raw text range in Buffer (zero-copy).
| |||
m_nodes[node].val_off = p;
| |||
m_nodes[node].val_len = nlen;
| |||
m_nodes[node].flags = 0x01; // has cached num
| |||
return JSON_OK;
| |||
}
| |||
return SetError(JSON_ERR_UNEXPECTED_CHAR, p);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Main entry point |
| |||
//+------------------------------------------------------------------+
| |||
bool CJson::Parse()
| |||
{
| |||
Reset();
| |||
const int len = ArraySize(Buffer);
| |||
if(len == 0)
| |||
{
| |||
m_last_error = JSON_ERR_EMPTY;
| |||
m_error_pos = 0;
| |||
m_error_line = 1;
| |||
m_error_col = 1;
| |||
return false;
| |||
}
| |||
| |||
// Allocate root.
| |||
m_root = AllocNode();
| |||
if(m_root < 0)
| |||
{
| |||
m_last_error = JSON_ERR_OUT_OF_MEMORY;
| |||
return false;
| |||
}
| |||
m_nodes[m_root].parent = -1;
| |||
| |||
// Parse the root value.
| |||
int err = ParseValueInto(m_root);
| |||
if(err != JSON_OK)
| |||
return false;
| |||
| |||
// If root is a container, process its contents.
| |||
JsonFrame frames[JSON_MAX_DEPTH];
| |||
int depth = 0;
| |||
| |||
char rtype = m_nodes[m_root].type;
| |||
if(rtype == JSON_TYPE_OBJECT || rtype == JSON_TYPE_ARRAY)
| |||
{
| |||
frames[0].parent_node = m_root;
| |||
frames[0].last_child = -1;
| |||
frames[0].container = rtype;
| |||
frames[0].state = (rtype == JSON_TYPE_OBJECT)
| |||
? (char)ST_EXPECT_KEY_OR_END
| |||
: (char)ST_EXPECT_VALUE_OR_END;
| |||
depth = 1;
| |||
}
| |||
| |||
// Main parse loop. We keep going while there is at least one open
| |||
// container on the stack. After the last container closes we exit
| |||
// and verify there is no trailing data.
| |||
while(depth > 0)
| |||
{
| |||
int p = SkipWs(m_pos);
| |||
m_pos = p;
| |||
if(p >= len)
| |||
{
| |||
SetError(JSON_ERR_UNEXPECTED_END, p);
| |||
return false;
| |||
}
| |||
uchar c = Buffer[p];
| |||
JsonFrame &f = frames[depth - 1];
| |||
char state = f.state;
| |||
| |||
if(state == ST_EXPECT_KEY_OR_END)
| |||
{
| |||
if(c == '}')
| |||
{
| |||
depth--;
| |||
m_pos = p + 1;
| |||
if(depth > 0)
| |||
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
| |||
continue;
| |||
}
| |||
if(c == '"')
| |||
{
| |||
int off, lenk;
| |||
err = ParseStringAt(p, off, lenk);
| |||
if(err != JSON_OK) return false;
| |||
// Add a new key-value pair child.
| |||
int child = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
| |||
if(child < 0)
| |||
{
| |||
SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
return false;
| |||
}
| |||
m_nodes[child].name_off = off;
| |||
m_nodes[child].name_len = lenk;
| |||
f.last_child = child;
| |||
f.state = (char)ST_EXPECT_COLON;
| |||
continue;
| |||
}
| |||
SetError(JSON_ERR_EXPECTED_KEY, p);
| |||
return false;
| |||
}
| |||
else if(state == ST_EXPECT_COLON)
| |||
{
| |||
if(c != ':')
| |||
{
| |||
SetError(JSON_ERR_EXPECTED_COLON, p);
| |||
return false;
| |||
}
| |||
m_pos = p + 1;
| |||
f.state = (char)ST_EXPECT_VALUE;
| |||
continue;
| |||
}
| |||
else if(state == ST_EXPECT_VALUE)
| |||
{
| |||
// Object: fill the last_child node (created for the key).
| |||
// Array : create a new child.
| |||
int target;
| |||
if(f.container == JSON_TYPE_OBJECT)
| |||
target = f.last_child;
| |||
else
| |||
{
| |||
target = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
| |||
if(target < 0)
| |||
{
| |||
SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
return false;
| |||
}
| |||
f.last_child = target;
| |||
}
| |||
err = ParseValueInto(target);
| |||
if(err != JSON_OK) return false;
| |||
// If the value is itself a container, descend into it.
| |||
char vtype = m_nodes[target].type;
| |||
if(vtype == JSON_TYPE_OBJECT || vtype == JSON_TYPE_ARRAY)
| |||
{
| |||
if(depth >= JSON_MAX_DEPTH)
| |||
{
| |||
SetError(JSON_ERR_MAX_DEPTH, m_pos);
| |||
return false;
| |||
}
| |||
JsonFrame &nf = frames[depth];
| |||
nf.parent_node = target;
| |||
nf.last_child = -1;
| |||
nf.container = vtype;
| |||
nf.state = (vtype == JSON_TYPE_OBJECT)
| |||
? (char)ST_EXPECT_KEY_OR_END
| |||
: (char)ST_EXPECT_VALUE_OR_END;
| |||
depth++;
| |||
}
| |||
else
| |||
{
| |||
f.state = (char)ST_EXPECT_COMMA_OR_END;
| |||
}
| |||
continue;
| |||
}
| |||
else if(state == ST_EXPECT_VALUE_OR_END)
| |||
{
| |||
if(c == ']')
| |||
{
| |||
depth--;
| |||
m_pos = p + 1;
| |||
if(depth > 0)
| |||
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
| |||
continue;
| |||
}
| |||
int target = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
| |||
if(target < 0)
| |||
{
| |||
SetError(JSON_ERR_OUT_OF_MEMORY, p);
| |||
return false;
| |||
}
| |||
f.last_child = target;
| |||
err = ParseValueInto(target);
| |||
if(err != JSON_OK) return false;
| |||
char vtype = m_nodes[target].type;
| |||
if(vtype == JSON_TYPE_OBJECT || vtype == JSON_TYPE_ARRAY)
| |||
{
| |||
if(depth >= JSON_MAX_DEPTH)
| |||
{
| |||
SetError(JSON_ERR_MAX_DEPTH, m_pos);
| |||
return false;
| |||
}
| |||
JsonFrame &nf = frames[depth];
| |||
nf.parent_node = target;
| |||
nf.last_child = -1;
| |||
nf.container = vtype;
| |||
nf.state = (vtype == JSON_TYPE_OBJECT)
| |||
? (char)ST_EXPECT_KEY_OR_END
| |||
: (char)ST_EXPECT_VALUE_OR_END;
| |||
depth++;
| |||
}
| |||
else
| |||
{
| |||
f.state = (char)ST_EXPECT_COMMA_OR_END;
| |||
}
| |||
continue;
| |||
}
| |||
else /* ST_EXPECT_COMMA_OR_END */
| |||
{
| |||
if(c == ',')
| |||
{
| |||
m_pos = p + 1;
| |||
f.state = (f.container == JSON_TYPE_OBJECT)
| |||
? (char)ST_EXPECT_KEY_OR_END
| |||
: (char)ST_EXPECT_VALUE_OR_END;
| |||
continue;
| |||
}
| |||
if(c == '}' || c == ']')
| |||
{
| |||
char expected = (f.container == JSON_TYPE_OBJECT) ? '}' : ']';
| |||
if(c != expected)
| |||
{
| |||
SetError(JSON_ERR_UNEXPECTED_CHAR, p);
| |||
return false;
| |||
}
| |||
depth--;
| |||
m_pos = p + 1;
| |||
if(depth > 0)
| |||
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
| |||
continue;
| |||
}
| |||
SetError(JSON_ERR_EXPECTED_COMMA, p);
| |||
return false;
| |||
}
| |||
}
| |||
| |||
// Reject trailing data.
| |||
int tail = SkipWs(m_pos);
| |||
if(tail < len)
| |||
{
| |||
SetError(JSON_ERR_TRAILING_DATA, tail);
| |||
return false;
| |||
}
| |||
m_pos = tail;
| |||
m_last_error = JSON_OK;
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Compare a stored UTF-8 key against a MQL5 string key. |
| |||
//| Returns true if they are equal. Handles multi-byte UTF-8 and |
| |||
//| UTF-16 surrogate pairs in the input key. |
| |||
//+------------------------------------------------------------------+
| |||
bool CJson::KeyMatch(const JsonNode &cn, const string key) const
| |||
{
| |||
int stored_len = cn.name_len;
| |||
int key_len = StringLen(key);
| |||
int si = 0; // index into stored UTF-8
| |||
int ki = 0; // index into MQL5 string (UTF-16 code units)
| |||
while(si < stored_len)
| |||
{
| |||
if(ki >= key_len) return false;
| |||
uchar b = m_strings[cn.name_off + si];
| |||
// Fast path: ASCII
| |||
if(b < 0x80)
| |||
{
| |||
ushort k = StringGetCharacter(key, ki);
| |||
if(k >= 0x80) return false;
| |||
if(b != (uchar)k) return false;
| |||
si++;
| |||
ki++;
| |||
continue;
| |||
}
| |||
// 2-byte UTF-8
| |||
if((b & 0xE0) == 0xC0)
| |||
{
| |||
if(si + 1 >= stored_len) return false;
| |||
ushort k = StringGetCharacter(key, ki);
| |||
if(k < 0x80 || k >= 0x800) return false;
| |||
int cp = ((b & 0x1F) << 6) | (m_strings[cn.name_off + si + 1] & 0x3F);
| |||
if(cp != (int)k) return false;
| |||
si += 2;
| |||
ki++;
| |||
continue;
| |||
}
| |||
// 3-byte UTF-8 (BMP)
| |||
if((b & 0xF0) == 0xE0)
| |||
{
| |||
if(si + 2 >= stored_len) return false;
| |||
ushort k = StringGetCharacter(key, ki);
| |||
if(k < 0x800) return false;
| |||
if(k >= 0xD800 && k <= 0xDFFF) return false; // unpaired surrogate
| |||
int cp = ((b & 0x0F) << 12)
| |||
| ((m_strings[cn.name_off + si + 1] & 0x3F) << 6)
| |||
| (m_strings[cn.name_off + si + 2] & 0x3F);
| |||
if(cp != (int)k) return false;
| |||
si += 3;
| |||
ki++;
| |||
continue;
| |||
}
| |||
// 4-byte UTF-8 (surrogate pair)
| |||
if((b & 0xF8) == 0xF0)
| |||
{
| |||
if(si + 3 >= stored_len) return false;
| |||
if(ki + 1 >= key_len) return false;
| |||
ushort k1 = StringGetCharacter(key, ki);
| |||
ushort k2 = StringGetCharacter(key, ki + 1);
| |||
if(!(k1 >= 0xD800 && k1 <= 0xDBFF && k2 >= 0xDC00 && k2 <= 0xDFFF))
| |||
return false;
| |||
int cp = 0x10000 + ((k1 - 0xD800) << 10) + (k2 - 0xDC00);
| |||
int cp2 = ((b & 0x07) << 18)
| |||
| ((m_strings[cn.name_off + si + 1] & 0x3F) << 12)
| |||
| ((m_strings[cn.name_off + si + 2] & 0x3F) << 6)
| |||
| (m_strings[cn.name_off + si + 3] & 0x3F);
| |||
if(cp != cp2) return false;
| |||
si += 4;
| |||
ki += 2;
| |||
continue;
| |||
}
| |||
return false; // invalid UTF-8 lead byte
| |||
}
| |||
return ki == key_len;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Internal: linear scan for a child with the given key |
| |||
//+------------------------------------------------------------------+
| |||
int CJson::NodeFindKey(int parent, const string key) const
| |||
{
| |||
if(parent < 0 || parent >= m_nodes_count)
| |||
return -1;
| |||
const JsonNode &p = m_nodes[parent];
| |||
if(p.type != JSON_TYPE_OBJECT)
| |||
return -1;
| |||
int c = p.child;
| |||
while(c >= 0)
| |||
{
| |||
const JsonNode cn = m_nodes[c];
| |||
if(KeyMatch(cn, key))
| |||
return c;
| |||
c = cn.next;
| |||
}
| |||
return -1;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Root-level wrappers |
| |||
//+------------------------------------------------------------------+
| |||
ENUM_JSON_TYPE CJson::GetType() const
| |||
{
| |||
if(m_root < 0) return JSON_TYPE_INVALID;
| |||
return (ENUM_JSON_TYPE)m_nodes[m_root].type;
| |||
}
| |||
bool CJson::IsObject() const { return GetType() == JSON_TYPE_OBJECT; }
| |||
bool CJson::IsArray() const { return GetType() == JSON_TYPE_ARRAY; }
| |||
bool CJson::IsString() const { return GetType() == JSON_TYPE_STRING; }
| |||
bool CJson::IsNumber() const { return GetType() == JSON_TYPE_NUMBER; }
| |||
bool CJson::IsBool() const { return GetType() == JSON_TYPE_BOOL; }
| |||
bool CJson::IsNull() const { return GetType() == JSON_TYPE_NULL; }
| |||
| |||
int CJson::Size() const
| |||
{
| |||
if(m_root < 0) return 0;
| |||
int c = m_nodes[m_root].child;
| |||
if(c < 0) return 0;
| |||
int n = 0;
| |||
while(c >= 0)
| |||
{
| |||
n++;
| |||
c = m_nodes[c].next;
| |||
}
| |||
return n;
| |||
}
| |||
| |||
bool CJson::Exists(const string key) const
| |||
{
| |||
if(m_root < 0) return false;
| |||
return NodeFindKey(m_root, key) >= 0;
| |||
}
| |||
| |||
string CJson::ToString(const string def) const
| |||
{
| |||
if(m_root < 0) return def;
| |||
const JsonNode &n = m_nodes[m_root];
| |||
if(n.type == JSON_TYPE_STRING)
| |||
{
| |||
if(n.val_len <= 0) return "";
| |||
return CharArrayToString(m_strings, n.val_off, n.val_len, CP_UTF8);
| |||
}
| |||
if(n.type == JSON_TYPE_NUMBER)
| |||
{
| |||
if(n.val_len <= 0) return def;
| |||
return CharArrayToString(Buffer, n.val_off, n.val_len);
| |||
}
| |||
return def;
| |||
}
| |||
double CJson::ToDouble(const double def) const
| |||
{
| |||
if(m_root < 0) return def;
| |||
const JsonNode &n = m_nodes[m_root];
| |||
if(n.type != JSON_TYPE_NUMBER) return def;
| |||
return n.num_value;
| |||
}
| |||
long CJson::ToInteger(const long def) const
| |||
{
| |||
if(m_root < 0) return def;
| |||
const JsonNode &n = m_nodes[m_root];
| |||
if(n.type != JSON_TYPE_NUMBER) return def;
| |||
return n.int_value;
| |||
}
| |||
bool CJson::ToBool(const bool def) const
| |||
{
| |||
if(m_root < 0) return def;
| |||
const JsonNode &n = m_nodes[m_root];
| |||
if(n.type != JSON_TYPE_BOOL) return def;
| |||
return n.bool_value;
| |||
}
| |||
| |||
CJsonNode CJson::operator[](const string key)
| |||
{
| |||
if(m_root < 0) return CJsonNode();
| |||
if(m_nodes[m_root].type != JSON_TYPE_OBJECT) return CJsonNode();
| |||
int found = NodeFindKey(m_root, key);
| |||
if(found < 0) return CJsonNode();
| |||
return CJsonNode((CJson*)GetPointer(this), found);
| |||
}
| |||
CJsonNode CJson::operator[](int index)
| |||
{
| |||
if(m_root < 0) return CJsonNode();
| |||
if(m_nodes[m_root].type != JSON_TYPE_ARRAY || index < 0) return CJsonNode();
| |||
int c = m_nodes[m_root].child;
| |||
while(c >= 0 && index > 0)
| |||
{
| |||
c = m_nodes[c].next;
| |||
index--;
| |||
}
| |||
if(c < 0) return CJsonNode();
| |||
return CJsonNode((CJson*)GetPointer(this), c);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Human-readable error message |
| |||
//+------------------------------------------------------------------+
| |||
string CJson::GetErrorMessage() const
| |||
{
| |||
switch(m_last_error)
| |||
{
| |||
case JSON_OK: return "No error";
| |||
case JSON_ERR_EMPTY: return "Empty document";
| |||
case JSON_ERR_UNEXPECTED_CHAR: return "Unexpected character";
| |||
case JSON_ERR_UNEXPECTED_END: return "Unexpected end of document";
| |||
case JSON_ERR_INVALID_NUMBER: return "Invalid number";
| |||
case JSON_ERR_INVALID_STRING: return "Invalid string";
| |||
case JSON_ERR_INVALID_ESCAPE: return "Invalid escape sequence";
| |||
case JSON_ERR_INVALID_UNICODE: return "Invalid unicode escape";
| |||
case JSON_ERR_EXPECTED_COLON: return "Expected ':'";
| |||
case JSON_ERR_EXPECTED_COMMA: return "Expected ','";
| |||
case JSON_ERR_EXPECTED_KEY: return "Expected object key";
| |||
case JSON_ERR_EXPECTED_VALUE: return "Expected value";
| |||
case JSON_ERR_MAX_DEPTH: return "Maximum nesting depth exceeded";
| |||
case JSON_ERR_INVALID_TOKEN: return "Invalid token";
| |||
case JSON_ERR_TRAILING_DATA: return "Trailing data after JSON document";
| |||
case JSON_ERR_INVALID_ROOT: return "Invalid root value";
| |||
case JSON_ERR_OUT_OF_MEMORY: return "Out of memory";
| |||
case JSON_ERR_INVALID_INDEX: return "Invalid index";
| |||
default: return "Unknown error";
| |||
}
| |||
}
| |||
| |||
#endif // JSON_MQH_INCLUDED
| |||
//+------------------------------------------------------------------+
|