/*
** Splint - annotation-assisted static program checker
** Copyright (C) 1994-2003 University of Virginia,
**         Massachusetts Institute of Technology
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your
** option) any later version.
** 
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** The GNU General Public License is available from http://www.gnu.org/ or
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
** MA 02111-1307, USA.
**
** For information on splint: info@splint.org
** To report a bug: splint-bug@splint.org
** For more information: http://www.splint.org
*/
/*
** cstring.c
*/

# include "splintMacros.nf"
# include "basic.h"

/*@only@*/ /*@notnull@*/ 
cstring cstring_newEmpty (void)
{
  return (cstring_create (0));
}

char cstring_firstChar (cstring s) 
{
  llassert (cstring_isDefined (s));
  llassert (cstring_length (s) > 0);

  return (s[0]);
}

char cstring_getChar (cstring s, size_t n) 
{
  size_t length = cstring_length (s);

  llassert (cstring_isDefined (s));
  llassert (n >= 1 && n <= length);

  return (s[n - 1]);
}

cstring cstring_suffix (cstring s, size_t n) 
{
  llassert (cstring_isDefined (s));
  llassert (n <= cstring_length (s));

  return (s + n);
}

cstring cstring_prefix (cstring s, size_t n) 
   /*@requires maxRead(s) >= n /\ maxSet(s) >= n @*/
   /*@ensures maxRead(result) == n /\ maxSet(result) == n @*/
{
  cstring t;
  char c;
  llassert (cstring_isDefined (s));
  llassert (n <= cstring_length (s));

  c = *(s + n);
  /*@-mods@*/  /* The modifications cancel out. */
  *(s + n) = '\0';
  t = cstring_copy (s);
  *(s + n) = c;
  /*@=mods@*/

  return t;
}

/* effects If s = [0-9]*, returns s as an int.
**         else returns -1.
*/

int cstring_toPosInt (cstring s)
{
  int val = 0;

  cstring_chars (s, c)
    {
      if (isdigit ((unsigned char) c))
	{
	  val = (val * 10) + (int)(c - '0');
	}
      else
	{
	  return -1;
	}
    } end_cstring_chars ; 

  return val;
}

cstring cstring_afterChar (cstring s, char c) 
{
  llassert (cstring_isDefined (s));
  return strchr (s, c);
}

cstring cstring_beforeChar (cstring s, char c)
{
  if (cstring_isDefined (s))
    {
      char *cp = strchr (s, c);

      if (cp != NULL)
	{
	  cstring ret;

	  /*@-mods@*/
	  *cp = '\0';
	  ret = cstring_copy (s);
	  *cp = c;
	  /*@=mods@*/ /* modification is undone */
	  
	  return ret;
	}
    }

  return cstring_undefined;
}

void cstring_setChar (cstring s, size_t n, char c) /*@requires maxRead(s) >= (n - 1) /\ maxSet(s) >= (n - 1) @*/
{
  llassert (cstring_isDefined (s));
  llassert (n > 0 && n <= cstring_length (s));

  s[n - 1] = c;
}

char cstring_lastChar (cstring s) 
{
  size_t length;

  llassert (cstring_isDefined (s));

  length = cstring_length (s);
  llassert (length > 0);

  return (s[length - 1]);
}

/*@only@*/ cstring cstring_copy (cstring s) /*@ensures maxSet(result) == maxRead(s) /\ maxRead(result) == maxRead(s) @*/
{
  if (cstring_isDefined (s))
    {
      return (mstring_copy (s));
    }
  else
    {
      return cstring_undefined;
    }
}

/*@only@*/ cstring
cstring_copyLength (const char *s, size_t len) /*@requires maxSet(s) >= (len - 1) @*/
{
  char *res = mstring_create (len);

  strncpy (res, s, len);
  res[len] = '\0';
  return res;
}

bool cstring_containsChar (cstring c, char ch)
{
  if (cstring_isDefined (c))
    {
      return (strchr (c, ch) != NULL);
    }
  else
    {
      return FALSE;
    }
}

/*
** Replaces all occurances of old in s with new.
*/

void cstring_replaceAll (cstring s, char old, char snew)
{
  
  llassert (old != snew);

  if (cstring_isDefined (s))
    {
      char *sp = strchr (s, old);

      while (sp != NULL)
	{
	  *sp = snew;
	  sp = strchr (sp, old);
	}

    }
}

void cstring_replaceLit (/*@unique@*/ cstring s, const char *old, const char *snew) 
   /*@requires maxRead(snew) >= 0 /\ maxRead(old) >= 0 /\ maxRead(old) >= maxRead(snew) @*/
{
  llassert (strlen (old) >= strlen (snew));
  
  if (cstring_isDefined (s))
    {
      char *sp = strstr (s, old);

      while (sp != NULL)
	{
	  int lendiff = size_toInt (strlen (old) - strlen (snew));
	  const char *tsnew = snew;

	  llassert (lendiff >= 0);

	  while (*tsnew != '\0')
	    {
	      llassert (*sp != '\0');
	      *sp++ = *tsnew++;
	    }
	  
	  if (lendiff > 0)
	    {
	      while (*(sp + lendiff) != '\0')
		{
		  *sp = *(sp + lendiff);
		  sp++;
		}
	      
	      *sp = '\0';
	    }

	  sp = strstr (s, old);
	}
    }
}

/*
** removes all chars in clist from s
*/

void cstring_stripChars (cstring s, const char *clist)
{
  if (cstring_isDefined (s))
    {
      int i;
      size_t size = cstring_length (s);

      for (i = 0; i < size_toInt (size); i++)
	{
	  	
	  char c = s[i];
	  
	  if (strchr (clist, c) != NULL)
	    {
	      /* strip this char */
	      int j;
	      
	      size--;
	      
	      for (j = i; j < size_toInt (size); j++)
		{
		  s[j] = s[j+1];
		}
	      
	      s[size] = '\0'; 
	      i--;
	    }
	}
    }
}

bool cstring_contains (/*@unique@*/ cstring c, cstring sub)
{
  if (cstring_isDefined (c))
    {
      llassert (cstring_isDefined (sub));
      
      return (strstr (c, sub) != NULL);
    }
  else
    {
      return FALSE;
    }
}

static char lookLike (char c) /*@*/
{
  if (c == 'I' || c == 'l')
    {
      return '1';
    }
  else if (c == 'O' || c == 'o')
    {
      return '0';
    }
  else if (c == 'Z')
    {
      return '2';
    }
  else if (c == 'S')
    {
      return '5';
    }
  else
    {
      return c;
    }
}

cmpcode cstring_genericEqual (cstring s, cstring t,
			      size_t nchars,
			      bool caseinsensitive,
			      bool lookalike) 
  /*@requires maxRead(s) >= nchars /\ maxRead(t) >= nchars @*/
{
  if (s == t) return CGE_SAME;
  else if (cstring_isUndefined (s))
    {
      return cstring_isEmpty (t) ? CGE_SAME : CGE_DISTINCT;
    }
  else if (cstring_isUndefined (t))
    {
      return cstring_isEmpty (s) ? CGE_SAME : CGE_DISTINCT;
    }
  else
    {
      int i = 0;
      bool diffcase = FALSE;
      bool difflookalike = FALSE;

      while (*s != '\0')
	{
	  if (nchars > 0 && i >= size_toInt (nchars))
	    {
	      break;
	    }

	  if (*t == *s)
	    {
	      ; /* no difference */
	    }
	  else if (caseinsensitive 
		   && (toupper ((int) *t) == toupper ((int) *s)))
	    {
	      diffcase = TRUE;
	    }
	  else if (lookalike && (lookLike (*t) == lookLike (*s)))
	    {
	      difflookalike = TRUE;
	    }
	  else 
	    {
	      return CGE_DISTINCT;
	    }
	  i++;
	  s++;
	  t++;
	}

        
      if (*s == '\0' && *t != '\0')
	{
	  return CGE_DISTINCT;
	}

      if (diffcase)
	{
	  return CGE_CASE;
	}
      else if (difflookalike)
	{
	  return CGE_LOOKALIKE;
	}
      else
	{
	  return CGE_SAME;
	}
    }
}



bool cstring_equalFree (/*@only@*/ cstring c1, /*@only@*/ cstring c2)
{
  bool res = cstring_equal (c1, c2);
  cstring_free (c1);
  cstring_free (c2);
  return res;
}

bool cstring_equal (cstring c1, cstring c2)
{
  if (c1 == c2) return TRUE;
  else if (cstring_isUndefined (c1)) return cstring_isEmpty (c2);
  else if (cstring_isUndefined (c2)) return cstring_isEmpty (c1);
  else return (strcmp (c1, c2) == 0);
}

bool cstring_equalLen (cstring c1, cstring c2, size_t len)
{
  if (c1 == c2) return TRUE;
  else if (cstring_isUndefined (c1)) return cstring_isEmpty (c2);
  else if (cstring_isUndefined (c2)) return cstring_isEmpty (c1);
  else return (strncmp (c1, c2, len) == 0);
}

bool cstring_equalCaseInsensitive (cstring c1, cstring c2)
{
  if (c1 == c2) return TRUE;
  else if (cstring_isUndefined (c1)) return cstring_isEmpty (c2);
  else if (cstring_isUndefined (c2)) return cstring_isEmpty (c1);
  else return (cstring_genericEqual (c1, c2, 0, TRUE, FALSE) != CGE_DISTINCT);
}

bool cstring_equalLenCaseInsensitive (cstring c1, cstring c2, size_t len)
{
  if (c1 == c2) return TRUE;
  else if (cstring_isUndefined (c1)) return cstring_isEmpty (c2);
  else if (cstring_isUndefined (c2)) return cstring_isEmpty (c1);
  else return (cstring_genericEqual (c1, c2, len, TRUE, FALSE) != CGE_DISTINCT);
}

bool cstring_equalPrefix (cstring c1, cstring c2)
{
  llassert (c2 != NULL);

  if (cstring_isUndefined (c1)) 
    {
      return (strlen (c2) == 0);
    }

  return (strncmp (c1, c2, strlen (c2)) == 0);
}

bool cstring_equalPrefixLit (cstring c1, const char *c2)
{
  llassert (c2 != NULL);

  if (cstring_isUndefined (c1)) 
    {
      return (strlen (c2) == 0);
    }

  return (strncmp (c1, c2, strlen (c2)) == 0);
}

int cstring_xcompare (cstring *c1, cstring *c2)
{
  return (cstring_compare (*c1, *c2));
}

int cstring_compare (cstring c1, cstring c2)
{
  int res;

  if (c1 == c2)
    {
      res = 0;
    }
  else if (cstring_isUndefined (c1))
    {
      if (cstring_isEmpty (c2))
	{
	  res = 0;
	}
      else
	{
	  res = 1;
	}
    }
  else if (cstring_isUndefined (c2))
    {
      if (cstring_isEmpty (c1))
	{
	  res = 0;
	}
      else
	{
	  res = -1;
	}
    }
  else
    {
      res = strcmp (c1, c2);
    }

    return (res);
}

void cstring_markOwned (/*@owned@*/ cstring s)
{
  sfreeEventually (s);
}

void cstring_free (/*@only@*/ cstring s)
{
  if (cstring_isDefined (s)) 
    {
      /*drl 2/3/2002*/
      s[0] = '\0';
      
      sfree (s);
    }
}

cstring cstring_fromChars (/*@exposed@*/ const char *cp)
{
  return (cstring) cp;
}

/*@exposed@*/ char *cstring_toCharsSafe (cstring s)
{
  static /*@only@*/ cstring emptystring = cstring_undefined;

  if (cstring_isDefined (s))
    {
      return (char *) s;
    }
  else
    {
      if (cstring_isUndefined (emptystring))
	{
	  emptystring = cstring_newEmpty ();
	}

      return emptystring;
    }
}

size_t cstring_length (cstring s)
{
  if (cstring_isDefined (s))
    {
      return strlen (s);
    }

  return 0;
}

cstring
cstring_capitalize (cstring s) /*@requires maxSet(s) >= 0 @*/
{
  if (!cstring_isEmpty (s))
    {
      cstring ret = cstring_copy (s);

      cstring_setChar (ret, 1, (char) toupper ((int) cstring_firstChar (ret)));
      return ret;
    }
  
  return cstring_undefined;
}

cstring
cstring_capitalizeFree (cstring s) /*@requires maxSet(s) >= 0 /\ maxRead(s) >= 0 @*/
{
  if (!cstring_isEmpty (s))
    {
      cstring_setChar (s, 1, (char) toupper ((int) cstring_firstChar (s)));
      return s;
    }
  
  return s;
}

cstring
cstring_clip (cstring s, size_t len)
{
  if (cstring_isUndefined (s) || cstring_length (s) <= len)
    {
      ;
    }
  else
    {
      llassert (s != NULL);
      
      *(s + len) = '\0';
    }
  
  return s;
}

/*@only@*/ cstring
cstring_elide (cstring s, size_t len)
{
  if (cstring_isUndefined (s) || cstring_length (s) <= len)
    {
      return cstring_copy (s);
    }
  else
    {
      cstring sc = cstring_create (len);
      
      strncpy (sc, s, len);
      
      *(sc + len - 1) = '\0';
      *(sc + len - 2) = '.';      
      *(sc + len - 3) = '.';      
      *(sc + len - 4) = '.';      
      
      return sc;
    }
}

/*@only@*/ cstring
cstring_fill (cstring s, size_t n) /*@requires n >= 0 @*/
{
  cstring t = cstring_create (n + 1);
  cstring ot = t;
  size_t len = cstring_length (s);
  size_t i;
  
  if (len > n)
    {
      for (i = 0; i < n; i++)
	{
		  
	  *t++ = *s++;
	}
      *t = '\0';
    }
  else
    {
      for (i = 0; i < len; i++)
	{
	
	  *t++ = *s++;
	}
      for (i = 0; i < n - len; i++)
	{

	  *t++ = ' ';
	}
      *t = '\0';
    }

  return ot;
}

cstring
cstring_downcase (cstring s)
{
  if (cstring_isDefined (s))
    {
      cstring t = cstring_create (strlen (s) + 1);
      cstring ot = t;
      char c;
      
      while ((c = *s) != '\0')
	{
	  if (c >= 'A' && c <= 'Z')
	    {
	      c = c - 'A' + 'a';
	    }
	  *t++ = c;
	  s++;
	}
      *t = '\0';
      
      return ot;
    }
  else
    {
      return cstring_undefined;
    }
}

/*@notnull@*/ cstring 
cstring_appendChar (/*@only@*/ cstring s1, char c)
{
  size_t l = cstring_length (s1);
  char *s;

  s = (char *) dmalloc (sizeof (*s) * (l + 2));

  if (cstring_isDefined (s1))
    {  
      strcpy (s, s1);
      *(s + l) = c;
      *(s + l + 1) = '\0';
      sfree (s1); 
    }
  else
    {
      *(s) = c;
       *(s + 1) = '\0';
    } 

  return s;
}

/*@only@*/ cstring 
cstring_concatFree (cstring s, cstring t)
{
  cstring res = cstring_concat (s, t);
  cstring_free (s);
  cstring_free (t);
  return res;
}

/*@only@*/ cstring 
cstring_concatFree1 (cstring s, cstring t)
{
  cstring res = cstring_concat (s, t);
  cstring_free (s);
  return res;
}

/*@only@*/ cstring 
cstring_concatChars (cstring s, const char *t)
{
  if (t != NULL)
    {
      return cstring_concatLength (s, t, strlen (t));
    }

  return s;
}

/*@only@*/ cstring 
cstring_concatLength (cstring s1, const char *s2, size_t len) /*@requires maxSet(s2) >= (len - 1) @*/
{
  if (len > 0)
    {
      char *res;
      size_t s1len = cstring_length (s1);

      res = mstring_create (s1len + len);
      if (cstring_isDefined (s1))
        {
          strcpy (res, s1);
        }
      strncpy (res+s1len, s2, len);
      res[s1len+len] = '\0';

      cstring_free (s1);
      return res;
    }

  return s1;
}

/*@only@*/ cstring 
cstring_concat (cstring s, cstring t) /*@requires maxSet(s) >= 0 @*/
{
  char *ret = mstring_create (cstring_length (s) + cstring_length (t));

  if (cstring_isDefined (s))
    {
      strcpy (ret, s);
    }
  if (cstring_isDefined (t))
    {
      strcat (ret, t);
    }

  return ret;
}

/*@notnull@*/ /*@only@*/ cstring 
cstring_prependCharO (char c, /*@only@*/ cstring s1)
{
  cstring res = cstring_prependChar (c, s1);

  cstring_free (s1);
  return (res);
}

/*@notnull@*/ /*@only@*/ cstring 
cstring_prependChar (char c, /*@temp@*/ cstring s1)
{
  size_t l = cstring_length (s1);
  char *s = (char *) dmalloc (sizeof (*s) * (l + 2));
  
  *(s) = c;

  if (cstring_isDefined (s1)) 
    {
      /*@-mayaliasunique@*/ 
      strcpy (s + 1, s1);
      /*@=mayaliasunique@*/ 
    }

 *(s + l + 1) = '\0';
  return s;
}

# ifdef DEADCODE
bool
cstring_hasNonAlphaNumBar (cstring s)
{
  int c;

  if (cstring_isUndefined (s)) return FALSE;

 while ((c = (int) *s) != (int) '\0')
    {
      if ((isalnum (c) == 0) && (c != (int) '_')
	  && (c != (int) '.') && (c != (int) CONNECTCHAR))
	{
	  return TRUE;
	}

      s++;
    }
  return FALSE;
}
# endif /* DEADCODE */

/*@only@*/ /*@notnull@*/ cstring 
cstring_create (size_t n)
{
  char *s = dmalloc (sizeof (*s) * (n + 1));
  
  *s = '\0';
  return s;
}

/*@only@*/ /*@notnull@*/ cstring
cstring_copySegment (cstring s, size_t findex, size_t tindex)
{
  cstring res = cstring_create (tindex - findex + 1);

  llassert (cstring_isDefined (s));
  llassert (cstring_length (s) > tindex);

  strncpy (res, (s + findex), size_fromInt (size_toInt (tindex - findex) + 1));
  return res;
}

lsymbol cstring_toSymbol (cstring s)
{
  lsymbol res = lsymbol_fromString (s);

  cstring_free (s);
  return res;
}

bool cstring_bsearch (cstring key, const char **table, int nentries)
{
  bool res = FALSE;

  if (cstring_isDefined (key))
    {
      int low = 0;
      int high = nentries;
      int mid = (high + low + 1) / 2;
      int last = -1;
      const char * ckey = cstring_toCharsSafe (key);

      while (low <= high && mid < nentries)
	{
	  int cmp;

	  llassert (mid != last);
	  llassert (mid >= 0 && mid < nentries);

	  cmp = strcmp (ckey, table[mid]);
	  
	  if (cmp == 0)
	    {
	      res = TRUE;
	      break;
	    }
	  else if (cmp < 0) /* key is before table[mid] */
	    {
	      high = mid - 1;
	    }
	  else /* key of after table[mid] */
	    {
	      low = mid + 1;
	    }

	  last = mid;
	  mid = (high + low + 1) / 2;
	}
    }
  
  return res;
}

extern /*@observer@*/ cstring cstring_advanceWhiteSpace (cstring s)
{
  if (cstring_isDefined (s)) {
    char *t = s;

   while (*t != '\0' && isspace ((int) *t)) {
      t++;
    }

    return t;
  }
  
  return cstring_undefined;
}

/*@only@*/ /*@notnull@*/ mstring
cstring_expandEscapes (/*@notnull@*/ cstring s, /*@out@*/ size_t *len)
{
  const char *ptr;
  char *ret, *retPtr;
  
  llassertfatal (cstring_isDefined (s));

  ptr = s;
  retPtr = ret = mstring_create (cstring_length(s));
  while (*ptr != '\0')
    {
      if (*ptr != '\\')
	{
	  *retPtr = *ptr;
	  retPtr++;
	  ptr++;
	  continue;
	}
      
      if (*ptr == '\\')
	{
	  ptr++;
	  if (*ptr == '\0')
	    {
	      /*not a legal escape sequence but try to handle it in a sesible way*/
	      *retPtr = '\\';
	      retPtr++;
	    }
	  
	  /* Handle Octal escapes  */
	  else if (*ptr >= '0' && *ptr <= '7' )
	    {
	      int total;
	      total = (int)(*ptr - '0');
	      ptr++;
	      /*octal can only be 3 characters long */
	      if (*ptr != '\0' &&  (*ptr >= '0' && *ptr <= '7' ) )
		{
		  total *= 8;
                  total += (int) (*ptr - '0');
		  ptr++;
		  if (*ptr != '\0' &&  (*ptr >= '0' && *ptr <= '7' ) )
		    {
		      total *= 8;
		      total += (int) (*ptr - '0');
		      ptr++;
		    }
		}
	      
	      *retPtr = (char) total;
	      retPtr++;
	    }
	  
	  else if (*ptr == 'x')
	    {
	      int total;
	      total = 0;
	      ptr++;
              if (!(*ptr != '\0' &&
                    ((*ptr >= '0' && *ptr <= '9' ) ||
                     (toupper(*ptr) >= (int)('A') && toupper(*ptr) <= (int)('F')))))
		{
		  total = (int)'x';
		}
	      else
		{
                  while (*ptr != '\0' &&
                      ((*ptr >= '0' && *ptr <= '9' ) ||
                       (toupper(*ptr) >= ((int)('A')) && toupper(*ptr) <= ((int)'F'))))
		    {
		      total *= 16;
		      if (*ptr >= '0' && *ptr <= '9' )
			total += (int)(*ptr - '0');
		      else
			total += ((toupper(*ptr) - 'A') + 10);
		      ptr++;
		    }
		}
	      *retPtr = (char) total;
	      retPtr++;
	    }
	  else
	    {
	      switch ( *ptr )
		{
		case 'a':
		  *retPtr = '\a';
		  retPtr++;
		  /*@switchbreak@*/ break;

		case 'b':
		  *retPtr = '\b';
		  retPtr++;
		  /*@switchbreak@*/ break;

		case 'f':
		  *retPtr = '\f';
		  retPtr++;
		  /*@switchbreak@*/ break;

		case 'n':
		  *retPtr = '\n';
		  retPtr++;
		  /*@switchbreak@*/ break;

		case 'r':
		  *retPtr = '\r';
		  retPtr++;
		  /*@switchbreak@*/ break;

		case 't':
		  *retPtr = '\t';
		  retPtr++;
		  /*@switchbreak@*/ break;
		  /* ' " ? \ */
		  /* we assume invalid sequences are handled somewhere else
		     so we handle an invalid sequence of the form \char by replacing
		     it with char (this is what gcc does) the C standard says a diagnostic is
		     required..*/
		default:
		  *retPtr = *ptr;
		  retPtr++;
		}
	      ptr++;
	    }
	  
	}/*end outer if*/
      
    }/*end while */

  /* add the null character */
  *retPtr = '\0';

  llassert(  (retPtr-ret) >= 0 );
  *len = (size_t)(retPtr - ret);
  return ret;
}

cstring cstring_replaceChar(/*@returned@*/ cstring c, char oldChar, char newChar)
{
  char *ptr;
  llassert(oldChar != '\0');
  if (cstring_isUndefined(c) )
    {
      llcontbug(cstring_makeLiteral("cstring_replaceChar called with undefined string"));
      return c;
    }
  
  ptr = c;
  while (*ptr != '\0')
    {
      if (*ptr == oldChar)
	*ptr = newChar;
      ptr++;
    }

  return c;
}








