+/// class myClass: public HashValue {
+/// public: int v;
+/// myClass(int a) { v = a;}
+/// };
+///
+/// // Create the objects as needed
+///
+/// myClass *a = new myClass(1);
+/// myClass *b = new myClass(2);
+/// myClass *c = new myClass(3);
+///
+/// // Then to insert into hash etc.
+///
+/// Hash* h = new Hash(193,true);
+/// // 'true' means when the hash will adopt the values and delete them when they are removed from the hash or when the hash is deleted.
+///
+/// h.hPut("a",a);
+/// h.hPut("b",b);
+/// h.hPut("c",c);
+///
+/// myClass *d = h.hGet("b");
+///
+/// etc.
+///
+/// // Iterate over hash
+/// HashLoop *l
+/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) {
+/// string key = l.key();
+/// MyClass *c = l.val();
+/// }
+/// delete l;
+///
+/// // Delete from hash - This will also delete 'a' because we set the 'adopt' flag on the hash.
+/// h.hDel("a");
+///
+/// //Delete the hash - this will also delete 'b' and 'c' because of the adopt flag.
+/// delete h;
+///
+class Hash : public HashValue {
+
+private:
+ /// Number of slots in the hashtable.
+ /// this should be approx number of elements to store. Depending on hash algorithm
+ /// it may optimally be a prime or a power of two etc. but probably not important
+ /// for MQL4 performance. A future optimisation might be to move the hashcode function to a DLL??
+ uint _hashSlots;
+
+ /// Number of elements at which hash will get resized.
+ int _resizeThreshold;
+
+ /// number of things in the hash
+ int _hashEntryCount;
+
+ /// an array of linked lists (HashEntry). one for each hash value.
+ /// To store an object against a string(key) - get the string hashcode, then insert pair (key,val) into the linked list for that hashcode.
+ /// To fetch an object against a string(key) - get the string hashcode, get linked-list at that hashcode index, then search for the key and return the val.
+ HashEntry* _buckets[];
+
+ /// If true the hash will free(delete) values as they are removed, or at cleanup.
+ bool _adoptValues;
+
+ int _errCode;
+ string _errText;
+
+ void init(uint size,bool adoptValues)
+ {
+ _hashSlots = 0;
+ _hashEntryCount = 0;
+ clearError();
+ setAdoptValues(adoptValues);
+
+ rehash(size);
+ }
+
+ // Hash table distribution is better when size is prime, eg if hash function procduces numbers
+ // that are multiples of x, then there may be grouping occuring around gcd(x,slots) gcd(2x,slots) etc
+ // using a prime size helps spread the distribution.
+ uint size2prime(uint size) {
+ int pmax=ArraySize(_primes);
+ for(int p=0 ; p
+/// HashLoop *l
+/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) {
+/// string key = l.key();
+/// MyClass *c = l.val();
+/// }
+/// delete l;
+///
+class HashLoop {
+ private:
+ uint _index;
+ HashEntry *_currentEntry;
+ Hash *_hash;
+
+ public:
+ /// Create iterator for a hash - move to first item
+ HashLoop(Hash *h) {
+ setHash(h);
+ }
+ ~HashLoop() {};
+
+ /// Clear current state and move to first item (if any).
+ void reset() {
+ _index=0;
+ _currentEntry = _hash.getEntry(_index);
+
+ // Move to first item
+ if (_currentEntry == NULL) {
+ next();
+ }
+ }
+
+ /// Change the hash over which to iterate.
+ void setHash(Hash *h) {
+ _hash = h;
+ reset();
+ }
+
+ /// Check if more items.
+ bool hasNext() {
+ bool ret = ( _currentEntry != NULL);
+ //config("hasNext=",ret);
+ return ret;
+ }
+
+ /// Move to next item.
+ void next() {
+
+ //config("next : index = ",_index);
+
+ // Advance
+ if (_currentEntry != NULL) {
+ _currentEntry = _currentEntry._next;
+ }
+
+ // Keep advancing if _currentEntry is null
+ while (_currentEntry==NULL) {
+ _index++;
+ if (_index >= _hash.getSlots() ) return ;
+ _currentEntry = _hash.getEntry(_index);
+ }
+ }
+
+ /// Return the key name of the current item.
+ string key() {
+ if (_currentEntry != NULL) {
+ return _currentEntry._key;
+ } else {
+ return NULL;
+ }
+ }
+
+ /// Return the value.
+ HashValue *val() {
+ if (_currentEntry != NULL) {
+ return _currentEntry._val;
+ } else {
+ return NULL;
+ }
+ }
+
+ /// Convenience functions for retriving int from a current HashInt entry
+ int valInt() {
+ return ((HashInt *)val()).getVal();
+ }
+
+ /// Convenience functions for retriving int from a current HashString entry
+ string valString() {
+ return ((HashString *)val()).getVal();
+ }
+
+ /// Convenience functions for retriving int from a current HashDouble entry
+ double valDouble() {
+ return ((HashDouble *)val()).getVal();
+ }
+
+ /// Convenience functions for retriving int from a current HashLong entry
+ long valLong() {
+ return ((HashLong *)val()).getVal();
+ }
+ /// Convenience functions for retriving int from a current HashDatetime entry
+ datetime valDatetime() {
+ return ((HashDatetime *)val()).getVal();
+ }
+};
+
+
+#endif
diff --git a/mq4/json.mqh b/mq4/json.mqh
new file mode 100644
index 00000000..8c391c72
--- /dev/null
+++ b/mq4/json.mqh
@@ -0,0 +1,1013 @@
+// $Id: json.mqh 118 2014-02-28 23:50:37Z ydrol $
+#ifndef YDROL_JSON_MQH
+#define YDROL_JSON_MQH
+
+#include
+/// string s = "{ \"firstName\": \"John\","+
+/// " \"lastName\": \"Smith\","+
+/// " \"age\": 25,"+
+/// " \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+
+/// " \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+
+/// " \"gender\":{ \"type\":\"male\" } }";
+///
+/// JSONParser *parser = new JSONParser();
+///
+/// JSONValue *jv = parser.parse(s);
+///
+/// if (jv == NULL) {
+///
+/// Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage());
+///
+/// } else {
+///
+/// Print("PARSED:"+jv.toString());
+///
+/// if (jv.isObject()) {
+///
+/// JSONObject *jo = jv;
+///
+/// // Direct access - will throw null pointer if wrong getter used.
+///
+/// Print("firstName:" + jo.getString("firstName"));
+/// Print("city:" + jo.getObject("address").getString("city"));
+/// Print("phone:" + jo.getArray("phoneNumber").getObject(0).getString("number"));
+///
+/// // Safe access in case JSON data is missing or different.
+///
+/// if (jo.getString("firstName",s) ) Print("firstName = "+s);
+///
+/// // Loop over object returning JSONValue
+///
+/// JSONIterator *it = new JSONIterator(jo);
+/// for( ; it.hasNext() ; it.next()) {
+/// Print("loop:"+it.key()+" = "+it.val().toString());
+/// }
+/// delete it;
+/// }
+/// delete jv;
+/// }
+/// delete parser;
+///
+
+class JSONParser
+ {
+private:
+ /// Current parse position
+ int _pos;
+ /// The input string is expanded into an array of ushort (wchar)
+ ushort _in[];
+ /// Length of string
+ int _len;
+ /// The original input string
+ string _instr;
+
+ int _errCode;
+ string _errMsg;
+
+ void setError(int code=1,string msg="unknown error")
+ {
+ _errCode|=code;
+ if(_errMsg=="")
+ {
+ _errMsg="JSONParser::Error "+msg;
+ } else {
+ _errMsg=StringConcatenate(_errMsg,"\n",msg);
+ }
+ }
+
+ /// Parse a JSON Object
+ JSONObject *parseObject()
+ {
+ JSONObject *o=new JSONObject();
+ skipSpace();
+ if(expect('{'))
+ {
+ while(_errCode==0)
+ {
+ skipSpace();
+ if(_in[_pos]!='"') break;
+
+ // Read the key
+ string key=parseString();
+
+ if(_errCode!=0 || key==NULL) break;
+
+ skipSpace();
+
+ if(!expect(':')) break;
+
+ // read the value
+ JSONValue *v = parseValue();
+ if(_errCode != 0 ) break;
+
+ o.put(key,v);
+
+ skipSpace();
+
+ if(!expectOptional(',')) break;
+ }
+ if(!expect('}'))
+ {
+ setError(2,"expected \" or } ");
+ }
+ }
+ if(_errCode!=0)
+ {
+ delete o;
+ o=NULL;
+ }
+ return o;
+ }
+
+ bool isDigit(ushort c)
+ {
+ return (c >= '0' && c <= '9' ) || c == '+' || c == '-';
+ }
+
+ bool isDoubleDigit(ushort c)
+ {
+ return (c >= '0' && c <= '9' ) || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E';
+ }
+
+ void skipSpace()
+ {
+ while(_in[_pos]==' ' || _in[_pos]=='\t' || _in[_pos]=='\r' || _in[_pos]=='\n')
+ {
+ if(_pos>=_len) break;
+ _pos++;
+ }
+ }
+
+ bool expect(ushort c)
+ {
+ bool ret=false;
+ if(c==_in[_pos])
+ {
+ _pos++;
+ ret=true;
+ } else {
+ setError(1,StringConcatenate("expected ",
+ ShortToString(c),"(",c,")",
+ " got ",ShortToString(_in[_pos]),"(",_in[_pos],")"));
+ }
+ return ret;
+ }
+
+ bool expectOptional(ushort c)
+ {
+ bool ret=false;
+ if(c==_in[_pos])
+ {
+ _pos++;
+ ret=true;
+ }
+ return ret;
+ }
+
+ string parseString()
+ {
+ string ret="";
+ if(expect('"'))
+ {
+ while(true)
+ {
+ int end=_pos;
+ while(end<_len && _in[end]!='"' && _in[end]!='\\')
+ {
+ end++;
+ }
+
+ if(end>=_len)
+ {
+ setError(2,"missing quote: end"+(string)end+":len"+(string)_len+":"+ShortToString(_in[_pos])+":"+StringSubstr(_instr,_pos,10)+"...");
+ break;
+ }
+ // Check if character was escaped.
+ // TODO \" \\ \/ \b \f \n \r \t \u0000
+ if(_in[end]=='\\')
+ {
+ // Add partial string and get more
+ ret=ret+StringSubstr(_instr,_pos,end-_pos);
+ end++;
+ if(end>=_len)
+ {
+ setError(4,"parse error after escape");
+ } else {
+ ushort c=0;
+ switch(_in[end])
+ {
+ case '"':
+ case '\\':
+ case '/':
+ c=_in[end];
+ break;
+ case 'b': c=8; break; // backspace - 8
+ case 'f': c=12; break; // form feed 12
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ default:
+ setError(3,"unknown escape");
+ }
+ if(c== 0) break;
+ ret = ret+ShortToString(c);
+ _pos= end+1;
+ }
+ } else if(_in[end]=='"') {
+ // End of string
+ ret=ret+StringSubstr(_instr,_pos,end-_pos);
+ _pos=end+1;
+ break;
+ }
+ }
+ }
+ if(_errCode!=0)
+ {
+ ret=NULL;
+ }
+ return ret;
+ }
+
+ JSONValue *parseValue()
+ {
+ JSONValue *ret=NULL;
+ skipSpace();
+ string s;
+
+ if(_in[_pos]=='[')
+ {
+
+ ret=(JSONValue*)parseArray();
+
+ } else if(_in[_pos]=='{') {
+
+ ret=(JSONValue*)parseObject();
+
+ } else if(_in[_pos]=='"') {
+
+ s=parseString();
+ ret=(JSONValue*)new JSONString(s);
+
+ } else if(isDoubleDigit(_in[_pos])) {
+ bool isDoubleOnly=false;
+ long l=0;
+ long sign;
+ // number
+ int i=_pos;
+
+ if(_in[_pos]=='-')
+ {
+ sign=-1;
+ _pos++;
+ } else if(_in[_pos]=='+') {
+ sign=1;
+ _pos++;
+ } else {
+ sign=1;
+ }
+
+ while(i<_len && isDigit(_in[i]))
+ {
+ l=l*10+(_in[i]-'0');
+ i++;
+ }
+ if(isDoubleDigit(_in[i]))
+ {
+ // Looks like a real number;
+ while(i<_len && isDoubleDigit(_in[i]))
+ {
+ i++;
+ }
+ s=StringSubstr(_instr,_pos,i-_pos);
+ double d=sign*StringToDouble(s);
+ ret=(JSONValue*)new JSONNumber(d); // Create a Number as double only
+ } else {
+ l=sign*l;
+ ret=(JSONValue*)new JSONNumber(l); // Create a Number as a long
+ }
+ _pos=i;
+
+ } else if(_in[_pos]=='t' && StringSubstr(_instr,_pos,4)=="true") {
+
+ ret=(JSONValue*)new JSONBool(true);
+ _pos+=4;
+
+ } else if(_in[_pos]=='f' && StringSubstr(_instr,_pos,5)=="false") {
+
+ ret=(JSONValue*)new JSONBool(false);
+ _pos+=5;
+
+ } else if(_in[_pos]=='n' && StringSubstr(_instr,_pos,4)=="null") {
+
+ ret=(JSONValue*)new JSONNull();
+ _pos+=4;
+
+ } else {
+
+ setError(3,"error parsing value at position "+(string)_pos);
+
+ }
+
+ if(_errCode!=0 && ret!=NULL)
+ {
+ delete ret;
+ ret=NULL;
+ }
+ return ret;
+ }
+
+ JSONArray *parseArray()
+ {
+ JSONArray *ret=new JSONArray();
+
+ int index=0;
+ skipSpace();
+ if(expect('['))
+ {
+ while(_errCode==0)
+ {
+ skipSpace();
+
+ // read the value
+ JSONValue *v = parseValue();
+ if(_errCode != 0) break;
+
+ if(!ret.put(index++,v))
+ {
+ setError(3,"memory error adding "+(string)index);
+ break;
+ }
+
+ skipSpace();
+
+ if(!expectOptional(',')) break;
+ }
+ if(!expect(']'))
+ {
+ setError(2,"list: expected , or ] ");
+ }
+ }
+
+ if(_errCode!=0)
+ {
+ delete ret;
+ ret=NULL;
+ }
+ return ret;
+ }
+public:
+ int getErrorCode()
+ {
+ return _errCode;
+ }
+ string getErrorMessage()
+ {
+ return _errMsg;
+ }
+ /// Parse a sequnce of characters and return a JSONValue.
+ JSONValue *parse(
+ string s ///< Serialized JSON text
+ )
+ {
+ int inLen;
+ JSONValue *ret=NULL;
+ StringTrimLeft(s);
+ StringTrimRight(s);
+
+ _instr=s;
+ _len=StringToShortArray(_instr,_in); // nul '0' is added to length
+ _pos= 0;
+ _errCode= 0;
+ _errMsg = "";
+ inLen=StringLen(_instr);
+ if(_len!=inLen+1 /* nul */)
+ {
+ setError(1,StringConcatenate("unable to create array ",inLen," got ",_len));
+ } else {
+ _len --;
+ ret=parseValue();
+ if(_errCode!=0)
+ {
+ _errMsg=StringConcatenate(_errMsg," at ",_pos," [",StringSubstr(_instr,_pos,10),"...]");
+ }
+ }
+ return ret;
+ }
+
+ };
+/// Class to iterate over a JSONObject (not a JSONArray)
+class JSONIterator
+ {
+private:
+ HashLoop*_l;
+
+public:
+ // Create iterator and move to first item
+ JSONIterator(JSONObject *jo)
+ {
+ _l=new HashLoop(jo.getHash());
+ }
+ ~JSONIterator()
+ {
+ delete _l;
+ }
+ // Check if more items
+ bool hasNext()
+ {
+ return _l.hasNext();
+ }
+
+ // Move to next item
+ void next()
+ {
+ _l.next();
+ }
+
+ // Return item
+ JSONValue *val()
+ {
+ return (JSONValue *) (_l.val());
+ }
+
+ // Return key
+ string key()
+ {
+ return _l.key();
+ }
+
+ };
+//+------------------------------------------------------------------+
+//| |
+//+------------------------------------------------------------------+
+void json_demo()
+ {
+ string s="{ \"firstName\": \"John\","+
+ " \"lastName\": \"Smith\","+
+ " \"age\": 25,"+
+ " \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+
+ " \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+
+ " \"gender\":{ \"type\":\"male\" } }";
+
+ JSONParser *parser=new JSONParser();
+ JSONValue *jv=parser.parse(s);
+ Print("json:");
+ if(jv==NULL)
+ {
+ Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage());
+ } else {
+ Print("PARSED:"+jv.toString());
+ if(jv.isObject())
+ {
+ JSONObject *jo=jv;
+
+ // Direct access - will throw null pointer if wrong getter used.
+ Print("firstName:"+jo.getString("firstName"));
+ Print("city:"+jo.getObject("address").getString("city"));
+ Print("phone:"+jo.getArray("phoneNumber").getObject(0).getString("number"));
+
+ // Safe access in case JSON data is missing or different.
+ if(jo.getString("firstName",s)) Print("firstName = "+s);
+
+ // Loop over object returning JSONValue
+ JSONIterator *it=new JSONIterator(jo);
+ for(; it.hasNext(); it.next())
+ {
+ Print("loop:"+it.key()+" = "+it.val().toString());
+ }
+ delete it;
+ }
+ delete jv;
+ }
+ delete parser;
+ }
+
+
+#endif
+//+------------------------------------------------------------------+