/*
 * tclXutil.c
 *
 * Utility functions for Extended Tcl.
 *-----------------------------------------------------------------------------
 * Copyright 1991-1993 Karl Lehenbauer and Mark Diekhans.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies.  Karl Lehenbauer and
 * Mark Diekhans make no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *-----------------------------------------------------------------------------
 * $Id: tclXutil.c,v 3.0 1993/11/19 06:59:28 markd Rel $
 *-----------------------------------------------------------------------------
 */

#include "tclExtdInt.h"

#ifndef _tolower
#  define _tolower tolower
#  define _toupper toupper
#endif

/*
 * Used to return argument messages by most commands.
 */
char *tclXWrongArgs = "wrong # args: ";


/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_StrToLong --
 *      Convert an Ascii string to an long number of the specified base.
 *
 * Parameters:
 *   o string (I) - String containing a number.
 *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
 *     based on the leading characters of the number.  Zero to let the number
 *     determine the base.
 *   o longPtr (O) - Place to return the converted number.  Will be 
 *     unchanged if there is an error.
 *
 * Returns:
 *      Returns 1 if the string was a valid number, 0 invalid.
 *-----------------------------------------------------------------------------
 */
int
Tcl_StrToLong (string, base, longPtr)
    CONST char *string;
    int         base;
    long       *longPtr;
{
    char *end;
    long  num;

    num = strtol(string, &end, base);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0))
        return FALSE;
    *longPtr = num;
    return TRUE;

}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_StrToInt --
 *      Convert an Ascii string to an number of the specified base.
 *
 * Parameters:
 *   o string (I) - String containing a number.
 *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
 *     based on the leading characters of the number.  Zero to let the number
 *     determine the base.
 *   o intPtr (O) - Place to return the converted number.  Will be 
 *     unchanged if there is an error.
 *
 * Returns:
 *      Returns 1 if the string was a valid number, 0 invalid.
 *-----------------------------------------------------------------------------
 */
int
Tcl_StrToInt (string, base, intPtr)
    CONST char *string;
    int         base;
    int        *intPtr;
{
    char *end;
    int   num;

    num = strtol(string, &end, base);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0))
        return FALSE;
    *intPtr = num;
    return TRUE;

}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_StrToUnsigned --
 *      Convert an Ascii string to an unsigned int of the specified base.
 *
 * Parameters:
 *   o string (I) - String containing a number.
 *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
 *     based on the leading characters of the number.  Zero to let the number
 *     determine the base.
 *   o unsignedPtr (O) - Place to return the converted number.  Will be 
 *     unchanged if there is an error.
 *
 * Returns:
 *      Returns 1 if the string was a valid number, 0 invalid.
 *-----------------------------------------------------------------------------
 */
int
Tcl_StrToUnsigned (string, base, unsignedPtr)
    CONST char *string;
    int         base;
    unsigned   *unsignedPtr;
{
    char          *end;
    unsigned long  num;

    num = strtoul (string, &end, base);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0))
        return FALSE;
    *unsignedPtr = num;
    return TRUE;

}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_StrToDouble --
 *   Convert a string to a double percision floating point number.
 *
 * Parameters:
 *   string (I) - Buffer containing double value to convert.
 *   doublePtr (O) - The convert floating point number.
 * Returns:
 *   TRUE if the number is ok, FALSE if it is illegal.
 *-----------------------------------------------------------------------------
 */
int
Tcl_StrToDouble (string, doublePtr)
    CONST char *string;
    double     *doublePtr;
{
    char   *end;
    double  num;

    num = strtod (string, &end);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0))
        return FALSE;

    *doublePtr = num;
    return TRUE;

}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_DownShift --
 *     Utility procedure to down-shift a string.  It is written in such
 *     a way as that the target string maybe the same as the source string.
 *
 * Parameters:
 *   o targetStr (I) - String to store the down-shifted string in.  Must
 *     have enough space allocated to store the string.  If NULL is specified,
 *     then the string will be dynamicly allocated and returned as the
 *     result of the function. May also be the same as the source string to
 *     shift in place.
 *   o sourceStr (I) - The string to down-shift.
 *
 * Returns:
 *   A pointer to the down-shifted string
 *-----------------------------------------------------------------------------
 */
char *
Tcl_DownShift (targetStr, sourceStr)
    char       *targetStr;
    CONST char *sourceStr;
{
    register char theChar;

    if (targetStr == NULL)
        targetStr = ckalloc (strlen ((char *) sourceStr) + 1);

    for (; (theChar = *sourceStr) != '\0'; sourceStr++) {
        if (isupper (theChar))
            theChar = _tolower (theChar);
        *targetStr++ = theChar;
    }
    *targetStr = '\0';
    return targetStr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_UpShift --
 *     Utility procedure to up-shift a string.
 *
 * Parameters:
 *   o targetStr (I) - String to store the up-shifted string in.  Must
 *     have enough space allocated to store the string.  If NULL is specified,
 *     then the string will be dynamicly allocated and returned as the
 *     result of the function. May also be the same as the source string to
 *     shift in place.
 *   o sourceStr (I) - The string to up-shift.
 *
 * Returns:
 *   A pointer to the up-shifted string
 *-----------------------------------------------------------------------------
 */
char *
Tcl_UpShift (targetStr, sourceStr)
    char       *targetStr;
    CONST char *sourceStr;
{
    register char theChar;

    if (targetStr == NULL)
        targetStr = ckalloc (strlen ((char *) sourceStr) + 1);

    for (; (theChar = *sourceStr) != '\0'; sourceStr++) {
        if (ISLOWER (theChar))
            theChar = _toupper (theChar);
        *targetStr++ = theChar;
    }
    *targetStr = '\0';
    return targetStr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_DStringGets --
 *
 *    Reads a line from a file into a dynamic string.  The string will be
 * expanded, if necessary and reads are done until EOL or EOF is reached.
 * The line is appended to any data already in the string.
 *
 * Parameter
 *   o filePtr (I) - File to read from.
 *   o dynStrPtr (I) - String to return the data in.
 * Returns:
 *    o TCL_BREAK - EOF
 *    o TCL_OK - If data was transfered.
 *    o TCL_ERROR - An error occured.
 *-----------------------------------------------------------------------------
 */
int
Tcl_DStringGets (filePtr, dynStrPtr)
    FILE         *filePtr;
    Tcl_DString  *dynStrPtr;
{
    char           buffer [128];
    register char *bufPtr, *bufEnd;
    register int   readVal;
    int            startLength = dynStrPtr->length;

    bufPtr = buffer;
    bufEnd = buffer + sizeof (buffer) - 1;

    clearerr (filePtr);  /* Clear previous error/EOF */

    while (TRUE) {
        readVal = getc (filePtr);
        if (readVal == '\n')      /* Is it a new-line? */
            break;
        if (readVal == EOF)
            break;
        *bufPtr++ = readVal;
        if (bufPtr > bufEnd) {
            Tcl_DStringAppend (dynStrPtr, buffer, sizeof (buffer));
            bufPtr = buffer;
        }
    }
    if ((readVal == EOF) && ferror (filePtr))
        return TCL_ERROR;   /* Error */

    if (bufPtr != buffer) {
        Tcl_DStringAppend (dynStrPtr, buffer, bufPtr - buffer);
    }

    if ((readVal == EOF) && dynStrPtr->length == startLength)
        return TCL_BREAK;
    else
        return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_GetLong --
 *
 *      Given a string, produce the corresponding long value.
 *
 * Results:
 *      The return value is normally TCL_OK;  in this case *longPtr
 *      will be set to the integer value equivalent to string.  If
 *      string is improperly formed then TCL_ERROR is returned and
 *      an error message will be left in interp->result.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */
int
Tcl_GetLong(interp, string, longPtr)
    Tcl_Interp *interp;
    CONST char *string;
    long       *longPtr;
{
    char *end;
    long  i;

    i = strtol(string, &end, 0);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0)) {
        Tcl_AppendResult (interp, "expected integer but got \"", string,
                          "\"", (char *) NULL);
        return TCL_ERROR;
    }
    *longPtr = i;
    return TCL_OK;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_GetUnsigned --
 *
 *      Given a string, produce the corresponding unsigned integer value.
 *
 * Results:
 *      The return value is normally TCL_OK;  in this case *unsignedPtr
 *      will be set to the integer value equivalent to string.  If
 *      string is improperly formed then TCL_ERROR is returned and
 *      an error message will be left in interp->result.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */
int
Tcl_GetUnsigned(interp, string, unsignedPtr)
    Tcl_Interp *interp;
    CONST char *string;
    unsigned   *unsignedPtr;
{
    char          *end;
    unsigned long  i;

    /*
     * Since some strtoul functions don't detect negative numbers, check
     * in advance.
     */
    while (ISSPACE(*string))
        string++;
    if (string [0] == '-')
        goto badUnsigned;

    i = strtoul(string, &end, 0);
    while ((*end != '\0') && ISSPACE(*end))
        end++;

    if ((end == string) || (*end != '\0'))
        goto badUnsigned;

    *unsignedPtr = i;
    return TCL_OK;

  badUnsigned:
    Tcl_AppendResult (interp, "expected unsigned integer but got \"", 
                      string, "\"", (char *) NULL);
    return TCL_ERROR;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_GetTime --
 *
 *      Given a string, produce the corresponding time_t value.
 *
 * Results:
 *      The return value is normally TCL_OK;  in this case *timepPtr
 *      will be set to the integer value equivalent to string.  If
 *      string is improperly formed then TCL_ERROR is returned and
 *      an error message will be left in interp->result.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */
int
Tcl_GetTime(interp, string, timePtr)
    Tcl_Interp *interp;
    CONST char *string;
    time_t     *timePtr;
{
    char   *end;
    long   i;
    time_t time;

    i = strtol(string, &end, 0);
    while ((*end != '\0') && ISSPACE(*end)) {
        end++;
    }
    if ((end == string) || (*end != 0))
        goto badTime;

    time = (time_t) i;
    if (time != i)
        goto badTime;

    *timePtr = time;
    return TCL_OK;

  badTime:
    Tcl_AppendResult (interp, "integer time \"", string, "\" to large\"",
                      (char *) NULL);
    return TCL_ERROR;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_RelativeExpr --
 *
 *    Evaluate an expression that may start with the magic words "end" or
 * "len".  These strings are replaced with either the end offset or the
 * length that is passed in.
 *
 * Parameters:
 *   o interp (I) - A pointer to the interpreter.
 *   o cstringExpr (I) - The expression to evaludate.
 *   o stringLen (I) - The length of the string.
 *   o exprResultPtr (O) - The result of the expression is returned here.
 * Returns:
 *   TCL_OK or TCL_ERROR.
 *-----------------------------------------------------------------------------
 */
int
Tcl_RelativeExpr (interp, cstringExpr, stringLen, exprResultPtr)
    Tcl_Interp  *interp;
    char        *cstringExpr;
    long         stringLen;
    long        *exprResultPtr;
{
    
    char *buf;
    int   exprLen, result;
    char  staticBuf [64];

    if (!(STRNEQU (cstringExpr, "end", 3) ||
          STRNEQU (cstringExpr, "len", 3))) {
        return Tcl_ExprLong (interp, cstringExpr, exprResultPtr);
    }

    sprintf (staticBuf, "%ld",
             stringLen - ((cstringExpr [0] == 'e') ? 1 : 0));
    exprLen = strlen (staticBuf) + strlen (cstringExpr) - 2;

    buf = staticBuf;
    if (exprLen > sizeof (staticBuf)) {
        buf = (char *) ckalloc (exprLen);
        strcpy (buf, staticBuf);
    }
    strcat (buf, cstringExpr + 3);

    result = Tcl_ExprLong (interp, buf, exprResultPtr);

    if (buf != staticBuf)
        ckfree (buf);
    return result;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_GetOpenFileStruct --
 *
 *    Convert a file handle to a pointer to the internal Tcl file structure.
 *
 * Parameters:
 *   o interp (I) - Current interpreter.
 *   o handle (I) - The file handle to convert.
 * Returns:
 *   A pointer to the open file structure for the file, or NULL if an error
 * occured.
 *-----------------------------------------------------------------------------
 */
OpenFile *
Tcl_GetOpenFileStruct (interp, handle)
    Tcl_Interp *interp;
    char       *handle;
{
    FILE   *filePtr;

    if (Tcl_GetOpenFile (interp, handle,
                         FALSE, FALSE,  /* No checking */
                         &filePtr) != TCL_OK)
        return NULL;

    return tclOpenFiles [fileno (filePtr)];
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_SetupFileEntry --
 *
 * Set up an entry in the Tcl file table for a file number, including the stdio
 * FILE structure.
 *
 * Parameters:
 *   o interp (I) - Current interpreter.
 *   o fileNum (I) - File number to set up the entry for.
 *   o permissions (I) - Flags consisting of TCL_FILE_READABLE,
 *     TCL_FILE_WRITABLE.
 * Returns:
 *   A pointer to the FILE structure for the file, or NULL if an error
 * occured.
 *-----------------------------------------------------------------------------
 */
FILE *
Tcl_SetupFileEntry (interp, fileNum, permissions)
    Tcl_Interp *interp;
    int         fileNum;
    int         permissions;
{
    Interp   *iPtr = (Interp *) interp;
    char     *mode;
    FILE     *filePtr;

    /*
     * Set up a stdio FILE control block for the new file.
     */
    if (permissions & TCL_FILE_WRITABLE) {
        if (permissions & TCL_FILE_READABLE)
            mode = "r+";
        else
            mode = "w";
    } else {
        mode = "r";
    }

    filePtr = fdopen (fileNum, mode);
    if (filePtr == NULL) {
        iPtr->result = Tcl_PosixError (interp);
        return NULL;
    }
    
    Tcl_EnterFile (interp, filePtr, permissions);

    return filePtr;
}

/*
 *-----------------------------------------------------------------------------
 *
 * Tcl_CloseForError --
 *
 *   Close a file number on error.  If the file is in the Tcl file table, clean
 * it up too. The variable errno, and interp->result and the errorCode variable
 * will be saved and not lost.
 *
 * Parameters:
 *   o interp (I) - Current interpreter.
 *   o fileNum (I) - File number to close.
 *-----------------------------------------------------------------------------
 */
void
Tcl_CloseForError (interp, fileNum)
    Tcl_Interp *interp;
    int         fileNum;
{
    static char *ERROR_CODE = "errorCode";
    int          saveErrNo = errno;
    char        *saveResult, *errorCode, *saveErrorCode, *argv [2], buf [32];

    saveResult = ckstrdup (interp->result);

    errorCode = Tcl_GetVar (interp, ERROR_CODE, TCL_GLOBAL_ONLY);
    if (errorCode != NULL)
        saveErrorCode = ckstrdup (errorCode);
    else
        saveErrorCode = NULL;

    sprintf (buf, "file%d", fileNum);

    argv [0] = "close";
    argv [1] = buf;
    Tcl_CloseCmd (NULL, interp, 2, argv);
    Tcl_ResetResult (interp);

    if (saveErrorCode != NULL) {
        Tcl_SetVar (interp, ERROR_CODE, saveErrorCode, TCL_GLOBAL_ONLY);
        free (saveErrorCode);
    }
    Tcl_SetResult (interp, saveResult, TCL_VOLATILE);
    free (saveResult);

    close (fileNum);  /* In case Tcl didn't have it open */
    
    errno = saveErrNo;
}
     
