1578 righe
54 KiB
MQL5
1578 righe
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
|
|
//+------------------------------------------------------------------+
|