/*  File: freesubs.c
 *  Author: Richard Durbin (rd@mrc-lmb.cam.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1991
 *-------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description: free format input - record based
 * Exported functions: lots - see regular.h
 * HISTORY:
 * Last edited: Dec  4 11:20 1998 (fw)
 * * Dec  3 14:46 1998 (edgrif): Insert version macros for libfree.
 *    freecard ignores "\r" and "\n" under WIN32
 * * Sep 30 14:19 1998 (edgrif)
 * * Nov 27 12:30 1995 (mieg): freecard no longer stips \, freeword does
 *    also i added freeunprotect
 * Created: Sun Oct 27 18:16:01 1991 (rd)
 *-------------------------------------------------------------------
 */

/* $Id: freesubs.c,v 1.1 2002/11/14 20:00:06 lstein Exp $ */

#include "regular.h"
#include "version.h"
#include <ctype.h>

/* free package version and copyright string.    */
/*                                               */
#define FREE_TITLE   "Free library"
#define FREE_DESC    "Sanger Centre Informatics utilities library."

#define FREE_VERSION 1
#define FREE_RELEASE 1
#define FREE_UPDATE  1
#define FREE_VERSION_NUMBER  UT_MAKE_VERSION_NUMBER(FREE_VERSION, FREE_RELEASE, FREE_UPDATE)

UT_COPYRIGHT_STRING(FREE_TITLE, FREE_VERSION, FREE_RELEASE, FREE_UPDATE, FREE_DESC)

 
int isInteractive = TRUE ;      /* can set FALSE, i.e. in tace */
 
#define MAXSTREAM 80
#define MAXNPAR 80
typedef struct
  { FILE *fil ;
    char *text ;
    char special[24] ;
    int npar ;
    int parMark[MAXNPAR] ;
    int line ;
    BOOL isPipe ;
  } STREAM ;
static STREAM   stream[MAXSTREAM] ;
static int	streamlevel ;
static FILE	*currfil ;	/* either currfil or currtext is 0 */
static char	*currtext ;	/* the other is the current source */
static Stack    parStack ;
static int	maxcard = 1024 ;
static unsigned char *card, *word, *cardEnd, *pos ;
static Associator filAss ;
 
#define _losewhite    while (*pos == ' '|| *pos == '\t') ++pos
#define _stepover(x)  (*pos == x && ++pos)
#define _FREECHAR     (currfil ? getc (currfil) : *currtext++)
 
/************************************/
 
void freeinit (void)
{ static BOOL isInitialised = FALSE ;
  
  if (!isInitialised)
    { streamlevel = 0 ;
      currtext = 0 ;
      stream[streamlevel].fil = currfil = stdin ;
      stream[streamlevel].text = 0 ;
      freespecial ("\n\t\\/@%") ;
      card = (unsigned char *) messalloc (maxcard) ;
      cardEnd = &card[maxcard-1] ;
      pos = card ;
      word = (unsigned char *) messalloc (maxcard) ;
      filAss = assCreate () ;
      parStack = stackCreate (128) ;
      isInitialised = TRUE ;
    }
}

/*******************/

/* Sometimes you may need to know if a function below you succeeded in       */
/* changing a stream level.                                                  */
UTIL_FUNC_DCL int freeCurrLevel(void)
  {
  return streamlevel ;
  }

/*******************/

static void freeExtend (unsigned char **pin)
{	/* only happens when getting card */
  unsigned char *oldCard = card ;

  maxcard *= 2 ;
  card = (unsigned char *) messalloc (maxcard) ;
  if (oldCard)     /* jtm june 22, 1992 */
    memcpy (card, oldCard, maxcard/2) ;
  cardEnd = &card[maxcard-1] ;
  *pin += (card - oldCard) ;
  messfree (oldCard) ;
  messfree (word) ;
  word = (unsigned char *) messalloc (maxcard) ;
}
 
/********************/

static char special[256] ;

void freespecial (char* text)
{
  if (!text)
    messcrash ("freespecial received 0 text") ;
  if (strlen(text) > 23)
    messcrash ("freespecial received a string longer than 23") ;
  if (text != stream[streamlevel].special)
    strcpy (stream[streamlevel].special, text) ;
  memset (special, 0, (mysize_t) 256) ;
  while (*text)
    special [((int) *text++) & 0xFF] = TRUE ;
  special[0] = TRUE ;
  special[(unsigned char)EOF] = TRUE ;		/* better have these to ensure streams terminate! */
}
 
/********************/
 
void freeforcecard (char *string)
{ int level = freesettext (string, "") ;
  freespecial ("") ;
  freecard (level) ;
}
 
/********************/
 
char* freecard (int level)	/* returns 0 when streamlevel drops below level */
{ 
  unsigned char *in,ch,*cp ;
  int kpar ;
  int isecho = FALSE ;		/* could reset sometime? */
  FILE *fil ;
  BOOL acceptShell, acceptCommand ;

restart :
  if (level > streamlevel)
    return 0 ;

  if (isecho)
    printf (!currfil ? "From text >" : "From file >") ;
  in = card ; --in ;

  acceptCommand = special['@'] ;
  acceptShell = special['$'] ;
 
  while (TRUE)
    { if (++in >= cardEnd)
	freeExtend (&in) ;

      *in = _FREECHAR ;
    lao:
      if (special[((int) *in) & 0xFF] && *in != '$' && *in != '@' )
	switch (*in)
	  {
#if defined(WIN32)
	  case '\r':
		  continue ; /* ignore carriage returns */ 
#endif
	  case '\n':		/* == '\x0a' */
	  case ';':		/* card break for multiple commands on one line */
	    goto got_line ;
	  case (unsigned char) EOF:
	  case '\0':
	    freeclose(streamlevel) ;
	    goto got_line;
	  case '\t':     /* tabs should get rounded to 8 spaces */
	    if (isecho)	/* write it out */
	      putchar (*in) ;
            *in++ = ' ' ;
            while ((in - card) % 8)
              { if (in >= cardEnd)
                  freeExtend (&in) ;
                *in++ = ' ' ;
              }
	    --in ;
	    continue ;  
	  case '/':		/* // means start of comment */
	    if ((ch = _FREECHAR) == '/')
	      { while ((ch = _FREECHAR) != '\n' && ch != (unsigned char)EOF) ;
		goto got_line ;
	      }
	    else
	      { if (isecho) putchar (*in) ;
		if (currfil)                     /* push back ch */
		  ungetc (ch, currfil) ;
		else
		  --currtext ;
	      }
	    break ;
	  case '%':		/* possible parameter */
	    --in ; kpar = 0 ;
	    while (isdigit (ch = _FREECHAR))
	      kpar = kpar*10 + (ch - '0') ;
	    if (kpar > 0 && kpar <= stream[streamlevel].npar)
	      for (cp = (unsigned char *) stackText (parStack, 
			     stream[streamlevel].parMark[kpar-1]) ; *cp ; ++cp)
		{ if (++in >= cardEnd)
		    freeExtend (&in) ;
		  *in = *cp ;
		  if (isecho)
		    putchar (*in) ;
		}
	    else
	      messout ("Parameter %%%d can not be substituted", kpar) ;
	    if (++in >= cardEnd)
	      freeExtend (&in) ;
	    *in = ch ; 
	    goto lao ; /* mieg */
	  case '\\':		/* escapes next character - interprets \n */
	    *in = _FREECHAR ;
	    if (*in == '\n')    /* fold continuation lines */
	      { if (isInteractive && !streamlevel)
		  printf ("  Continuation >") ;
		while ((ch = _FREECHAR) == ' ' || ch == '\t') ;
			/* remove whitespace at start of next line */
		if (currfil)                     /* push back ch */
		  ungetc (ch, currfil) ;
		else
		  --currtext ;
		stream[streamlevel].line++ ;
		--in ;
	      }
#if !defined(WIN32)
	    else if (*in == 'n') /* reinterpret \n as a format */
	      { *in = '\n' ; 
	      }
#endif
	    else  /* keep the \ till freeword is called */
	      { *(in+1) = *in ;
		*in = '\\' ;
		if (++in >= cardEnd)
		  freeExtend (&in) ;
	      }
	    break ;
	  default:
	    messerror ("freesubs got unrecognised special character 0x%x = %c\n",
		     *in, *in) ;
	  }
      else
	{ if (!isprint(*in) && *in != '\t' && *in != '\n') /* mieg dec 15 94 */
	    --in ;
	  else if (isecho)	/* write it out */
	    putchar (*in) ;
	}
    }				/* while TRUE loop */
 
got_line:
  stream[streamlevel].line++ ;
  *in = 0 ;
  if (isecho)
    putchar ('\n') ;
  pos = card ;
  _losewhite ;
  if (acceptCommand && _stepover ('@'))        /* command file */
    { char *name ;
      if ((name = freeword ()) && 
	  (fil = filopen (name, 0, "r")))
	freesetfile (fil, (char*) pos) ;
      goto restart ;
    }
  if (acceptShell && _stepover ('$'))        /* shell command */
    {
#if !defined(MACINTOSH)
      system ((char*)pos) ;
#endif
      goto restart ;
    }

  return (char*) card ;
}
 
/************************************************/

void freecardback (void)    /* goes back one card */
{ stream[streamlevel].line-- ;
  freesettext ((char*) card, "") ;
}

/************************************************/
 
BOOL freeread (FILE *fil)                /* reads card from fil */
{
  unsigned char ch, *in = card ;
  int  *line, chint ;
  
  if (!assFind (filAss, fil, &line))
    { line = (int*) messalloc (sizeof (int)) ;
      assInsert (filAss, fil, line) ;
    }
 
  --in ;
  while (TRUE)
    { ++in ;
      if (in >= cardEnd)
	    freeExtend (&in) ;
	  chint = getc(fil) ;
	  if (ferror(fil))
	  	messerror ("chint was bad");
	  *in = chint ;
      switch (*in)
        {
	case '\n' :
	  ++*line ;
	case (unsigned char) EOF :
	  goto got_line ;
	case '/' :		/* // means start of comment */
	  if ((ch = getc (fil)) == '/')
	    { while (getc(fil) != '\n' && !feof(fil)) ;
	      ++*line ;
	      if (in > card)	/* // at start of line ignores line */
		goto got_line ;
	      else
		--in ; /* in = 0   unprintable, so backstepped */
	    }
	  else
	    ungetc (ch,fil) ;
	  break ;
	case '\\' :		/* escape next character */
	  *in = getc(fil) ;
	  if (*in == '\n')	/* continuation */
	    { ++*line ;
	      while (isspace (*in = getc(fil))) ;    /* remove whitespace */
	    }
	  else if (*in == '"' || *in == '\\') /* escape for freeword */
	    { *(in+1) = *in ;
	      *in = '\\' ;
	      ++in ;
	    }
	  /* NB fall through - in case next char is nonprinting */
	default:
	  if (!isprint (*in) && *in != '\t')	/* ignore control chars, e.g. \x0d */
	    --in ;
	}
    }
 
got_line :
  *in = 0 ;
  pos = card ;
  _losewhite ;
  if (feof(fil))
    { assRemove (filAss, fil) ;
      messfree (line) ;
    }
  return *pos || !feof(fil) ;
}

int freeline (FILE *fil)
{ int *line ;

  if (assFind (filAss, fil, &line))
    return *line ;
  else
    return 0 ;
}
 
int freestreamline (int level)
{ 
  return stream[level].line  ;
}
 
/********************************************/


static void freenewstream (char *parms)
{
  int kpar ;

  stream[streamlevel].fil = currfil ;
  stream[streamlevel].text = currtext ;
  if (++streamlevel == MAXSTREAM)
    messcrash ("MAXSTREAM overflow in freenewstream") ;
  strcpy (stream[streamlevel].special, stream[streamlevel-1].special) ;

  stream[streamlevel].npar = 0 ;
  stream[streamlevel].line = 1 ;

 if (!parms || !*parms)
    return ;                           /* can t abuse NULL ! */
  pos = (unsigned char *) parms ;			/* abuse freeword() to get parms */

  for (kpar = 0 ; kpar < MAXNPAR && freeword () ; kpar++) /* read parameters */
    { stream[streamlevel].parMark[kpar] = stackMark (parStack) ;
      pushText (parStack, (char*) word) ;
    }

  stream[streamlevel].npar = kpar ;
  stream[streamlevel].isPipe = FALSE ;
  pos = card ;			/* restore pos to start of blank card */
  *card = 0 ;
}
 
int freesettext (char *string, char *parms)
{
  freenewstream (parms) ;

  currfil = 0 ;
  currtext = string ;

  return streamlevel ;
}

int freesetfile (FILE *fil, char *parms)
{
  freenewstream (parms) ;

  currfil = fil ;
  currtext = 0 ;

  return streamlevel ;
}

int freesetpipe (FILE *fil, char *parms)
{
  freenewstream (parms) ;

  currfil = fil ;
  currtext = 0 ;
  stream[streamlevel].isPipe = TRUE ;
  return streamlevel ;
}

void freeclose(int level)
{ int kpar ;
  while (streamlevel >= level)
    { if (currfil && currfil != stdin && currfil != stdout)
	{
	  if (stream[streamlevel].isPipe)
	    pclose (currfil) ;
	  else
	    filclose (currfil) ;
	}
      for (kpar = stream[streamlevel].npar ; kpar-- ;)
	popText (parStack) ;
      --streamlevel ;
      currfil = stream[streamlevel].fil ;
      currtext = stream[streamlevel].text ;
      freespecial (stream[streamlevel].special) ;
    }
}

/************************************************/
/* freeword(), freewordcut() and freestep() are the only calls that
     directly move pos forward -- all others act via freeword().
   freeback() moves pos back one word.
*/
 
char *freeword (void)
{
  unsigned char *cw ;
 
  _losewhite ;             /* needed in case of intervening freestep() */

  if (_stepover ('"'))
    { for (cw = word ; !_stepover('"') && *pos ; *cw++ = *pos++)
	if (_stepover('\\'))	/* accept next char unless end of line */
	  if (!*pos)
	    break ;
      _losewhite ;
      *cw = 0 ;
      return (char*) word ;	/* always return a word, even if empty */
    }

		/* default: break on space and \t, not on comma */
  for (cw = word ; isgraph (*pos) && *pos != '\t' ; *cw++ = *pos++)
    if (_stepover('\\'))	/* accept next char unless end of line */
      if (!*pos)
	break ;
  _losewhite ;
  *cw = 0 ;
  return *word ? (char*) word : 0 ;
}
 
/************************************************/

#if defined(WIN32)

char *freepath (void)
{
  unsigned char *cw ;
 
  _losewhite ;             /* needed in case of intervening freestep() */

  if (_stepover ('"'))
    { for (cw = word ; !_stepover('"') && *pos ; *cw++ = *pos++)
	if (_stepover('\\'))	/* accept next char unless end of line */
	  if (!*pos)
	    break ;
      _losewhite ;
      *cw = 0 ;
      return (char*) word ;	/* always return a word, even if empty */
    }

  /* default: break on space, \t or end of line, not on comma
	 also, does not skip over backslashes which are assumed to be
	 MS DOS/Windows path delimiters */
  for (cw = word ; ( *pos == '\\' || isgraph (*pos) ) && *pos != '\t' ; *cw++ = *pos++) ;
  _losewhite ;
  *cw = 0 ;
  return *word ? (char*) word : 0 ;
}

#endif
 
/************************************************/
 
char *freewordcut (char *cutset, char *cutter)
        /* Moves along card, looking for a character from cut, which is a
           0-terminated char list of separators.
           Returns everything up to but not including the first match.
           pos is moved one char beyond the character.
           *cutter contains the char found, or if end of card is reached, 0.
        */
{ unsigned char *cc,*cw ;
 
  for (cw = word ; *pos ; *cw++ = *pos++)
    for (cc = (unsigned char *) cutset ; *cc ; ++cc)
      if (*cc == *pos)
        goto wcut ;
wcut:
  *cutter = *pos ;
  if (*pos)
    ++pos ;
  _losewhite ;
  *cw = 0 ;
  return *word ? (char*) word : 0 ;
}
 
/************************************************/
 
void freeback (void)    /* goes back one word - inefficient but reliable */
 
 {unsigned char *now = pos ;
  unsigned char *old = pos ;
 
  pos = card ; _losewhite ;
  while  (pos < now)
   {old = pos ;
    freeword () ;
   }
  pos = old ;
 }
 
/************************************************/

#define NON_INT -(1<<30)
#define NON_FLOAT -(1<<30)
 
BOOL freeint (int *p)
 
 {unsigned char *keep = pos ;
  unsigned char *cp ;
  int value = 0 ;
  BOOL isMinus = FALSE ;
 
  if (freeword ())
    { /*printf ("freeint got '%s'\n", word) ;*/
      cp = word ;
      if (!strcmp ((char*)cp, "NULL"))
	{ *p = NON_INT ;
	  return TRUE ;
	}
      if (*cp == '-')
        { isMinus = TRUE ;
          ++cp ;
        }
      while (*cp)
      	{ if (*cp >= '0' && *cp <= '9')
      	    value = value*10 + (*cp++ - '0') ;
      	  else
      	    { pos = keep ;
      	      return FALSE ;
      	    }
      	 }
   	  *p = isMinus ? -value : value ;
      return (TRUE) ;
    }
  else
    { pos = keep ;
      return (FALSE) ;
    }
 }
 
/*****************************/
 
BOOL freefloat (float *p)
{
  unsigned char *keep = pos ;
  float old = *p ;
  char dummy ; 
 
  if (freeword ())
    { if (!strcmp ((char*)word, "NULL"))
	{ *p = NON_FLOAT ;
	  return TRUE ;
	}
      if (sscanf ((char*) word,"%f%c",p,&dummy) == 1)
	return (TRUE) ;
    }

  pos = keep ;
  *p = old ;
  return (FALSE) ;
}
 
/**************************************************/
 
BOOL freedouble (double *p)
{ 
  unsigned char *keep = pos ;
  double old = *p ;
  char dummy ;
 
  if (freeword () && (sscanf ((char*) word,"%lf%c",p,&dummy) == 1))
    return (TRUE) ;
  else
    { pos = keep ;
      *p = old ;
      return (FALSE) ;
    }
}
 
/*************************************************/
 
static int ambiguouskey;
 
BOOL freekey (KEY *kpt, FREEOPT *options)
{
  unsigned char  *keep = pos ;

  if (!freeword())
    return FALSE ;

  if (freekeymatch ((char*) word, kpt, options))
    return TRUE;
 
  if (ambiguouskey)
    messout ("Keyword %s is ambiguous",word) ;
  else if (word[0] != '?')
    messout ("Keyword %s does not match",word) ;
 
  pos = keep ;
  return FALSE ;
}
 
/*****************/
 
BOOL freekeymatch (char *cp, KEY *kpt, FREEOPT *options)
{
  char  *io,*iw ;
  int   nopt = (int)options->key ;
  KEY   key ;
 
  ambiguouskey = FALSE;
  if (!nopt || !cp)
    return FALSE ;
 
  while (TRUE)
    { iw = cp ;
      io = (++options)->text ;

      while (freeupper (*iw++) == freeupper(*io++))
	if (!*iw)
	  goto foundit ;
      if (!--nopt)
        return FALSE ;
    }
 
foundit :
  key = options->key ;
 
  if (*io && *io != ' ')		/* not a full word match */
    while (--nopt)	/* check that later options are different */
      { io = (++options)->text ;
	iw = (char*) word ;
	while (freeupper (*iw++) == freeupper (*io++))
	  if (!*iw)
	    { ambiguouskey = TRUE;
	      return FALSE ;
	    }
      }
 
  *kpt = key ;
  return TRUE ;
}
 
/***************************************************/
  /* Return the text corresponding to the key */
char *freekey2text (KEY k, FREEOPT *o)  
{ int i = o->key ; char *title = o->text ;
  if (i<0)
    messcrash("Negative number of options in freekey2text") ;
  while (o++, i--) 
    if (o->key == k)
      return (o->text) ;
  return title ;
}

/***************************************************/
 
BOOL freeselect (KEY *kpt, FREEOPT *options)     /* like the old freemenu */
{
  if (isInteractive)
    printf ("%s > ",options[0].text) ;
  freecard (0) ;                       /* just get a card */
  if (isInteractive)
    while (freestep ('?'))            /* write out options list */
      { int i ;
	for (i = 1 ; i <= options[0].key ; i++)
	  printf ("  %s\n",options[i].text) ;
	printf ("%s > ",options[0].text) ;
	freecard (0) ;
      }
  return freekey (kpt,options) ;
}
  /* same but returns TRUE, -1, if stremlevel drops below level */
BOOL freelevelselect (int level, KEY *kpt, FREEOPT *options)     /* like the old freemenu */
{
  if (isInteractive)
    printf ("%s > ",options[0].text) ;
  if (!freecard (level)) /* try to get another card */
    { *kpt = (KEY)(-1) ;  
      return TRUE ;                       
    }
  if (isInteractive)
    while (freestep ('?'))            /* write out options list */
      { int i ;
	for (i = 1 ; i <= options[0].key ; i++)
	  printf ("  %s\n",options[i].text) ;
	printf ("%s > ",options[0].text) ;
	if (!freecard (level)) /* try to get another card */
	 { *kpt = (KEY)(-1) ;  
	   return TRUE ;                       
	 }
       }
  return freekey (kpt,options) ;
} 

/**************************************/
 
BOOL freequery (char *query)
{
  if (isInteractive)
    { int retval, answer = 0 ;
      printf ("%s (y or n) ",query) ;
      answer = getchar () ;
      retval = (answer == 'y' || answer == 'Y') ? TRUE : FALSE ;
      while (answer != (unsigned char) EOF &&
	     answer != -1 && /* mieg: used not to break on EOF in pipes */
	     answer != '\n')
        answer = getchar () ;
      return retval ;
    }
  else
    return TRUE ;
}
 
/**********/
 
BOOL freeprompt (char *prompt, char *dfault, char *fmt)
{ 
  if (isInteractive)
    printf("%s ? > ",prompt);
  freecard (0) ;                       /* just get a card */
  if (freecheck (fmt))
    return TRUE ;
  else
    { messout ("input mismatch : format '%s' expected, card was\n%s",
	       fmt, card) ;
      return FALSE ;
   }
}
 
/*************************************/
 
int freefmtlength (char *fmt)
 
 {char *cp ;
  int length = 0 ;
 
  if (isdigit((int)*fmt))
   {sscanf (fmt,"%d",&length) ;
    return length ;
   }
 
  for (cp = fmt ; *cp ; ++cp)
    switch (*cp)
     {
case 'i' : case 'f' : case 'd' : length += 8 ; break ;
case 'w' : length += 32 ; break ;
case 't' : length += 80 ; break ;
case 'o' :
      if (*++cp)
        messcrash ("'o' can not end free format %s",fmt) ;
      length += 2 ; break ;
     }
 
  if (!length)
    length = 40 ;
  return length ;
 }
 
/****************/
 
BOOL freecheck (char *fmt)
        /* checks that whatever is in card fits specified format
           note that 't' format option changes card by inserting a '"' */
 {unsigned char *keep = pos ;
  union {int i ; float r ; double d ;}
          target ;
  char *fp ;
  unsigned char *start ;
  int nquote = 1 ;
 
  for (fp = fmt ; *fp ; ++fp)
    switch (*fp)
     {
case 'w' : if (freeword ()) break ; else goto retFALSE ;
case 'i' : if (freeint (&target.i)) break ; else goto retFALSE ;
case 'f' : if (freefloat (&target.r)) break ; else goto retFALSE ;
case 'd' : if (freedouble (&target.d)) break ; else goto retFALSE ;
case 't' :      /* must insert '"' and escape any remaining '"'s or '\'s */
      for (start = pos ; *pos ; ++pos)
        if (*pos == '"' || *pos == '\\')
          ++nquote ;
      *(pos+nquote+1) = '"' ;		/* end of line */
      for ( ; pos >= start ; --pos)
	{ *(pos + nquote) = *pos ;
	  if (*pos == '"' || *pos == '\\')
	    *(pos + --nquote) = '\\' ;
        }
      *start = '"' ;
      goto retTRUE ;
case 'z' : if (freeword ()) goto retFALSE ; else goto retTRUE ;
case 'o' :
      if (!*++fp) messcrash ("'o' can not end free format %s",fmt) ;
      freestep (*fp) ; break ;
case 'b' : break; /* special for graphToggleEditor no check needed  il */
default :
      if (!isdigit((int)*fp) && !isspace((int)*fp))
        messerror ("unrecognised char %d = %c in free format %s",
		   *fp, *fp, fmt) ;
     }
 
retTRUE :
  pos = keep ; return TRUE ;
retFALSE :
  pos = keep ; return FALSE ;
 }
 
/************************ little routines ************************/
 
BOOL freestep (char x)
 {return (*pos && freeupper (*pos) == x && pos++) ;
 }
 
void freenext (void)
 {_losewhite ;
 }
 
char FREE_UPPER[] =
{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
  16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
  32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,
  48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
  64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,
  96,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,
  80,81,82,83,84,85,86,87,88,89,90,123,124,125,126,127
} ;

char FREE_LOWER[] =
{  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15, 
  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  
  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  
  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  
  64,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,  91,  92,  93,  94,  95,
  96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127
} ;

char* freepos (void)		/* cheat to give pos onwards */
{ return (char*) pos ;
}

#ifdef JUNK
/* we want a more direct reciprocal to free protect */
char* freeunprotect (char *text)
{ int level ; char *cp ;
  static Stack s = 0 ;

  if (!text || !*text) return 0 ;
  s = stackReCreate (s, 80) ;
  level = freesettext (text,"") ;
  freespecial ("\t\\") ;    /* No \n, no subshells, No attach, No %, Nothing */
  freecard(level) ; 
  while ((cp = freeword()))
    { if (stackMark(s)) catText (s, " ") ; 
      catText (s, cp); 
    }
  freeclose (level) ;
  return stackMark (s) ? stackText (s, 0) : "" ; /* is text is not 0, do not return 0 */
}
#endif
char* freeunprotect (char *text)
{
  static char *buf = 0 ;
  char *cp, *cp0, *cq ;
  messfree (buf) ;
  buf = strnew(text ? text : "", 0) ;

  /* remove external space and tabs and first quotes */
  cp = buf ;
  while (*cp == ' ' || *cp == '\t') cp++ ;
  if (*cp == '"') cp++ ;
  while (*cp == ' ' || *cp == '\t') cp++ ;

  cq = cp + strlen(cp) - 1 ;
  while (cq > cp && (*cp == ' ' || *cq == '\t')) *cq-- = 0 ;
  if (*cq == '"') /* remove one unprotected quote */
    {
      int i = 0 ; char *cr = cq - 1 ;
      while (cr > cp && *cr == '\\')
	{ i++ ; cr-- ; }
      if ( i%2 == 0)
	*cq-- = 0 ;  /* discard */
    }
  while (cq > cp && (*cp == ' ' || *cq == '\t')) *cq-- = 0 ;

  /* gobble the \ */
  cp0 = cq = cp-- ;
  while (*++cp)
    switch (*cp)
      {
      case '\\': 
	if (*(cp+1) == '\\') { cp++ ; *cq++ = '\\' ; break ;}
	if (*(cp+1) == '\n') { cp ++ ; break ; } /* skip backsalh-newline */
	if (*(cp+1) == 'n') { cp ++ ; *cq++ = '\n' ; break ; }
	break ;
      default: *cq++ = *cp ;
      }
  *cq = 0 ;   /* terminate the string */
  return cp0 ;
}

char* freeprotect (char* text)	/* freeword will read result back as text */
{
  static Array a = 0 ;
  char *cp, *cq ;
  int base ;

		/* code to make this efficiently reentrant */

  if (a && text >= arrp(a,0,char) && text < arrp(a,arrayMax(a),char))
    { base = text - arrp(a,0,char) ;
      array (a, base+3*(1+strlen(text)), char) = 0 ; /* ensure long enough */
      text = arrp(a,0,char) + base ;            /* may have relocated */
      base += 1 + strlen(text) ;
    }
  else
    { a = arrayReCreate (a, 128, char) ;
      base = 0 ;
      array (a, 2*(1+strlen(text)), char) = 0 ; /* ensure long enough */
    }

  cq = arrp (a, base, char) ;
  *cq++ = '"' ;
  for (cp = text ; *cp ; *cq++ = *cp++)
    { if (*cp == '\\' || *cp == '"' || 		       /* protect these */
	  *cp == '/' || *cp == '%' || *cp == ';' ||
	  *cp == '\t' || *cp == '\n')
	*cq++ = '\\' ;
      if (*cp == '\n') {*cq++ = 'n' ; *cq++ = '\\' ; } /* -> /n/n (text then real) */
    }
  *cq++ = '"' ;
  *cq = 0 ;
  return arrp (a, base, char) ;
}

char* freejavaprotect (char* text)	/* freeword will read result back as text */
{
  static Array a = 0 ;
  char *cp, *cq ;
  int base ;

		/* code to make this efficiently reentrant */

  if (a && text >= arrp(a,0,char) && text < arrp(a,arrayMax(a),char))
    { base = text - arrp(a,0,char) ;
      array (a, base+3*(1+strlen(text)), char) = 0 ; /* ensure long enough */
      text = arrp(a,0,char) + base ;            /* may have relocated */
      base += 1 + strlen(text) ;
    }
  else
    { a = arrayReCreate (a, 128, char) ;
      base = 0 ;
      array (a, 2*(1+strlen(text)), char) = 0 ; /* ensure long enough */
    }

  cq = arrp (a, base, char) ;
  cp = text;
#ifdef JUNK
  while (*cp) {
    if (*cp == '\t' || *cp == '\n' || *cp == '\r') {
      *cq++ = '\\' ;
      switch (*cp) {
      case '\t':
	*cq++ = 't';
	break;
      case '\n':
	*cq++ = 'n';
	break;
      case '\r':
	*cq++ = 'r';
	break;
      default:
	;
      }
      cp++; /* skip this character */
    } 
    else {
      if (*cp == '?') *cq++ = '\\';
      *cq++ = *cp++;
    }
  }
#endif
  while (*cp) 
    switch (*cp)
      {
      case '\n':
	*cq++ = '\\';
	*cq++ = 'n';
	cp++;
	break;
      case '\\': case '?':
	*cq++ = '\\' ;
	/* fall thru */
      default:
	*cq++ = *cp++;
      }
  *cq = 0 ;
  return arrp (a, base, char) ;
}

/*********** end of file *****************/