/* -----------------------------------------------------------------------------
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * string.c
 *
 *     Implements a string object that supports both sequence operations and
 *     file semantics.
 * ----------------------------------------------------------------------------- */

#include "dohint.h"

extern DohObjInfo DohStringType;

typedef struct String {
  DOH *file;
  int line;
  int maxsize;			/* Max size allocated */
  int len;			/* Current length     */
  int hashkey;			/* Hash key value     */
  int sp;			/* Current position   */
  char *str;			/* String data        */
} String;

/* -----------------------------------------------------------------------------
 * String_data() - Return as a 'void *'
 * ----------------------------------------------------------------------------- */

static void *String_data(DOH *so) {
  String *s = (String *) ObjData(so);
  s->str[s->len] = 0;
  return (void *) s->str;
}

/* static char *String_char(DOH *so) {
  return (char *) String_data(so);
}
*/

/* -----------------------------------------------------------------------------
 * String_dump() - Serialize a string onto out
 * ----------------------------------------------------------------------------- */

static int String_dump(DOH *so, DOH *out) {
  int nsent;
  int ret;
  String *s = (String *) ObjData(so);
  nsent = 0;
  while (nsent < s->len) {
    ret = Write(out, s->str + nsent, (s->len - nsent));
    if (ret < 0)
      return ret;
    nsent += ret;
  }
  return nsent;
}

/* -----------------------------------------------------------------------------
 * CopyString() - Copy a string
 * ----------------------------------------------------------------------------- */

static DOH *CopyString(DOH *so) {
  String *str;
  String *s = (String *) ObjData(so);
  str = (String *) DohMalloc(sizeof(String));
  str->hashkey = s->hashkey;
  str->sp = s->sp;
  str->line = s->line;
  str->file = s->file;
  if (str->file)
    Incref(str->file);
  str->str = (char *) DohMalloc(s->len + 1);
  memcpy(str->str, s->str, s->len);
  str->maxsize = s->len;
  str->len = s->len;
  str->str[str->len] = 0;

  return DohObjMalloc(&DohStringType, str);
}

/* -----------------------------------------------------------------------------
 * DelString() - Delete a string
 * ----------------------------------------------------------------------------- */

static void DelString(DOH *so) {
  String *s = (String *) ObjData(so);
  DohFree(s->str);
  DohFree(s);
}

/* -----------------------------------------------------------------------------
 * DohString_len() - Length of a string
 * ----------------------------------------------------------------------------- */

static int String_len(DOH *so) {
  String *s = (String *) ObjData(so);
  return s->len;
}


/* -----------------------------------------------------------------------------
 * String_cmp() - Compare two strings
 * ----------------------------------------------------------------------------- */

static int String_cmp(DOH *so1, DOH *so2) {
  String *s1, *s2;
  char *c1, *c2;
  int maxlen, i;
  s1 = (String *) ObjData(so1);
  s2 = (String *) ObjData(so2);
  maxlen = s1->len;
  if (s2->len < maxlen)
    maxlen = s2->len;
  c1 = s1->str;
  c2 = s2->str;
  for (i = maxlen; i; --i, c1++, c2++) {
    if (*c1 != *c2)
      break;
  }
  if (i != 0) {
    if (*c1 < *c2)
      return -1;
    else
      return 1;
  }
  if (s1->len == s2->len)
    return 0;
  if (s1->len > s2->len)
    return 1;
  return -1;
}

/* -----------------------------------------------------------------------------
 * String_equal() - Say if two string are equal
 * ----------------------------------------------------------------------------- */

static int String_equal(DOH *so1, DOH *so2) {
  String *s1 = (String *) ObjData(so1);
  String *s2 = (String *) ObjData(so2);
  int len = s1->len;
  if (len != s2->len) {
    return 0;
  } else {
    char *c1 = s1->str;
    char *c2 = s2->str;
#if 0
    int mlen = len >> 2;
    int i = mlen;
    for (; i; --i) {
      if (*(c1++) != *(c2++))
	return 0;
      if (*(c1++) != *(c2++))
	return 0;
      if (*(c1++) != *(c2++))
	return 0;
      if (*(c1++) != *(c2++))
	return 0;
    }
    for (i = len - (mlen << 2); i; --i) {
      if (*(c1++) != *(c2++))
	return 0;
    }
    return 1;
#else
    return memcmp(c1, c2, len) == 0;
#endif
  }
}

/* -----------------------------------------------------------------------------
 * String_hash() - Compute string hash value
 * ----------------------------------------------------------------------------- */

static int String_hash(DOH *so) {
  String *s = (String *) ObjData(so);
  if (s->hashkey >= 0) {
    return s->hashkey;
  } else {
    /* We use the djb2 hash function: https://theartincode.stanis.me/008-djb2/
     *
     * One difference is we use initial seed 0.  It seems the usual seed value
     * is intended to help spread out hash values, which is beneficial if
     * linear probing is used but DOH Hash uses a chain of buckets instead, and
     * grouped hash values are probably more cache friendly.  In tests using
     * 0 seems slightly faster anyway.
     */
    const char *c = s->str;
    unsigned int len = s->len > 50 ? 50 : s->len;
    unsigned int h = 0;
    unsigned int mlen = len >> 2;
    unsigned int i = mlen;
    for (; i; --i) {
      h = h + (h << 5) + *(c++);
      h = h + (h << 5) + *(c++);
      h = h + (h << 5) + *(c++);
      h = h + (h << 5) + *(c++);
    }
    for (i = len - (mlen << 2); i; --i) {
      h = h + (h << 5) + *(c++);
    }
    h &= 0x7fffffff;
    s->hashkey = (int)h;
    return h;
  }
}

/* -----------------------------------------------------------------------------
 * DohString_append() - Append to s
 * ----------------------------------------------------------------------------- */

static void DohString_append(DOH *so, const DOHString_or_char *str) {
  int oldlen, newlen, newmaxsize, l, sp;
  char *tc;
  String *s = (String *) ObjData(so);
  char *newstr = 0;

  if (DohCheck(str)) {
    String *ss = (String *) ObjData(str);
    newstr = (char *) String_data((DOH *) str);
    l = ss->len;
  } else {
    newstr = (char *) (str);
    l = (int) strlen(newstr);
  }
  if (!newstr)
    return;
  s->hashkey = -1;

  oldlen = s->len;
  newlen = oldlen + l + 1;
  if (newlen >= s->maxsize - 1) {
    newmaxsize = 2 * s->maxsize;
    if (newlen >= newmaxsize - 1)
      newmaxsize = newlen + 1;
    s->str = (char *) DohRealloc(s->str, newmaxsize);
    s->maxsize = newmaxsize;
  }
  tc = s->str;
  memcpy(tc + oldlen, newstr, l + 1);
  sp = s->sp;
  if (sp >= oldlen) {
    int i = oldlen + l - sp;
    tc += sp;
    for (; i; --i) {
      if (*(tc++) == '\n')
	s->line++;
    }
    s->sp = oldlen + l;
  }
  s->len += l;
}


/* -----------------------------------------------------------------------------
 * String_clear() - Clear a string
 * ----------------------------------------------------------------------------- */

static void String_clear(DOH *so) {
  String *s = (String *) ObjData(so);
  s->hashkey = -1;
  s->len = 0;
  *(s->str) = 0;
  s->sp = 0;
  s->line = 1;
}

/* -----------------------------------------------------------------------------
 * String_insert() - Insert a string
 * ----------------------------------------------------------------------------- */

static int String_insert(DOH *so, int pos, DOH *str) {
  String *s;
  int len;
  char *data;

  if (pos == DOH_END) {
    DohString_append(so, str);
    return 0;
  }


  s = (String *) ObjData(so);
  s->hashkey = -1;
  if (DohCheck(str)) {
    String *ss = (String *) ObjData(str);
    data = (char *) String_data(str);
    len = ss->len;
  } else {
    data = (char *) (str);
    len = (int) strlen(data);
  }

  if (pos < 0)
    pos = 0;
  else if (pos > s->len)
    pos = s->len;

  /* See if there is room to insert the new data */
  while (s->maxsize <= s->len + len) {
    int newsize = 2 * s->maxsize;
    s->str = (char *) DohRealloc(s->str, newsize);
    s->maxsize = newsize;
  }
  memmove(s->str + pos + len, s->str + pos, (s->len - pos));
  memcpy(s->str + pos, data, len);
  if (s->sp >= pos) {
    int i;

    for (i = 0; i < len; i++) {
      if (data[i] == '\n')
	s->line++;
    }
    s->sp += len;
  }
  s->len += len;
  s->str[s->len] = 0;
  return 0;
}

/* -----------------------------------------------------------------------------
 * String_delitem() - Delete a character
 * ----------------------------------------------------------------------------- */

static int String_delitem(DOH *so, int pos) {
  String *s = (String *) ObjData(so);
  s->hashkey = -1;
  if (pos == DOH_END)
    pos = s->len - 1;
  if (pos == DOH_BEGIN)
    pos = 0;
  if (s->len == 0)
    return 0;

  if (s->sp > pos) {
    s->sp--;
    assert(s->sp >= 0);
    if (s->str[pos] == '\n')
      s->line--;
  }
  memmove(s->str + pos, s->str + pos + 1, ((s->len - 1) - pos));
  s->len--;
  s->str[s->len] = 0;
  return 0;
}

/* -----------------------------------------------------------------------------
 * String_delslice() -  Delete a range
 * ----------------------------------------------------------------------------- */

static int String_delslice(DOH *so, int sindex, int eindex) {
  String *s = (String *) ObjData(so);
  int size;
  if (s->len == 0)
    return 0;
  s->hashkey = -1;
  if (eindex == DOH_END)
    eindex = s->len;
  if (sindex == DOH_BEGIN)
    sindex = 0;

  size = eindex - sindex;
  if (s->sp > sindex) {
    /* Adjust the file pointer and line count */
    int i, end;
    if (s->sp > eindex) {
      end = eindex;
      s->sp -= size;
    } else {
      end = s->sp;
      s->sp = sindex;
    }
    for (i = sindex; i < end; i++) {
      if (s->str[i] == '\n')
	s->line--;
    }
    assert(s->sp >= 0);
  }
  memmove(s->str + sindex, s->str + eindex, s->len - eindex);
  s->len -= size;
  s->str[s->len] = 0;
  return 0;
}

/* -----------------------------------------------------------------------------
 * String_str() - Returns a string (used by printing commands)
 * ----------------------------------------------------------------------------- */

static DOH *String_str(DOH *so) {
  String *s = (String *) ObjData(so);
  s->str[s->len] = 0;
  return NewString(s->str);
}

/* -----------------------------------------------------------------------------
 * String_read() - Read data from a string
 * ----------------------------------------------------------------------------- */

static int String_read(DOH *so, void *buffer, int len) {
  int reallen, retlen;
  char *cb;
  String *s = (String *) ObjData(so);
  if ((s->sp + len) > s->len)
    reallen = (s->len - s->sp);
  else
    reallen = len;

  cb = (char *) buffer;
  retlen = reallen;

  if (reallen > 0) {
    memmove(cb, s->str + s->sp, reallen);
    s->sp += reallen;
  }
  return retlen;
}

/* -----------------------------------------------------------------------------
 * String_write() - Write data to a string
 * ----------------------------------------------------------------------------- */
static int String_write(DOH *so, const void *buffer, int len) {
  int newlen;
  String *s = (String *) ObjData(so);
  s->hashkey = -1;
  if (s->sp > s->len)
    s->sp = s->len;
  newlen = s->sp + len + 1;
  if (newlen > s->maxsize) {
    s->str = (char *) DohRealloc(s->str, newlen);
    s->maxsize = newlen;
    s->len = s->sp + len;
  }
  if ((s->sp + len) > s->len)
    s->len = s->sp + len;
  memmove(s->str + s->sp, buffer, len);
  s->sp += len;
  s->str[s->len] = 0;
  return len;
}

/* -----------------------------------------------------------------------------
 * String_seek() - Seek to a new position
 * ----------------------------------------------------------------------------- */

static int String_seek(DOH *so, long offset, int whence) {
  int pos, nsp, inc;
  String *s = (String *) ObjData(so);
  if (whence == SEEK_SET)
    pos = 0;
  else if (whence == SEEK_CUR)
    pos = s->sp;
  else if (whence == SEEK_END) {
    pos = s->len;
    offset = -offset;
  } else
    pos = s->sp;

  nsp = pos + offset;
  if (nsp < 0)
    nsp = 0;
  if (s->len > 0 && nsp > s->len)
    nsp = s->len;

  inc = (nsp > s->sp) ? 1 : -1;

  {
#if 0
    int sp = s->sp;
    char *tc = s->str;
    int len = s->len;
    while (sp != nsp) {
      int prev = sp + inc;
      if (prev >= 0 && prev <= len && tc[prev] == '\n')
	s->line += inc;
      sp += inc;
    }
#else
    int sp = s->sp;
    char *tc = s->str;
    if (inc > 0) {
      while (sp != nsp) {
	if (tc[++sp] == '\n')
	  ++s->line;
      }
    } else {
      while (sp != nsp) {
	if (tc[--sp] == '\n')
	  --s->line;
      }
    }
#endif
    s->sp = sp;
  }
  assert(s->sp >= 0);
  return 0;
}

/* -----------------------------------------------------------------------------
 * String_tell() - Return current position
 * ----------------------------------------------------------------------------- */

static long String_tell(DOH *so) {
  String *s = (String *) ObjData(so);
  return (long) (s->sp);
}

/* -----------------------------------------------------------------------------
 * String_putc()
 * ----------------------------------------------------------------------------- */

static int String_putc(DOH *so, int ch) {
  String *s = (String *) ObjData(so);
  int len = s->len;
  int sp = s->sp;
  s->hashkey = -1;
  if (sp >= len) {
    int maxsize = s->maxsize;
    char *tc = s->str;
    if (len > (maxsize - 2)) {
      maxsize *= 2;
      tc = (char *) DohRealloc(tc, maxsize);
      s->maxsize = (int) maxsize;
      s->str = tc;
    }
    tc += sp;
    *tc = (char) ch;
    *(++tc) = 0;
    s->len = s->sp = sp + 1;
  } else {
    s->str[s->sp++] = (char) ch;
  }
  if (ch == '\n')
    s->line++;
  return ch;
}

/* -----------------------------------------------------------------------------
 * String_getc()
 * ----------------------------------------------------------------------------- */

static int String_getc(DOH *so) {
  int c;
  String *s = (String *) ObjData(so);
  if (s->sp >= s->len)
    c = EOF;
  else
    c = (int)(unsigned char) s->str[s->sp++];
  if (c == '\n')
    s->line++;
  return c;
}

/* -----------------------------------------------------------------------------
 * String_ungetc()
 * ----------------------------------------------------------------------------- */

static int String_ungetc(DOH *so, int ch) {
  String *s = (String *) ObjData(so);
  if (ch == EOF)
    return ch;
  if (s->sp <= 0)
    return EOF;
  s->sp--;
  if (ch == '\n')
    s->line--;
  return ch;
}

static char *end_quote(char *s) {
  char *qs;
  char qc;
  char *q;
  char *nl;
  qc = *s;
  qs = s;
  while (1) {
    q = strpbrk(s + 1, "\"\'");
    nl = strchr(s + 1, '\n');
    if (nl && (nl < q)) {
      /* A new line appears before the end of the string */
      if (*(nl - 1) == '\\') {
	s = nl + 1;
	continue;
      }
      /* String was terminated by a newline.  Wing it */
      return qs;
    }
    if (!q && nl) {
      return qs;
    }
    if (!q)
      return 0;
    if ((*q == qc) && (*(q - 1) != '\\'))
      return q;
    s = q;
  }
}

static char *end_comment(char *s) {
  char *substring = strstr(s, "*/");
  if (substring)
    ++substring;
  return substring;
}

static char *match_simple(char *base, char *s, char *token, int tokenlen) {
  (void) base;
  (void) tokenlen;
  return strstr(s, token);
}

static char *match_identifier(char *base, char *s, char *token, int tokenlen) {
  while (s) {
    s = strstr(s, token);
    if (!s)
      return 0;
    if ((s > base) && (isalnum((int) *(s - 1)) || (*(s - 1) == '_'))) {
      /* We could advance by tokenlen if strstr(s, token) matches can't overlap. */
      ++s;
      continue;
    }
    if (isalnum((int) *(s + tokenlen)) || (*(s + tokenlen) == '_')) {
      /* We could advance by tokenlen if strstr(s, token) matches can't overlap. */
      ++s;
      continue;
    }
    return s;
  }
  return 0;
}


static char *match_identifier_begin(char *base, char *s, char *token, int tokenlen) {
  (void)tokenlen;
  while (s) {
    s = strstr(s, token);
    if (!s)
      return 0;
    if ((s > base) && (isalnum((int) *(s - 1)) || (*(s - 1) == '_'))) {
      /* We could advance by tokenlen if strstr(s, token) matches can't overlap. */
      ++s;
      continue;
    }
    return s;
  }
  return 0;
}

static char *match_identifier_end(char *base, char *s, char *token, int tokenlen) {
  (void) base;
  while (s) {
    s = strstr(s, token);
    if (!s)
      return 0;
    if (isalnum((int) *(s + tokenlen)) || (*(s + tokenlen) == '_')) {
      /* We could advance by tokenlen if strstr(s, token) matches can't overlap. */
      ++s;
      continue;
    }
    return s;
  }
  return 0;
}

static char *match_number_end(char *base, char *s, char *token, int tokenlen) {
  (void) base;
  while (s) {
    s = strstr(s, token);
    if (!s)
      return 0;
    if (isdigit((int) *(s + tokenlen))) {
      /* We could advance by tokenlen if strstr(s, token) matches can't overlap. */
      ++s;
      continue;
    }
    return s;
  }
  return 0;
}

/* -----------------------------------------------------------------------------
 * replace_simple()
 *
 * Replaces count non-overlapping occurrences of token with rep in a string.   
 * ----------------------------------------------------------------------------- */

static int replace_simple(String *str, char *token, char *rep, int flags, int count, char *(*match) (char *, char *, char *, int)) {
  int tokenlen;			/* Length of the token */
  int replen;			/* Length of the replacement */
  int delta, expand = 0;
  int ic;
  int rcount = 0;
  int noquote = 0;
  int nocomment = 0;
  char *c, *s, *t, *first;
  char *q, *q2;
  char *base;
  int i;

  /* Figure out if anything gets replaced */
  if (!strlen(token))
    return 0;

  base = str->str;
  tokenlen = (int)strlen(token);
  s = (*match) (base, base, token, tokenlen);

  if (!s)
    return 0;			/* No matches.  Who cares */

  str->hashkey = -1;

  if (flags & DOH_REPLACE_NOQUOTE)
    noquote = 1;

  if (flags & DOH_REPLACE_NOCOMMENT)
    nocomment = 1;

  assert(!(noquote && nocomment)); /* quote and comment combination not implemented */

  /* If we are not replacing inside quotes, we need to do a little extra work */
  if (noquote) {
    q = strpbrk(base, "\"\'");
    if (!q) {
      noquote = 0;		/* Well, no quotes to worry about. Oh well */
    } else {
      while (q && (q < s)) {
	/* First match was found inside a quote.  Try to find another match */
	q2 = end_quote(q);
	if (!q2) {
	  return 0;
	}
	if (q2 > s) {
	  /* Find next match */
	  s = (*match) (base, q2 + 1, token, tokenlen);
	}
	if (!s)
	  return 0;		/* Oh well, no matches */
	q = strpbrk(q2 + 1, "\"\'");
	if (!q)
	  noquote = 0;		/* No more quotes */
      }
    }
  }

  /* If we are not replacing inside comments, we need to do a little extra work */
  if (nocomment) {
    q = strstr(base, "/*");
    if (!q) {
      nocomment = 0;		/* Well, no comments to worry about. Oh well */
    } else {
      while (q && (q < s)) {
	/* First match was found inside a comment.  Try to find another match */
	q2 = end_comment(q);
	if (!q2) {
	  return 0;
	}
	if (q2 > s) {
	  /* Find next match */
	  s = (*match) (base, q2 + 1, token, tokenlen);
	}
	if (!s)
	  return 0;		/* Oh well, no matches */
	q = strstr(q2 + 1, "/*");
	if (!q)
	  nocomment = 0;		/* No more comments */
      }
    }
  }

  first = s;
  replen = (int)strlen(rep);

  delta = (replen - tokenlen);

  if (delta <= 0) {
    /* String is either shrinking or staying the same size */
    /* In this case, we do the replacement in place without memory reallocation */
    ic = count;
    t = s;			/* Target of memory copies */
    while (ic && s) {
      if (replen) {
	memcpy(t, rep, replen);
	t += replen;
      }
      rcount++;
      expand += delta;
      /* Find the next location */
      s += tokenlen;
      if (ic == 1)
	break;
      c = (*match) (base, s, token, tokenlen);

      if (noquote) {
	q = strpbrk(s, "\"\'");
	if (!q) {
	  noquote = 0;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a quote.  Try to find another match */
	    q2 = end_quote(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c)
	      c = (*match) (base, q2 + 1, token, tokenlen);
	    if (!c)
	      break;
	    q = strpbrk(q2 + 1, "\"\'");
	    if (!q)
	      noquote = 0;	/* No more quotes */
	  }
	}
      }
      if (nocomment) {
	q = strstr(s, "/*");
	if (!q) {
	  nocomment = 0;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a comment.  Try to find another match */
	    q2 = end_comment(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c)
	      c = (*match) (base, q2 + 1, token, tokenlen);
	    if (!c)
	      break;
	    q = strstr(q2 + 1, "/*");
	    if (!q)
	      nocomment = 0;	/* No more comments */
	  }
	}
      }
      if (delta) {
	if (c) {
	  memmove(t, s, c - s);
	  t += (c - s);
	} else {
	  memmove(t, s, (str->str + str->len) - s + 1);
	}
      } else {
	if (c) {
	  t += (c - s);
	}
      }
      s = c;
      ic--;
    }
    if (s && delta) {
      memmove(t, s, (str->str + str->len) - s + 1);
    }
    str->len += expand;
    str->str[str->len] = 0;
    if (str->sp >= str->len)
      str->sp += expand;	/* Fix the end of file pointer */
    return rcount;
  }
  /* The string is expanding as a result of the replacement */
  /* Figure out how much expansion is going to occur and allocate a new string */
  {
    char *ns;
    int newsize;

    rcount++;
    ic = count - 1;
    s += tokenlen;
    while (ic && (c = (*match) (base, s, token, tokenlen))) {
      if (noquote) {
	q = strpbrk(s, "\"\'");
	if (!q) {
	  break;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a quote.  Try to find another match */
	    q2 = end_quote(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c) {
	      c = (*match) (base, q2 + 1, token, tokenlen);
	      if (!c)
		break;
	    }
	    q = strpbrk(q2 + 1, "\"\'");
	    if (!q)
	      noquote = 0;
	  }
	}
      }
      if (nocomment) {
	q = strstr(s, "/*");
	if (!q) {
	  break;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a comment.  Try to find another match */
	    q2 = end_comment(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c) {
	      c = (*match) (base, q2 + 1, token, tokenlen);
	      if (!c)
		break;
	    }
	    q = strstr(q2 + 1, "/*");
	    if (!q)
	      nocomment = 0;
	  }
	}
      }
      if (c) {
	rcount++;
	ic--;
	s = c + tokenlen;
      } else {
	break;
      }
    }

    expand = delta * rcount;	/* Total amount of expansion for the replacement */
    newsize = str->maxsize;
    while ((str->len + expand) >= newsize)
      newsize *= 2;

    ns = (char *) DohMalloc(newsize);
    t = ns;
    s = first;

    /* Copy the first part of the string */
    if (first > str->str) {
      memcpy(t, str->str, (first - str->str));
      t += (first - str->str);
    }
    for (i = 0; i < rcount; i++) {
      memcpy(t, rep, replen);
      t += replen;
      s += tokenlen;
      c = (*match) (base, s, token, tokenlen);
      if (noquote) {
	q = strpbrk(s, "\"\'");
	if (!q) {
	  noquote = 0;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a quote.  Try to find another match */
	    q2 = end_quote(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c) {
	      c = (*match) (base, q2 + 1, token, tokenlen);
	      if (!c)
		break;
	    }
	    q = strpbrk(q2 + 1, "\"\'");
	    if (!q)
	      noquote = 0;	/* No more quotes */
	  }
	}
      }
      if (nocomment) {
	q = strstr(s, "/*");
	if (!q) {
	  nocomment = 0;
	} else {
	  while (q && (q < c)) {
	    /* First match was found inside a comment.  Try to find another match */
	    q2 = end_comment(q);
	    if (!q2) {
	      c = 0;
	      break;
	    }
	    if (q2 > c) {
	      c = (*match) (base, q2 + 1, token, tokenlen);
	      if (!c)
		break;
	    }
	    q = strstr(q2 + 1, "/*");
	    if (!q)
	      nocomment = 0;	/* No more comments */
	  }
	}
      }
      if (i < (rcount - 1)) {
	memcpy(t, s, c - s);
	t += (c - s);
      } else {
	memcpy(t, s, (str->str + str->len) - s + 1);
      }
      s = c;
    }
    c = str->str;
    str->str = ns;
    if (str->sp >= str->len)
      str->sp += expand;
    str->len += expand;
    str->str[str->len] = 0;
    str->maxsize = newsize;
    DohFree(c);
    return rcount;
  }
}

/* -----------------------------------------------------------------------------
 * String_replace()
 * ----------------------------------------------------------------------------- */

static int String_replace(DOH *stro, const DOHString_or_char *token, const DOHString_or_char *rep, int flags) {
  int count = -1;
  String *str = (String *) ObjData(stro);

  if (flags & DOH_REPLACE_FIRST)
    count = 1;

  if (flags & DOH_REPLACE_ID_END) {
    return replace_simple(str, Char(token), Char(rep), flags, count, match_identifier_end);
  } else if (flags & DOH_REPLACE_ID_BEGIN) {
    return replace_simple(str, Char(token), Char(rep), flags, count, match_identifier_begin);
  } else if (flags & DOH_REPLACE_ID) {
    return replace_simple(str, Char(token), Char(rep), flags, count, match_identifier);
  } else if (flags & DOH_REPLACE_NUMBER_END) {
    return replace_simple(str, Char(token), Char(rep), flags, count, match_number_end);
  } else {
    return replace_simple(str, Char(token), Char(rep), flags, count, match_simple);
  }
}

/* -----------------------------------------------------------------------------
 * String_chop()
 * ----------------------------------------------------------------------------- */

static void String_chop(DOH *so) {
  char *c;
  String *str = (String *) ObjData(so);
  /* Replace trailing whitespace */
  c = str->str + str->len - 1;
  while ((str->len > 0) && (isspace((int) *c))) {
    if (str->sp >= str->len) {
      str->sp--;
      if (*c == '\n')
	str->line--;
    }
    str->len--;
    c--;
  }
  str->str[str->len] = 0;
  assert(str->sp >= 0);
  str->hashkey = -1;
}

static void String_setfile(DOH *so, DOH *file) {
  DOH *fo;
  String *str = (String *) ObjData(so);

  if (!DohCheck(file)) {
    fo = NewString(file);
    Decref(fo);
  } else
    fo = file;
  Incref(fo);
  Delete(str->file);
  str->file = fo;
}

static DOH *String_getfile(DOH *so) {
  String *str = (String *) ObjData(so);
  return str->file;
}

static void String_setline(DOH *so, int line) {
  String *str = (String *) ObjData(so);
  str->line = line;
}

static int String_getline(DOH *so) {
  String *str = (String *) ObjData(so);
  return str->line;
}

static DohListMethods StringListMethods = {
  0,				/* doh_getitem */
  0,				/* doh_setitem */
  String_delitem,		/* doh_delitem */
  String_insert,		/* doh_insitem */
  String_delslice,		/* doh_delslice */
};

static DohFileMethods StringFileMethods = {
  String_read,
  String_write,
  String_putc,
  String_getc,
  String_ungetc,
  String_seek,
  String_tell,
};

static DohStringMethods StringStringMethods = {
  String_replace,
  String_chop,
};

DohObjInfo DohStringType = {
  "String",			/* objname */
  DelString,			/* doh_del */
  CopyString,			/* doh_copy */
  String_clear,			/* doh_clear */
  String_str,			/* doh_str */
  String_data,			/* doh_data */
  String_dump,			/* doh_dump */
  String_len,	    	        /* doh_len */
  String_hash,			/* doh_hash    */
  String_cmp,			/* doh_cmp */
  String_equal,	    	        /* doh_equal */
  0,				/* doh_first    */
  0,				/* doh_next     */
  String_setfile,		/* doh_setfile */
  String_getfile,		/* doh_getfile */
  String_setline,		/* doh_setline */
  String_getline,		/* doh_getline */
  0,				/* doh_mapping */
  &StringListMethods,		/* doh_sequence */
  &StringFileMethods,		/* doh_file */
  &StringStringMethods,		/* doh_string */
  0,				/* doh_position */
  0
};


#define INIT_MAXSIZE  16

/* -----------------------------------------------------------------------------
 * NewString() - Create a new string
 * ----------------------------------------------------------------------------- */

DOHString *DohNewString(const DOHString_or_char *so) {
  int l = 0, max;
  String *str;
  char *s;
  int hashkey = -1;
  if (DohCheck(so)) {
    str = (String *) ObjData(so);
    s = (char *) String_data((String *) so);
    l = s ? str->len : 0;
    hashkey = str->hashkey;
  } else {
    s = (char *) so;
    l = s ? (int) strlen(s) : 0;
  }

  str = (String *) DohMalloc(sizeof(String));
  str->hashkey = hashkey;
  str->sp = 0;
  str->line = 1;
  str->file = 0;
  max = INIT_MAXSIZE;
  if (s) {
    if ((l + 1) > max)
      max = l + 1;
  }
  str->str = (char *) DohMalloc(max);
  str->maxsize = max;
  if (s) {
    strcpy(str->str, s);
    str->len = l;
    str->sp = l;
  } else {
    str->str[0] = 0;
    str->len = 0;
  }
  return DohObjMalloc(&DohStringType, str);
}


/* -----------------------------------------------------------------------------
 * NewStringEmpty() - Create a new string
 * ----------------------------------------------------------------------------- */

DOHString *DohNewStringEmpty(void) {
  int max = INIT_MAXSIZE;
  String *str = (String *) DohMalloc(sizeof(String));
  str->hashkey = 0;
  str->sp = 0;
  str->line = 1;
  str->file = 0;
  str->str = (char *) DohMalloc(max);
  str->maxsize = max;
  str->str[0] = 0;
  str->len = 0;
  return DohObjMalloc(&DohStringType, str);
}

/* -----------------------------------------------------------------------------
 * NewStringWithSize() - Create a new string
 * ----------------------------------------------------------------------------- */

DOHString *DohNewStringWithSize(const DOHString_or_char *so, int len) {
  int l = 0, max;
  String *str;
  char *s;
  if (DohCheck(so)) {
    s = (char *) String_data((String *) so);
  } else {
    s = (char *) so;
  }

  str = (String *) DohMalloc(sizeof(String));
  str->hashkey = -1;
  str->sp = 0;
  str->line = 1;
  str->file = 0;
  max = INIT_MAXSIZE;
  if (s) {
    l = (int) len;
    if ((l + 1) > max)
      max = l + 1;
  }
  str->str = (char *) DohMalloc(max);
  str->maxsize = max;
  if (s) {
    strncpy(str->str, s, len);
    str->str[l] = 0;
    str->len = l;
    str->sp = l;
  } else {
    str->str[0] = 0;
    str->len = 0;
  }
  return DohObjMalloc(&DohStringType, str);
}

/* -----------------------------------------------------------------------------
 * NewStringf()
 *
 * Create a new string from a list of objects.
 * ----------------------------------------------------------------------------- */

DOHString *DohNewStringf(const DOHString_or_char *fmt, ...) {
  va_list ap;
  DOH *r;
  va_start(ap, fmt);
  r = NewStringEmpty();
  DohvPrintf(r, Char(fmt), ap);
  va_end(ap);
  return (DOHString *) r;
}

/* -----------------------------------------------------------------------------
 * Strcmp()
 * Strncmp()
 * Strstr()
 * Strchr()
 *
 * Some utility functions.
 * ----------------------------------------------------------------------------- */

int DohStrcmp(const DOHString_or_char *s1, const DOHString_or_char *s2) {
  const char *c1 = Char(s1);
  const char *c2 = Char(s2);
  return strcmp(c1, c2);
}

int DohStrncmp(const DOHString_or_char *s1, const DOHString_or_char *s2, int n) {
  return strncmp(Char(s1), Char(s2), n);
}

char *DohStrstr(const DOHString_or_char *s1, const DOHString_or_char *s2) {
  char *p1 = Char(s1);
  char *p2 = Char(s2);
  return p1 == 0 || p2 == 0 || *p2 == '\0' ? p1 : strstr(p1, p2);
}

char *DohStrchr(const DOHString_or_char *s1, int ch) {
  return strchr(Char(s1), ch);
}
