| //+------------------------------------------------------------------+
|
| //| 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
|
| //+------------------------------------------------------------------+
|