]> git.saurik.com Git - wxWidgets.git/blobdiff - contrib/src/stc/scintilla/src/PropSet.cxx
Patch #1106564, corrects possible 100% CPU load condition.
[wxWidgets.git] / contrib / src / stc / scintilla / src / PropSet.cxx
index 8582105eacc9266c02416b1fe70c0f07640276ce..d0a7f8b0f7f1c8ff6b69637e9a7d2aa7466d3b1f 100644 (file)
 // SciTE - Scintilla based Text Editor
-// PropSet.cxx - a java style properties file module
-// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
+/** @file PropSet.cxx
+ ** A Java style properties file module.
+ **/
+// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
 // The License.txt file describes the conditions under which this software may be distributed.
 
 // Maintain a dictionary of properties
 
 #include <stdlib.h>
 #include <string.h>
-#include <ctype.h>
 #include <stdio.h>
 
 #include "Platform.h"
 
 #include "PropSet.h"
 
+// The comparison and case changing functions here assume ASCII
+// or extended ASCII such as the normal Windows code page.
+
+static inline char MakeUpperCase(char ch) {
+       if (ch < 'a' || ch > 'z')
+               return ch;
+       else
+               return static_cast<char>(ch - 'a' + 'A');
+}
+
+static inline bool IsLetter(char ch) {
+       return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
+}
+
+inline bool IsASpace(unsigned int ch) {
+    return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
+}
+
+int CompareCaseInsensitive(const char *a, const char *b) {
+       while (*a && *b) {
+               if (*a != *b) {
+                       char upperA = MakeUpperCase(*a);
+                       char upperB = MakeUpperCase(*b);
+                       if (upperA != upperB)
+                               return upperA - upperB;
+               }
+               a++;
+               b++;
+       }
+       // Either *a or *b is nul
+       return *a - *b;
+}
+
+int CompareNCaseInsensitive(const char *a, const char *b, size_t len) {
+       while (*a && *b && len) {
+               if (*a != *b) {
+                       char upperA = MakeUpperCase(*a);
+                       char upperB = MakeUpperCase(*b);
+                       if (upperA != upperB)
+                               return upperA - upperB;
+               }
+               a++;
+               b++;
+               len--;
+       }
+       if (len == 0)
+               return 0;
+       else
+               // Either *a or *b is nul
+               return *a - *b;
+}
+
 bool EqualCaseInsensitive(const char *a, const char *b) {
-#if PLAT_GTK
-       return 0 == strcasecmp(a, b);
-#elif PLAT_WIN
-       return 0 == stricmp(a, b);
-#elif PLAT_WX
-       return 0 == wxStricmp(a, b);
-#endif
-}
-
-// Get a line of input. If end of line escaped with '\\' then continue reading.
-static bool GetFullLine(const char *&fpc, int &lenData, char *s, int len) {
-       bool continuation = true;
-       s[0] = '\0';
-       while ((len > 1) && lenData > 0) {
-               char ch = *fpc;
-               fpc++;
-               lenData--;
-               if ((ch == '\r') || (ch == '\n')) {
-                       if (!continuation) {
-                               if ((lenData > 0) && (ch == '\r') && ((*fpc) == '\n')) {
-                                       // munch the second half of a crlf
-                                       fpc++;
-                                       lenData--;
-                               }
-                               *s++ = '\0';
-                               return true;
-                       }
-               } else if ((ch == '\\') && (lenData > 0) && ((*fpc == '\r') || (*fpc == '\n'))) {
-                       continuation = true;
+       return 0 == CompareCaseInsensitive(a, b);
+}
+
+// Since the CaseInsensitive functions declared in SString
+// are implemented here, I will for now put the non-inline
+// implementations of the SString members here as well, so
+// that I can quickly see what effect this has.
+
+SString::SString(int i) : sizeGrowth(sizeGrowthDefault) {
+       char number[32];
+       sprintf(number, "%0d", i);
+       s = StringAllocate(number);
+       sSize = sLen = (s) ? strlen(s) : 0;
+}
+
+SString::SString(double d, int precision) : sizeGrowth(sizeGrowthDefault) {
+       char number[32];
+       sprintf(number, "%.*f", precision, d);
+       s = StringAllocate(number);
+       sSize = sLen = (s) ? strlen(s) : 0;
+}
+
+bool SString::grow(lenpos_t lenNew) {
+       while (sizeGrowth * 6 < lenNew) {
+               sizeGrowth *= 2;
+       }
+       char *sNew = new char[lenNew + sizeGrowth + 1];
+       if (sNew) {
+               if (s) {
+                       memcpy(sNew, s, sLen);
+                       delete []s;
+               }
+               s = sNew;
+               s[sLen] = '\0';
+               sSize = lenNew + sizeGrowth;
+       }
+       return sNew != 0;
+}
+
+SString &SString::assign(const char *sOther, lenpos_t sSize_) {
+       if (!sOther) {
+               sSize_ = 0;
+       } else if (sSize_ == measure_length) {
+               sSize_ = strlen(sOther);
+       }
+       if (sSize > 0 && sSize_ <= sSize) {     // Does not allocate new buffer if the current is big enough
+               if (s && sSize_) {
+                       memcpy(s, sOther, sSize_);
+               }
+               s[sSize_] = '\0';
+               sLen = sSize_;
+       } else {
+               delete []s;
+               s = StringAllocate(sOther, sSize_);
+               if (s) {
+                       sSize = sSize_; // Allow buffer bigger than real string, thus providing space to grow
+                       sLen = strlen(s);
                } else {
-                       continuation = false;
-                       *s++ = ch;
-                       *s = '\0';
-                       len--;
+                       sSize = sLen = 0;
                }
        }
-       return false;
+       return *this;
 }
 
-PropSet::PropSet() {
-       superPS = 0;
-       size = 10;
-       used = 0;
-       vals = new char * [size];
+bool SString::operator==(const SString &sOther) const {
+       if ((s == 0) && (sOther.s == 0))
+               return true;
+       if ((s == 0) || (sOther.s == 0))
+               return false;
+       return strcmp(s, sOther.s) == 0;
 }
 
-PropSet::~PropSet() {
-       superPS = 0;
-       Clear();
-       delete []vals;
+bool SString::operator==(const char *sOther) const {
+       if ((s == 0) && (sOther == 0))
+               return true;
+       if ((s == 0) || (sOther == 0))
+               return false;
+       return strcmp(s, sOther) == 0;
 }
 
-void PropSet::EnsureCanAddEntry() {
-       if (used >= size - 2) {
-               int newsize = size + 10;
-               char **newvals = new char * [newsize];
+SString SString::substr(lenpos_t subPos, lenpos_t subLen) const {
+       if (subPos >= sLen) {
+               return SString();                                       // return a null string if start index is out of bounds
+       }
+       if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+               subLen = sLen - subPos;         // can't substr past end of source string
+       }
+       return SString(s, subPos, subPos + subLen);
+}
 
-               for (int i = 0; i < used; i++) {
-                       newvals[i] = vals[i];
+SString &SString::lowercase(lenpos_t subPos, lenpos_t subLen) {
+       if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+               subLen = sLen - subPos;         // don't apply past end of string
+       }
+       for (lenpos_t i = subPos; i < subPos + subLen; i++) {
+               if (s[i] < 'A' || s[i] > 'Z')
+                       continue;
+               else
+                       s[i] = static_cast<char>(s[i] - 'A' + 'a');
+       }
+       return *this;
+}
+
+SString &SString::uppercase(lenpos_t subPos, lenpos_t subLen) {
+       if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+               subLen = sLen - subPos;         // don't apply past end of string
+       }
+       for (lenpos_t i = subPos; i < subPos + subLen; i++) {
+               if (s[i] < 'a' || s[i] > 'z')
+                       continue;
+               else
+                       s[i] = static_cast<char>(s[i] - 'a' + 'A');
+       }
+       return *this;
+}
+
+SString &SString::append(const char *sOther, lenpos_t sLenOther, char sep) {
+       if (!sOther) {
+               return *this;
+       }
+       if (sLenOther == measure_length) {
+               sLenOther = strlen(sOther);
+       }
+       int lenSep = 0;
+       if (sLen && sep) {      // Only add a separator if not empty
+               lenSep = 1;
+       }
+       lenpos_t lenNew = sLen + sLenOther + lenSep;
+       // Conservative about growing the buffer: don't do it, unless really needed
+       if ((lenNew < sSize) || (grow(lenNew))) {
+               if (lenSep) {
+                       s[sLen] = sep;
+                       sLen++;
+               }
+               memcpy(&s[sLen], sOther, sLenOther);
+               sLen += sLenOther;
+               s[sLen] = '\0';
+       }
+       return *this;
+}
+
+SString &SString::insert(lenpos_t pos, const char *sOther, lenpos_t sLenOther) {
+       if (!sOther || pos > sLen) {
+               return *this;
+       }
+       if (sLenOther == measure_length) {
+               sLenOther = strlen(sOther);
+       }
+       lenpos_t lenNew = sLen + sLenOther;
+       // Conservative about growing the buffer: don't do it, unless really needed
+       if ((lenNew < sSize) || grow(lenNew)) {
+               lenpos_t moveChars = sLen - pos + 1;
+               for (lenpos_t i = moveChars; i > 0; i--) {
+                       s[pos + sLenOther + i - 1] = s[pos + i - 1];
+               }
+               memcpy(s + pos, sOther, sLenOther);
+               sLen = lenNew;
+       }
+       return *this;
+}
+
+/**
+ * Remove @a len characters from the @a pos position, included.
+ * Characters at pos + len and beyond replace characters at pos.
+ * If @a len is 0, or greater than the length of the string
+ * starting at @a pos, the string is just truncated at @a pos.
+ */
+void SString::remove(lenpos_t pos, lenpos_t len) {
+       if (pos >= sLen) {
+               return;
+       }
+       if (len < 1 || pos + len >= sLen) {
+               s[pos] = '\0';
+               sLen = pos;
+       } else {
+               for (lenpos_t i = pos; i < sLen - len + 1; i++) {
+                       s[i] = s[i+len];
+               }
+               sLen -= len;
+       }
+}
+
+bool SString::startswith(const char *prefix) {
+       lenpos_t lenPrefix = strlen(prefix);
+       if (lenPrefix > sLen) {
+               return false;
+       }
+       return strncmp(s, prefix, lenPrefix) == 0;
+}
+
+bool SString::endswith(const char *suffix) {
+       lenpos_t lenSuffix = strlen(suffix);
+       if (lenSuffix > sLen) {
+               return false;
+       }
+       return strncmp(s + sLen - lenSuffix, suffix, lenSuffix) == 0;
+}
+
+int SString::search(const char *sFind, lenpos_t start) const {
+       if (start < sLen) {
+               const char *sFound = strstr(s + start, sFind);
+               if (sFound) {
+                       return sFound - s;
                }
-               delete []vals;
-               vals = newvals;
-               size = newsize;
        }
+       return -1;
+}
+
+int SString::substitute(char chFind, char chReplace) {
+       int c = 0;
+       char *t = s;
+       while (t) {
+               t = strchr(t, chFind);
+               if (t) {
+                       *t = chReplace;
+                       t++;
+                       c++;
+               }
+       }
+       return c;
+}
+
+int SString::substitute(const char *sFind, const char *sReplace) {
+       int c = 0;
+       lenpos_t lenFind = strlen(sFind);
+       lenpos_t lenReplace = strlen(sReplace);
+       int posFound = search(sFind);
+       while (posFound >= 0) {
+               remove(posFound, lenFind);
+               insert(posFound, sReplace, lenReplace);
+               posFound = search(sFind, posFound + lenReplace);
+               c++;
+       }
+       return c;
+}
+
+char *SContainer::StringAllocate(lenpos_t len) {
+       if (len != measure_length) {
+               return new char[len + 1];
+       } else {
+               return 0;
+       }
+}
+
+char *SContainer::StringAllocate(const char *s, lenpos_t len) {
+       if (s == 0) {
+               return 0;
+       }
+       if (len == measure_length) {
+               len = strlen(s);
+       }
+       char *sNew = new char[len + 1];
+       if (sNew) {
+               memcpy(sNew, s, len);
+               sNew[len] = '\0';
+       }
+       return sNew;
+}
+
+// End SString functions
+
+PropSet::PropSet() {
+       superPS = 0;
+       for (int root = 0; root < hashRoots; root++)
+               props[root] = 0;
+}
+
+PropSet::~PropSet() {
+       superPS = 0;
+       Clear();
 }
 
-void PropSet::Set(const char *key, const char *val) {
-       EnsureCanAddEntry();
-       for (int i = 0; i < used; i += 2) {
-               if (EqualCaseInsensitive(vals[i], key)) {
+void PropSet::Set(const char *key, const char *val, int lenKey, int lenVal) {
+       if (!*key)      // Empty keys are not supported
+               return;
+       if (lenKey == -1)
+               lenKey = static_cast<int>(strlen(key));
+       if (lenVal == -1)
+               lenVal = static_cast<int>(strlen(val));
+       unsigned int hash = HashString(key, lenKey);
+       for (Property *p = props[hash % hashRoots]; p; p = p->next) {
+               if ((hash == p->hash) &&
+                       ((strlen(p->key) == static_cast<unsigned int>(lenKey)) &&
+                               (0 == strncmp(p->key, key, lenKey)))) {
                        // Replace current value
-                       delete [](vals[i + 1]);
-                       vals[i + 1] = StringDup(val);
+                       delete [](p->val);
+                       p->val = StringDup(val, lenVal);
                        return;
                }
        }
        // Not found
-       vals[used++] = StringDup(key);
-       vals[used++] = StringDup(val);
+       Property *pNew = new Property;
+       if (pNew) {
+               pNew->hash = hash;
+               pNew->key = StringDup(key, lenKey);
+               pNew->val = StringDup(val, lenVal);
+               pNew->next = props[hash % hashRoots];
+               props[hash % hashRoots] = pNew;
+       }
+}
+
+void PropSet::Set(const char *keyVal) {
+       while (IsASpace(*keyVal))
+               keyVal++;
+       const char *endVal = keyVal;
+       while (*endVal && (*endVal != '\n'))
+               endVal++;
+       const char *eqAt = strchr(keyVal, '=');
+       if (eqAt) {
+               Set(keyVal, eqAt + 1, eqAt-keyVal, endVal - eqAt - 1);
+       } else if (*keyVal) {   // No '=' so assume '=1'
+               Set(keyVal, "1", endVal-keyVal, 1);
+       }
 }
 
-void PropSet::Set(char *keyval) {
-       char *eqat = strchr(keyval, '=');
-       if (eqat) {
-               *eqat = '\0';
-               Set(keyval, eqat + 1);
-               *eqat = '=';
+void PropSet::SetMultiple(const char *s) {
+       const char *eol = strchr(s, '\n');
+       while (eol) {
+               Set(s);
+               s = eol + 1;
+               eol = strchr(s, '\n');
        }
+       Set(s);
 }
 
 SString PropSet::Get(const char *key) {
-       for (int i = 0; i < used; i += 2) {
-               if (EqualCaseInsensitive(vals[i], key)) {
-                       return vals[i + 1];
+       unsigned int hash = HashString(key, strlen(key));
+       for (Property *p = props[hash % hashRoots]; p; p = p->next) {
+               if ((hash == p->hash) && (0 == strcmp(p->key, key))) {
+                       return p->val;
                }
        }
        if (superPS) {
@@ -119,17 +408,65 @@ SString PropSet::Get(const char *key) {
        }
 }
 
-int PropSet::GetInt(const char *key, int defaultValue) {
+bool PropSet::IncludesVar(const char *value, const char *key) {
+       const char *var = strstr(value, "$(");
+       while (var) {
+               if (isprefix(var + 2, key) && (var[2 + strlen(key)] == ')')) {
+                       // Found $(key) which would lead to an infinite loop so exit
+                       return true;
+               }
+               var = strstr(var + 2, ")");
+               if (var)
+                       var = strstr(var + 1, "$(");
+       }
+       return false;
+}
+
+SString PropSet::GetExpanded(const char *key) {
        SString val = Get(key);
+       if (IncludesVar(val.c_str(), key))
+               return val;
+       return Expand(val.c_str());
+}
+
+SString PropSet::Expand(const char *withVars, int maxExpands) {
+       char *base = StringDup(withVars);
+       char *cpvar = strstr(base, "$(");
+       while (cpvar && (maxExpands > 0)) {
+               char *cpendvar = strchr(cpvar, ')');
+               if (!cpendvar)
+                       break;
+               int lenvar = cpendvar - cpvar - 2;      // Subtract the $()
+               char *var = StringDup(cpvar + 2, lenvar);
+               SString val = Get(var);
+               if (IncludesVar(val.c_str(), var))
+                       break;
+               size_t newlenbase = strlen(base) + val.length() - lenvar;
+               char *newbase = new char[newlenbase];
+               strncpy(newbase, base, cpvar - base);
+               strcpy(newbase + (cpvar - base), val.c_str());
+               strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
+               delete []var;
+               delete []base;
+               base = newbase;
+               cpvar = strstr(base, "$(");
+               maxExpands--;
+       }
+       SString sret = base;
+       delete []base;
+       return sret;
+}
+
+int PropSet::GetInt(const char *key, int defaultValue) {
+       SString val = GetExpanded(key);
        if (val.length())
-               return Get(key).value();
-       else
-               return defaultValue;
+               return val.value();
+       return defaultValue;
 }
 
 bool isprefix(const char *target, const char *prefix) {
        while (*target && *prefix) {
-               if (toupper(*target) != toupper(*prefix))
+               if (*target != *prefix)
                        return false;
                target++;
                prefix++;
@@ -140,67 +477,67 @@ bool isprefix(const char *target, const char *prefix) {
                return true;
 }
 
-bool issuffix(const char *target, const char *suffix) {
-       int lentarget = strlen(target);
-       int lensuffix = strlen(suffix);
+static bool IsSuffixCaseInsensitive(const char *target, const char *suffix) {
+       size_t lentarget = strlen(target);
+       size_t lensuffix = strlen(suffix);
        if (lensuffix > lentarget)
                return false;
-       for (int i = lensuffix - 1; i >= 0; i--) {
-               if (toupper(target[i + lentarget - lensuffix]) != toupper(suffix[i]))
+       for (int i = static_cast<int>(lensuffix) - 1; i >= 0; i--) {
+               if (MakeUpperCase(target[i + lentarget - lensuffix]) !=
+                       MakeUpperCase(suffix[i]))
                        return false;
        }
        return true;
 }
 
 SString PropSet::GetWild(const char *keybase, const char *filename) {
-       for (int i = 0; i < used; i += 2) {
-               if (isprefix(vals[i], keybase)) {
-                       char *orgkeyfile = vals[i] + strlen(keybase);
-                       char *keyfile = NULL;
-
-                       if (strstr(orgkeyfile, "$(") == orgkeyfile) {
-                               char *cpendvar = strchr(orgkeyfile, ')');
-                               if (cpendvar) {
-                                       int lenvar = cpendvar - orgkeyfile - 2;         // Subtract the $()
-                                       char *var = static_cast<char *>(malloc(lenvar + 1));
-                                       strncpy(var, orgkeyfile + 2, lenvar);
-                                       var[lenvar] = '\0';
-                                       SString s = Get(var);
-                                       free(var);
-                                       keyfile = strdup(s.c_str());
+       for (int root = 0; root < hashRoots; root++) {
+               for (Property *p = props[root]; p; p = p->next) {
+                       if (isprefix(p->key, keybase)) {
+                               char * orgkeyfile = p->key + strlen(keybase);
+                               char *keyfile = NULL;
+
+                               if (strstr(orgkeyfile, "$(") == orgkeyfile) {
+                                       char *cpendvar = strchr(orgkeyfile, ')');
+                                       if (cpendvar) {
+                                               *cpendvar = '\0';
+                                               SString s = GetExpanded(orgkeyfile + 2);
+                                               *cpendvar = ')';
+                                               keyfile = StringDup(s.c_str());
+                                       }
                                }
-                       }
-                       char *keyptr = keyfile;
-
-                       if (keyfile == NULL)
-                               keyfile = orgkeyfile;
-
-                       for (; ; ) {
-                               char *del = strchr(keyfile, ';');
-                               if (del == NULL)
-                                       del = keyfile + strlen(keyfile);
-                               char delchr = *del;
-                               *del = '\0';
-                               if (*keyfile == '*') {
-                                       if (issuffix(filename, keyfile + 1)) {
+                               char *keyptr = keyfile;
+
+                               if (keyfile == NULL)
+                                       keyfile = orgkeyfile;
+
+                               for (;;) {
+                                       char *del = strchr(keyfile, ';');
+                                       if (del == NULL)
+                                               del = keyfile + strlen(keyfile);
+                                       char delchr = *del;
+                                       *del = '\0';
+                                       if (*keyfile == '*') {
+                                               if (IsSuffixCaseInsensitive(filename, keyfile + 1)) {
+                                                       *del = delchr;
+                                                       delete []keyptr;
+                                                       return p->val;
+                                               }
+                                       } else if (0 == strcmp(keyfile, filename)) {
                                                *del = delchr;
-                                               free(keyptr);
-                                               return vals[i + 1];
+                                               delete []keyptr;
+                                               return p->val;
                                        }
-                               } else if (EqualCaseInsensitive(keyfile, filename)) {
+                                       if (delchr == '\0')
+                                               break;
                                        *del = delchr;
-                                       free(keyptr);
-                                       return vals[i + 1];
+                                       keyfile = del + 1;
                                }
-                               if (delchr == '\0')
-                                       break;
-                               *del = delchr;
-                               keyfile = del + 1;
-                       }
-                       free(keyptr);
+                               delete []keyptr;
 
-                       if (EqualCaseInsensitive(vals[i], keybase)) {
-                               return vals[i + 1];
+                               if (0 == strcmp(p->key, keybase)) {
+                                       return p->val;
+                               }
                        }
                }
        }
@@ -212,18 +549,19 @@ SString PropSet::GetWild(const char *keybase, const char *filename) {
        }
 }
 
+// GetNewExpand does not use Expand as it has to use GetWild with the filename for each
+// variable reference found.
 SString PropSet::GetNewExpand(const char *keybase, const char *filename) {
        char *base = StringDup(GetWild(keybase, filename).c_str());
        char *cpvar = strstr(base, "$(");
-       while (cpvar) {
+       int maxExpands = 1000;  // Avoid infinite expansion of recursive definitions
+       while (cpvar && (maxExpands > 0)) {
                char *cpendvar = strchr(cpvar, ')');
                if (cpendvar) {
                        int lenvar = cpendvar - cpvar - 2;      // Subtract the $()
-                       char *var = new char[lenvar + 1];
-                       strncpy(var, cpvar + 2, lenvar);
-                       var[lenvar] = '\0';
+                       char *var = StringDup(cpvar + 2, lenvar);
                        SString val = GetWild(var, filename);
-                       int newlenbase = strlen(base) + val.length() - lenvar;
+                       size_t newlenbase = strlen(base) + val.length() - lenvar;
                        char *newbase = new char[newlenbase];
                        strncpy(newbase, base, cpvar - base);
                        strcpy(newbase + (cpvar - base), val.c_str());
@@ -233,6 +571,7 @@ SString PropSet::GetNewExpand(const char *keybase, const char *filename) {
                        base = newbase;
                }
                cpvar = strstr(base, "$(");
+               maxExpands--;
        }
        SString sret = base;
        delete []base;
@@ -240,66 +579,126 @@ SString PropSet::GetNewExpand(const char *keybase, const char *filename) {
 }
 
 void PropSet::Clear() {
-       for (int i = 0; i < used; i++) {
-               delete [](vals[i]);
-               vals[i] = 0;
+       for (int root = 0; root < hashRoots; root++) {
+               Property *p = props[root];
+               while (p) {
+                       Property *pNext = p->next;
+                       p->hash = 0;
+                       delete []p->key;
+                       p->key = 0;
+                       delete []p->val;
+                       p->val = 0;
+                       delete p;
+                       p = pNext;
+               }
+               props[root] = 0;
        }
-       used = 0;
 }
 
-void PropSet::ReadFromMemory(const char *data, int len) {
-       if (len > 0) {
-               const char *pd = data;
-               char linebuf[60000];
-               while (GetFullLine(pd, len, linebuf, sizeof(linebuf))) {
-                       if (isalpha(linebuf[0]))
-                               Set(linebuf);
+char *PropSet::ToString() {
+       size_t len=0;
+       for (int r = 0; r < hashRoots; r++) {
+               for (Property *p = props[r]; p; p = p->next) {
+                       len += strlen(p->key) + 1;
+                       len += strlen(p->val) + 1;
+               }
+       }
+       if (len == 0)
+               len = 1;        // Return as empty string
+       char *ret = new char [len];
+       if (ret) {
+               char *w = ret;
+               for (int root = 0; root < hashRoots; root++) {
+                       for (Property *p = props[root]; p; p = p->next) {
+                               strcpy(w, p->key);
+                               w += strlen(p->key);
+                               *w++ = '=';
+                               strcpy(w, p->val);
+                               w += strlen(p->val);
+                               *w++ = '\n';
+                       }
                }
-               // If there is a final line:
-               if (isalpha(linebuf[0]))
-                       Set(linebuf);
+               ret[len-1] = '\0';
        }
+       return ret;
 }
 
-void PropSet::Read(const char *filename) {
-       //printf("Opening properties <%s>\n", filename);
-       Clear();
-       char propsData[60000];
-       FILE *rcfile = fopen(filename, "rb");
-       if (rcfile) {
-               int lenFile = fread(propsData, 1, sizeof(propsData), rcfile);
-               fclose(rcfile);
-               ReadFromMemory(propsData, lenFile);
-       } else {
-               //printf("Could not open <%s>\n", filename);
+/**
+ * Initiate enumeration.
+ */
+bool PropSet::GetFirst(char **key, char **val) {
+       for (int i = 0; i < hashRoots; i++) {
+               for (Property *p = props[i]; p; p = p->next) {
+                       if (p) {
+                               *key = p->key;
+                               *val = p->val;
+                               enumnext = p->next; // GetNext will begin here ...
+                               enumhash = i;             // ... in this block
+                               return true;
+                       }
+               }
        }
+       return false;
 }
 
-static bool iswordsep(char ch, bool onlyLineEnds) {
-       if (!isspace(ch))
-               return false;
-       if (!onlyLineEnds)
-               return true;
-       return ch == '\r' || ch == '\n';
+/**
+ * Continue enumeration.
+ */
+bool PropSet::GetNext(char ** key, char ** val) {
+       bool firstloop = true;
+
+       // search begins where we left it : in enumhash block
+       for (int i = enumhash; i < hashRoots; i++) {
+               if (!firstloop)
+                       enumnext = props[i]; // Begin with first property in block
+               // else : begin where we left
+               firstloop = false;
+
+               for (Property *p = enumnext; p; p = p->next) {
+                       if (p) {
+                               *key = p->key;
+                               *val = p->val;
+                               enumnext = p->next; // for GetNext
+                               enumhash = i;
+                               return true;
+                       }
+               }
+       }
+       return false;
 }
 
-// Creates an array that points into each word in the string and puts \0 terminators
-// after each word.
-static char **ArrayFromWordList(char *wordlist, bool onlyLineEnds = false) {
-       char prev = '\n';
+/**
+ * Creates an array that points into each word in the string and puts \0 terminators
+ * after each word.
+ */
+static char **ArrayFromWordList(char *wordlist, int *len, bool onlyLineEnds = false) {
+       int prev = '\n';
        int words = 0;
+       // For rapid determination of whether a character is a separator, build
+       // a look up table.
+       bool wordSeparator[256];
+       for (int i=0;i<256; i++) {
+               wordSeparator[i] = false;
+       }
+       wordSeparator['\r'] = true;
+       wordSeparator['\n'] = true;
+       if (!onlyLineEnds) {
+               wordSeparator[' '] = true;
+               wordSeparator['\t'] = true;
+       }
        for (int j = 0; wordlist[j]; j++) {
-               if (!iswordsep(wordlist[j], onlyLineEnds) && iswordsep(prev, onlyLineEnds))
+               int curr = static_cast<unsigned char>(wordlist[j]);
+               if (!wordSeparator[curr] && wordSeparator[prev])
                        words++;
-               prev = wordlist[j];
+               prev = curr;
        }
-       char **keywords = new char * [words + 1];
+       char **keywords = new char *[words + 1];
        if (keywords) {
                words = 0;
                prev = '\0';
-               int len = strlen(wordlist);
-               for (int k = 0; k < len; k++) {
-                       if (!iswordsep(wordlist[k], onlyLineEnds)) {
+               size_t slen = strlen(wordlist);
+               for (size_t k = 0; k < slen; k++) {
+                       if (!wordSeparator[static_cast<unsigned char>(wordlist[k])]) {
                                if (!prev) {
                                        keywords[words] = &wordlist[k];
                                        words++;
@@ -309,25 +708,33 @@ static char **ArrayFromWordList(char *wordlist, bool onlyLineEnds = false) {
                        }
                        prev = wordlist[k];
                }
-               keywords[words] = &wordlist[len];
+               keywords[words] = &wordlist[slen];
+               *len = words;
+       } else {
+               *len = 0;
        }
        return keywords;
 }
 
 void WordList::Clear() {
        if (words) {
-               delete []words;
                delete []list;
+               delete []words;
+               delete []wordsNoCase;
        }
        words = 0;
+       wordsNoCase = 0;
        list = 0;
        len = 0;
+       sorted = false;
 }
 
 void WordList::Set(const char *s) {
-       len = 0;
        list = StringDup(s);
-       words = ArrayFromWordList(list, onlyLineEnds);
+       sorted = false;
+       words = ArrayFromWordList(list, &len, onlyLineEnds);
+       wordsNoCase = new char * [len + 1];
+       memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
 }
 
 char *WordList::Allocate(int size) {
@@ -337,46 +744,36 @@ char *WordList::Allocate(int size) {
 }
 
 void WordList::SetFromAllocated() {
-       len = 0;
-       words = ArrayFromWordList(list, onlyLineEnds);
-}
-
-// Shell sort based upon public domain C implementation by Raymond Gardner 1991
-// Used here because of problems with mingw qsort.
-static void SortWordList(char **words, unsigned int len) {
-       unsigned int gap = len / 2;
-
-       while (gap > 0) {
-               unsigned int i = gap;
-               while (i < len) {
-                       unsigned int j = i;
-                       char **a = words + j;
-                       do {
-                               j -= gap;
-                               char **b = a;
-                               a -= gap;
-                               if (strcmp(*a, *b) > 0) {
-                                       char *tmp = *a;
-                                       *a = *b;
-                                       *b = tmp;
-                               } else {
-                                       break;
-                               }
-                       } while (j >= gap);
-                       i++;
-               }
-               gap = gap / 2;
-       }
+       sorted = false;
+       words = ArrayFromWordList(list, &len, onlyLineEnds);
+       wordsNoCase = new char * [len + 1];
+       memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
+}
+
+int cmpString(const void *a1, const void *a2) {
+       // Can't work out the correct incantation to use modern casts here
+       return strcmp(*(char**)(a1), *(char**)(a2));
+}
+
+int cmpStringNoCase(const void *a1, const void *a2) {
+       // Can't work out the correct incantation to use modern casts here
+       return CompareCaseInsensitive(*(char**)(a1), *(char**)(a2));
+}
+
+static void SortWordList(char **words, char **wordsNoCase, unsigned int len) {
+       qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
+             cmpString);
+       qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
+             cmpStringNoCase);
 }
 
 bool WordList::InList(const char *s) {
        if (0 == words)
                return false;
-       if (len == 0) {
-               for (int i = 0; words[i][0]; i++)
-                       len++;
-               SortWordList(words, len);
-               for (int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
+       if (!sorted) {
+               sorted = true;
+               SortWordList(words, wordsNoCase, len);
+               for (unsigned int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
                        starts[k] = -1;
                for (int l = len - 1; l >= 0; l--) {
                        unsigned char indexChar = words[l][0];
@@ -400,5 +797,234 @@ bool WordList::InList(const char *s) {
                        j++;
                }
        }
+       j = starts['^'];
+       if (j >= 0) {
+               while (words[j][0] == '^') {
+                       const char *a = words[j] + 1;
+                       const char *b = s;
+                       while (*a && *a == *b) {
+                               a++;
+                               b++;
+                       }
+                       if (!*a)
+                               return true;
+                       j++;
+               }
+       }
        return false;
 }
+
+/**
+ * Returns an element (complete) of the wordlist array which has
+ * the same beginning as the passed string.
+ * The length of the word to compare is passed too.
+ * Letter case can be ignored or preserved (default).
+ */
+const char *WordList::GetNearestWord(const char *wordStart, int searchLen /*= -1*/, bool ignoreCase /*= false*/, SString wordCharacters /*='/0' */, int wordIndex /*= -1 */) {
+       int start = 0; // lower bound of the api array block to search
+       int end = len - 1; // upper bound of the api array block to search
+       int pivot; // index of api array element just being compared
+       int cond; // comparison result (in the sense of strcmp() result)
+       const char *word; // api array element just being compared
+
+       if (0 == words)
+               return NULL;
+       if (!sorted) {
+               sorted = true;
+               SortWordList(words, wordsNoCase, len);
+       }
+       if (ignoreCase) {
+               while (start <= end) { // binary searching loop
+                       pivot = (start + end) >> 1;
+                       word = wordsNoCase[pivot];
+                       cond = CompareNCaseInsensitive(wordStart, word, searchLen);
+                       if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+                               // Found a word in a binary fashion. Now checks if a specific index was requested
+                               if (wordIndex < 0)
+                                       return word; // result must not be freed with free()
+
+                               // Finds first word in a series of equal words
+                               int first = pivot;
+                               end = pivot - 1;
+                               while (start <= end) {
+                                       pivot = (start + end) >> 1;
+                                       word = wordsNoCase[pivot];
+                                       cond = CompareNCaseInsensitive(wordStart, word, searchLen);
+                                       if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+                                               // Found another word
+                                               first = pivot;
+                                               end = pivot - 1;
+                                       }
+                                       else if (cond > 0)
+                                               start = pivot + 1;
+                                       else if (cond <= 0)
+                                               break;
+                               }
+
+                               // Gets the word at the requested index
+                               word = wordsNoCase[first + wordIndex];
+                               return word;
+                       }
+                       else if (cond > 0)
+                               start = pivot + 1;
+                       else if (cond <= 0)
+                               end = pivot - 1;
+               }
+       } else { // preserve the letter case
+               while (start <= end) { // binary searching loop
+                       pivot = (start + end) >> 1;
+                       word = words[pivot];
+                       cond = strncmp(wordStart, word, searchLen);
+                       if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+                               // Found a word in a binary fashion. Now checks if a specific index was requested
+                               if (wordIndex < 0)
+                                       return word; // result must not be freed with free()
+
+                               // Finds first word in a series of equal words
+                               int first = pivot;
+                               end = pivot - 1;
+                               while (start <= end) {
+                                       pivot = (start + end) >> 1;
+                                       word = words[pivot];
+                                       cond = strncmp(wordStart, word, searchLen);
+                                       if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+                                               // Found another word
+                                               first = pivot;
+                                               end = pivot - 1;
+                                       }
+                                       else if (cond > 0)
+                                               start = pivot + 1;
+                                       else if (cond <= 0)
+                                               break;
+                               }
+
+                               // Gets the word at the requested index
+                               word = words[first + wordIndex];
+                               return word;
+                       }
+                       else if (cond > 0)
+                               start = pivot + 1;
+                       else if (cond <= 0)
+                               end = pivot - 1;
+               }
+       }
+       return NULL;
+}
+
+/**
+ * Find the length of a 'word' which is actually an identifier in a string
+ * which looks like "identifier(..." or "identifier:" or "identifier" and where
+ * there may be extra spaces after the identifier that should not be
+ * counted in the length.
+ */
+static unsigned int LengthWord(const char *word, char otherSeparator) {
+       // Find a '(', or ':'. If that fails go to the end of the string.
+       const char *endWord = strchr(word, '(');
+       if (!endWord)
+               endWord = strchr(word, ':');
+       if (!endWord && otherSeparator)
+               endWord = strchr(word, otherSeparator);
+       if (!endWord)
+               endWord = word + strlen(word);
+       // Last case always succeeds so endWord != 0
+
+       // Drop any space characters.
+       if (endWord > word) {
+               endWord--;      // Back from the '(', ':', or '\0'
+               // Move backwards over any spaces
+               while ((endWord > word) && (IsASpace(*endWord))) {
+                       endWord--;
+               }
+       }
+       return endWord - word;
+}
+
+/**
+ * Returns elements (first words of them) of the wordlist array which have
+ * the same beginning as the passed string.
+ * The length of the word to compare is passed too.
+ * Letter case can be ignored or preserved (default).
+ * If there are more words meeting the condition they are returned all of
+ * them in the ascending order separated with spaces.
+ *
+ * NOTE: returned buffer has to be freed with delete[].
+ */
+char *WordList::GetNearestWords(
+    const char *wordStart,
+    int searchLen /*= -1*/,
+    bool ignoreCase /*= false*/,
+    char otherSeparator /*= '\0'*/,
+    bool exactLen /*=false*/) {
+       unsigned int wordlen; // length of the word part (before the '(' brace) of the api array element
+       SString wordsNear;
+       wordsNear.setsizegrowth(1000);
+       int start = 0; // lower bound of the api array block to search
+       int end = len - 1; // upper bound of the api array block to search
+       int pivot; // index of api array element just being compared
+       int cond; // comparison result (in the sense of strcmp() result)
+
+       if (0 == words)
+               return NULL;
+       if (!sorted) {
+               sorted = true;
+               SortWordList(words, wordsNoCase, len);
+       }
+       if (ignoreCase) {
+               while (start <= end) { // Binary searching loop
+                       pivot = (start + end) / 2;
+                       cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
+                       if (!cond) {
+                               // Find first match
+                               while ((pivot > start) &&
+                                       (0 == CompareNCaseInsensitive(wordStart,
+                                               wordsNoCase[pivot-1], searchLen))) {
+                                       --pivot;
+                               }
+                               // Grab each match
+                               while ((pivot <= end) &&
+                                       (0 == CompareNCaseInsensitive(wordStart,
+                                               wordsNoCase[pivot], searchLen))) {
+                                       wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
+                                       if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
+                                               break;
+                                       wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
+                                       ++pivot;
+                               }
+                               return wordsNear.detach();
+                       } else if (cond < 0) {
+                               end = pivot - 1;
+                       } else if (cond > 0) {
+                               start = pivot + 1;
+                       }
+               }
+       } else {        // Preserve the letter case
+               while (start <= end) { // Binary searching loop
+                       pivot = (start + end) / 2;
+                       cond = strncmp(wordStart, words[pivot], searchLen);
+                       if (!cond) {
+                               // Find first match
+                               while ((pivot > start) &&
+                                       (0 == strncmp(wordStart,
+                                               words[pivot-1], searchLen))) {
+                                       --pivot;
+                               }
+                               // Grab each match
+                               while ((pivot <= end) &&
+                                       (0 == strncmp(wordStart,
+                                               words[pivot], searchLen))) {
+                                       wordlen = LengthWord(words[pivot], otherSeparator) + 1;
+                                       if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
+                                               break;
+                                       wordsNear.append(words[pivot], wordlen, ' ');
+                                       ++pivot;
+                               }
+                               return wordsNear.detach();
+                       } else if (cond < 0) {
+                               end = pivot - 1;
+                       } else if (cond > 0) {
+                               start = pivot + 1;
+                       }
+               }
+       }
+       return NULL;
+}