/*
 * Copyright (C) 20011126  Pete Vassoff <pfv@grex.org> (A complete rewrite of Chris' implementation.)
 * Copyright (C) 1999-2005 Chris Ross
 * Copyright (c) 2002-2005 Alex Holden
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * o Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * o Neither the name of the ferite software nor the names of its contributors may
 *   be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

uses "string.lib", "array"; /* array is needed by sprintf() */

module-header
{

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#ifdef VCWIN32
# include <snprintf.h>
#endif
    
}

/**
 * @namespace String
 * @brief Provides a set of functions to manipulate strings
 */
namespace String
{

    /**
     * @function length
     * @declaration function length( string str )
     * @brief Finds the length of a string
     * @param string str The string
     * @return The number of characters in the string
     */
    native function length( string str )
    {
        FE_RETURN_LONG( str->length );
    }

    /**
     * @function toDouble
     * @declaration function toDouble( string str )
     * @brief Converts a string to a number stored internally as a double
     * @param string str The string
     * @return The number
     * @description This function converts the specified string to a number,
     *              which will be internally represented as a double. Note
     *              that it does not perform any error checking at all. Use
     *              String.toNumber() instead on new projects.
     */
    native function toDouble( string str )
    {
        double value;
        value = atof( str->data );
        FE_RETURN_DOUBLE( value );
    }

    /**
     * @function toLong
     * @declaration function toLong( string str )
     * @brief Converts a string to a number stored internally as a long
     * @param string str The string
     * @return The number
     * @description This function converts the specified string to a number,
     *              which will be internally represented as a long. Note that
     *              it does not perform any error checking at all. Use
     *              String.toNumber(), and if necessary round it to an integer
     *              instead on new projects.
     */
    native function toLong( string str )
    {
        long value;
        value = atol( str->data );
        FE_RETURN_LONG( value );
    }

    /**
     * @function toHex
     * @declaration function toHex( string s )
     * @brief Interprets a string as a number and converts it to a hexadecimal string representation of it
     * @param string s The input string
     * @return A string containing the hex representation of the input string
     * @description This function performs a similar function to the standard
     *              Unix utility, hexdump. It is useful for converting a block
     *              of binary data into a printable hexadecimal string.
     */
    native function toHex(string s)
    {
        int i;
        char *p;
        FeriteString *str;
        if(!(str = ferite_str_new(NULL, s->length * 2, FE_CHARSET_DEFAULT)))
        {
            FE_RETURN_NULL_OBJECT;
        }
        p = str->data;
        for(i = 0; i < s->length; i++)
        {
            sprintf(p, "%.2x", (unsigned char)s->data[i]);
            p += 2;
        }
        FE_RETURN_STR(str, FE_TRUE);
    }

    /**
     * @function numToChar
     * @declaration function numToChar( number n )
     * @brief Converts a number to a character in a string of length one
     * @param number n The number
     * @return A single character string
     * @description Converts the ASCII number n into a character and returns it
     *              as a string one character long. The number should be a
     *              positive integer less than 255. If it is not, a zero
     *              length string will be returned.
     * @example <nl/><code>
     <type>string</type> space = String.numToChar(32); &raquo; space = " "</code><nl/>     
     */
    native function numToChar(number n)
    {
        unsigned char c;
        FeriteString *str;

        if((long)n < 0 || (long)n > 255)
        {
            str = ferite_str_new(NULL, 0, FE_CHARSET_DEFAULT);
            FE_RETURN_STR(str, FE_TRUE);
        }
        else c = (unsigned char)n;

        if(!(str = ferite_str_new(&c, 1, FE_CHARSET_DEFAULT)))
        {
            FE_RETURN_NULL_OBJECT;
        }
        FE_RETURN_STR(str, FE_TRUE);
    }

    /**
     * @function charToNum
     * @declaration function charToNum( string s )
     * @brief Converts the first character of a string to a number
     * @param string s The string containing the character to convert
     * @return An integer number in the range 0 to 255 or -1 on error.
     * @description This function converts the first character of the specified
     *              string into its numerical equivalent.
     * @example <nl/><code>
     <type>number</type> space = String.numToChar(" "); &raquo; space = 32</code><nl/>
     */
    native function charToNum(string s)
    {
        if(s->length == 0)
        {
            FE_RETURN_LONG(-1);
        }
        else
        {
            FE_RETURN_LONG((long)s->data[0]);
        }
    }

    /**
     * @function index
     * @declaration function index( string a, string b )
     * @brief Finds the first occurance of string b in string a
     * @param string a The string to search through
     * @param string b The string to search for
     * @return The index number of the substring if found, or -1 if not found
     * @example <nl/><code>
     <type>number</type> space = String.index("Hello W<keyword>or</keyword>ld", "W<keyword>or</span>ld"); &raquo; space = 6</code><nl/>
     */
    native function index( string a, string b )
    {
        FE_RETURN_LONG( ferite_find_string( a->data, b->data ) );
    }

    /**
     * @function isNumber
     * @declaration function isNumber( string s )
     * @brief Checks whether a string represents a valid number
     * @param string s The string
     * @return True if the string represents a valid number, false if it doesn't
     * @description This function is used to discover whether String.toNumber()
     *              will fail when called with a particular string or not. A
     *              string which causes isNumber() to return true won't
     *              necessarily work with String.toDouble() or String.toLong()
     *              because String.toNumber() copes with many cases which the
     *              above functions do not. An alternative to calling this
     *              function before String.toNumber() is to check err.number
     *              for failure after the String.toNumber() call.
     * @example <nl/><code>
     <type>number</type> space = 0;<nl/>
     <keyword>if</keyword>( String.isNumber("32") ) {<nl/>
         <tab/>space = String.toNumber("32");<nl/>
     } &raquo; space = 32</code><nl/>
     */
    native function isNumber(string s)
    {
        char *ep;

        errno = 0;
        if(s->length == 0) FE_RETURN_FALSE;
        strtod(s->data, &ep);
        if(errno == ERANGE || *ep != 0)
        {
            FE_RETURN_FALSE;
        }
        else
        {
            FE_RETURN_TRUE;
        }
    }

    /**
     * @function toNumber
     * @declaration function toNumber( string s )
     * @brief Converts a string to a number
     * @param string s The string
     * @return The number
     * @description This function attempts to convert a string to a number.
     *              If the number is too large to be represented by a double,
     *              (note that this requires a very large number indeed) it
     *              returns HUGE_VAL or -HUGE_VAL and sets err.number to
     *              ERANGE. The actual value of HUGE_VAL depends on your C
     *              library (on some systems it is a special value which means
     *              infinity). If the string doesn't represent a valid number,
     *              0 is returned and err.number is set to Sys.EINVAL. If the
     *              conversion is successful, the result is returned as a
     *              number and err.number is set to 0. Note that this function
     *              is much more powerful and not significantly slower than
     *              String.toLong() and String.toDouble(), so it should always
     *              be used in preference to them in new projects.
     * @example <nl/><code>
     <type>number</type> space = 0;<nl/>
     <keyword>if</keyword>( String.isNumber("32") ) {<nl/>
         <tab/>space = String.toNumber("32");<nl/>
     } &raquo; space = 32</code><nl/>
     */
    native function toNumber(string s)
    {
        long l;
        double d = 0.0;
        char *ep;

        if(s->length)
        {
            errno = 0;
            l = strtol(s->data, &ep, 0);
            if(errno != ERANGE && *ep == 0) FE_RETURN_LONG(l);
            errno = 0;
            d = strtod(s->data, &ep);
            if(errno == ERANGE)
            {
                ferite_set_error(script, errno, "%s", strerror(errno));
                FE_RETURN_DOUBLE(d);
            }
        }

        if(!s->length || *ep != 0)
        {
            ferite_set_error(script, EINVAL, "String does not represent a "
                             "valid number");
            FE_RETURN_LONG(0);
        }

        ferite_set_error(script, 0, "");
        FE_RETURN_DOUBLE(d);
    }

    /**
     * @function reverse
     * @declaration function reverse( string s )
     * @brief Reverses a string
     * @param string s The input string
     * @return The input string with the order of the characters reversed
     * @example <nl/><code>
     <type>string</type> s = String.reverse("gninnuc"); &raquo; s = "cunning"</code><nl/>
     */
    function reverse(string s)
    {
        return s[String.length(s) - 1..0];
    }

    /**
     * @function dissect
     * @declaration function dissect(string s, string d, number limit)
     * @brief Dissects a string into an array with single character delimiters
     * @param string s The string to dissect
     * @param string d The list of single delimiting characters
     * @param number limit If greater than zero, limits the number of cuts
     * @return Array of the resulting substrings
     * @description This function dissects string s into an array of substrings
     *              delimited by the characters in string d. It is similar to
     *              String.toArray() except that it expects a list of single
     *              character delimiters instead of a single delimiting string.
     *              For example, if s contains "aaa1bbbb2ccc3dddd" and d
     *              contains "123" the result will be ["aaa", "bbbb", "ccc",
     *              "dddd"]. If none of the delimiting characters are present
     *              in the string, the result array will contain a single
     *              string which is an exact copy of the input string. If limit
     *              is 0, dissect() will cut the string every time it
     *              encounters a delimiting character. If it is greater than
     *              0, it will not cut the string more than limit times. If the
     *              limit parameter is omitted, there is no limit to the number
     *              of times the string will be cut.
     */
    native function dissect(string s, string d, number limit)
    {
        int cuts = 0;
        int left, right, i;
        FeriteVariable *a, *fv;

        if(!(a = ferite_create_uarray_variable(script, "string::split", 0,
                                               FE_STATIC)))
        {
            FE_RETURN_NULL_OBJECT;
        }

        for(left = right = 0; right < s->length; right++)
        {
            for(i = 0; i < d->length; i++)
            {
                if(s->data[right] == d->data[i])
                {
                    if((right - left) > 0)
                    {
                        fv = ferite_create_string_variable_from_ptr(script,
                                                                    "", s->data + left, right - left,
                                                                    FE_CHARSET_DEFAULT, FE_STATIC);
                        ferite_uarray_add(script, VAUA(a), fv, NULL,
                                          FE_ARRAY_ADD_AT_END);
                        cuts++;
                    }
                    left = right + 1;
                    break;
                }
            }
            if(limit > 0 && cuts == (int)limit) break;
        }

        /* Handle anything left on the end of the string: */
        if((s->length - left) > 0)
        {
            fv = ferite_create_string_variable_from_ptr(script, "",
                                                        s->data + left, s->length - left,
                                                        FE_CHARSET_DEFAULT, FE_STATIC);
            ferite_uarray_add(script, VAUA(a), fv, NULL, FE_ARRAY_ADD_AT_END);
        }

        FE_RETURN_VAR(a);
    }
    function dissect(string s, string d)
    {
        return(String.dissect(s, d, 0));
    }

    /**
     * @function toArray
     * @brief Splits a string up into an array based on a string delimiter
     * @declaration function toArray(string s, string d, number limit)
     * @param string str The string to split
     * @param string delims The string to use as the delimiter
     * @description This function splits string s into an array of substrings
     *              delimited by string d. It is similar to String.dissect()
     *              except that it expects a string as a delimiter instead of
     *              a list of single characters. For example, if s contains
     *              "foo equals bar" and d contains " equals " then the
     *              result will be ["foo", "bar"]. If d is not present in
     *              s, then the returned array will contain a single string
     *              which is an exact copy of s. If limit is greater than 0,
     *              toArray() will not break the string more than limit times.
     *              Otherwise, it will break the string every time it
     *              encounters the delimiting string. Note: it is possible to
     *              omit the limit parameter, and it will default to 0.
     */
    native function toArray( string str, string delims, number limit )
    {
        int splits = 0;
        char *name = NULL;
        FeriteVariable *array = NULL, *vstr = NULL;
        int i = 0, j = 0, start = 0;

        if( str->length > 0 && delims->length > 0 )
        {
            name = FE_CLEAN_STRING(str->length+1);
            array = ferite_create_uarray_variable( script, "string::toArray", 100, FE_STATIC );

            for( i = 0; i < str->length; i++ )
            {
                if( str->data[i] == delims->data[0] )
                {
                    for( j = 0; delims->data[j] == str->data[j+i] && (j+i) < str->length && j < delims->length; j++ )
                      ;
                    if( j == delims->length )
                    {
                        strncpy( name, str->data+start, i - start ); /* copy the data */
                        name[i-start] = '\0';
                        if( strlen( name ) >= 0 )
                        {
                            vstr = ferite_create_string_variable_from_ptr( script, "", name, strlen(name), FE_CHARSET_DEFAULT, FE_STATIC );
                            ferite_uarray_add( script, VAUA(array), vstr, NULL, FE_ARRAY_ADD_AT_END );
                        }
                        start = i + j;
                        i += j - 1;
                        splits++;
                    }
                }
                if(limit > 0 && splits == (int)limit) break;
            }
            if( start < str->length )
            {
                strncpy( name, str->data+start, str->length - start ); /* copy the data */
                name[str->length - start] = '\0';
                vstr = ferite_create_string_variable_from_ptr( script, "", name, strlen(name), FE_CHARSET_DEFAULT, FE_STATIC );
                ferite_uarray_add( script, VAUA(array), vstr, NULL, FE_ARRAY_ADD_AT_END );
            }
            ffree( name );
            FE_RETURN_VAR( array );
        }
    }
    function toArray(string str, string delims)
    {
        return(String.toArray(str, delims, 0));
    }

    /**
     * @function trim
     * @declaration function trim( string str, string delims )
     * @brief Trims a string (front and back) using the specified delimiters
     * @param string str The string to trim
     * @param string delims The list of delimiters
     * @return The trimmed string
     * @description This function removes all occurrences of any of the
     *              characters in the delims string from the front and back of
     *              the specified string.
     * @example <nl/><code>
     <type>string</type> s = String.trim(" FNAR<tab/>  ", " "); &raquo; s = "FNAR"</code><nl/>
     */
    native function trim( string str, string delims )
    {
        int i,j;
        char *p;
        FeriteVariable *var;

        for( i = 0 ; i < str->length ; i++ )
        {
      /* FIXME Change this after code defrost:
            if( memchr(delims->data, str->data[i], delims->length) == NULL ) */
            if( strchr(delims->data, str->data[i]) == NULL )
              break;
        }
        for( j = str->length ; j > i-1 ; j-- )
        {
      /* FIXME Change this after code defrost:
            if( memchr(delims->data, str->data[i], delims->length) == NULL ) */
            if( strchr(delims->data, str->data[j]) == NULL )
              break;
        }
        j++;
        p = ( j - i == 0) ? "" : str->data + i;
        var = fe_new_str_static( "string::trim", p, j - i, FE_CHARSET_DEFAULT );
        FE_RETURN_VAR( var );
    }

    /**
     * @function preTrim
     * @declaration function preTrim( string str, string delims )
     * @brief Trims the front of a string using the specified delimiters
     * @param string str The string to trim
     * @param string delims The list of delmimiters
     * @return The trimmed string
     * @description This function removes all occurrences of any of the
     *              chararacters in the delims string from the front of the
     *              specified string.
     */
    native function preTrim( string str, string delims )
    {
        int i;
        char *p;
        FeriteVariable *var;
        for( i = 0 ; i < str->length ; i++ )
        {
      /* FIXME Change this after code defrost:
            if( memchr(delims->data, str->data[i], delims->length) == NULL ) */
            if( strchr(delims->data, str->data[i]) == NULL )
              break;
        }
        p = ( str->length - i == 0) ? "" : str->data + i;
        var = fe_new_str_static( "string::preTrim", p, str->length - i , FE_CHARSET_DEFAULT );
        FE_RETURN_VAR( var );
    }

    /**
     * @function postTrim
     * @declaration function postTrim( string str, string delims )
     * @brief Trims the back of a string using the specified delimiters
     * @param string str The string to trim
     * @param string delims The list of delimiters
     * @return The trimmed string
     * @description This function removes all occurrences of any of the
     *              characters in the delims string from the back of the
     *              specified string.
     */
    native function postTrim( string str, string delims )
    {
        int j = 0;
        char *p = NULL;
        FeriteVariable *var = NULL;

        for( j = str->length ; j > 0 ; j-- )
        {
      /* FIXME Change this after code defrost:
            if( memchr(delims->data, str->data[i], delims->length) == NULL ) */
            if( strchr(delims->data, str->data[j]) == NULL )
              break;
        }
		if( str->length > 1 )
          j++;
        
		p = ( j == 0) ? "" : str->data;
        var = fe_new_str_static( "string::postTrim", str->data, j, FE_CHARSET_DEFAULT );
        FE_RETURN_VAR( var );
    }

    /**
     * @function toLower
     * @declaration function toLower( string str )
     * @brief Translates a string to lower case
     * @param string str The input string
     * @return A copy of the input string translated to lower case
     * @description This function translates all occurrences of upper case
     *              alphabetical characters (A, B, C, etc.) in the input string
     *              to their lower case equivalents (a, b, c, etc.) and returns
     *              the result in a new string.
     * @example <nl/><code>
     <type>string</type> s = String.toLower("FNAR"); &raquo; s = "fnar"</code><nl/>
     */
    native function toLower( string str )
    {
        FeriteVariable *var;
        FeriteString *s;
        int i;

        var = ferite_create_string_variable( script, "string::toLower", str, FE_STATIC );
        s = VAS(var);
        for( i = 0 ; i < s->length ; i++ )
        {
            s->data[i] = tolower( s->data[i] );
        }
        FE_RETURN_VAR( var );
    }

    /**
     * @function toUpper
     * @declaration function toUpper( string str )
     * @brief Translates a string to upper case
     * @param string str The input string
     * @return A copy of the input string translated to upper case
     * @description This function translates all occurrences of lower case
     *              alphabetical characters (a, b, c, etc.) in the input string
     *              to their upper case equivalents (A, B, C, etc.) and returns
     *              the result in a new string.
     * @example <nl/><code>
     <type>string</type> s = String.toUpper("fnar"); &raquo; s = "FNAR"</code><nl/>
     */
    native function toUpper( string str )
    {
        FeriteVariable *var;
        FeriteString *s;
        int i;

        var = ferite_create_string_variable( script, "string::toUpper", str, FE_STATIC );
        s = VAS(var);
        for( i = 0 ; i < s->length ; i++ )
        {
            s->data[i] = toupper( s->data[i] );
        }
        FE_RETURN_VAR( var );
    }

    /**
     * @function compareCase
     * @declaration function compareCase( string a, string b )
     * @brief Compares two strings
     * @param string a The first string
     * @param string b The second string
     * @return True if they match, false if they do not
     * @description This function compares the two strings and returns true if
     *              they match or false if they do not. The case of
     *              alphabetical characters is not ignored.
     */
    native function compareCase( string a, string b )
    {
        int result;
        result = ferite_str_cmp(a, b);
        FE_RETURN_LONG( (long)result );
    }
    native function compare( string a, string b )
    {
        FE_RETURN_LONG( ferite_strcasecmp( a->data, b->data ) );
    }
    /**
     * @function compareNoCase
     * @declaration function compareNoCase( string a, string b )
     * @brief Compares two strings, ignoring the case of alphabetical characters
     * @param string a The first string
     * @param string b The second string
     * @return True if they match, false if they do not
     * @description This function compares the two strings and returns true if
     *              they match or false if they do not. The case of
     *              alphabetical characters is ignored, eg. if you compare
     *              "hello" and "HELLO" with this function, they will match.
     */
    native function compareNoCase( string a, string b )
    {
        int result;

        result = ferite_str_case_cmp(a, b);
        FE_RETURN_LONG( (long)result );
    }

    /**
     * @function nCompareCase
     * @declaration function nCompareCase( string a, string b, number max )
     * @brief Compares the initial part of two strings
     * @param string a The first string
     * @param string b The second string
     * @param number max The maximum number of characters to compare
     * @return True if the first "max" characters match, false if they do not
     * @description This function compares the first "max" characters of the
     *              two strings and returns true if they match or false if they
     *              do not. The case of alphabetical characters is not ignored.
     */
    native function nCompareCase( string a, string b, number max )
    {
        int result;

        result = ferite_str_ncmp(a, b, (size_t)max);
        FE_RETURN_LONG( (long)result );
    }

    /**
     * @function nCompareNoCase
     * @declaration function nCompareCase( string a, string b, number max )
     * @brief Compares the initial part of two strings, ignoring the case of alphabetical characters
     * @param string a The first string
     * @param string b The second string
     * @param number max The maximum number of characters to compare
     * @return True if the first "max" characters match, false if they do not
     * @description This function compares the first "max" characters of the
     *              two strings and returns true if they match or false if they
     *              do not. The case of alphabetical characters is ignored, eg.
     *              if you compare "hello" and "HELLO" with this function, they
     *              will match.
     */
    native function nCompareNoCase( string a, string b, number max )
    {
        int result;

        result= ferite_str_case_ncmp(a, b, (size_t)max);
        FE_RETURN_LONG( (long)result );
    }

    /**
     * @function isalpha
     * @native function isalpha( string s )
     * @brief Tests whether the first character of a string is alphabetic
     * @returns True if it is alphabetic, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [a-z] or [A-Z]. False is returned if it
     *              is not alphabetic or if the string is empty.
     */
    native function isalpha(string s)
    {
        if(s->length > 0 && isalpha(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function islower
     * @native function islower( string s )
     * @brief Tests whether the first character of a string is a lower case letter
     * @returns True if it is a lower case letter, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [a-z]. False is returned if it is not a
     *              lower case letter or if the string is empty.
     */
    native function islower(string s)
    {
        if(s->length > 0 && islower(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isupper
     * @native function isupper( string s )
     * @brief Tests whether the first character of a string is an upper case letter
     * @returns True if it is an upper case letter, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [A-Z]. False is returned if it is not an
     *              upper case letter or if the string is empty.
     */
    native function isupper(string s)
    {
        if(s->length > 0 && isupper(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isdigit
     * @native function isdigit( string s )
     * @brief Tests whether the first character of a string is a digit
     * @returns True if it is a digit, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [0-9]. False is returned if it is not a
     *              digit or if the string is empty.
     */
    native function isdigit(string s)
    {
        if(s->length > 0 && isdigit(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isalnum
     * @native function isalnum( string s )
     * @brief Tests whether the first character of a string is alphanumerical
     * @returns True if it is alphanumerical, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [a-z], [A-Z], or [0-9]. False is returned
     *              if it is not alphanumerical or if the string is empty.
     */
    native function isalnum(string s)
    {
        if(s->length > 0 && isalnum(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isxdigit
     * @native function isxdigit( string s )
     * @brief Tests whether the first character of a string is a hex digit
     * @returns True if it is a hex digit, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of [0-9], [a-f], or [A-F]. False is returned
     *              if it is not a hex digit or if the string is empty.
     */
    native function isxdigit(string s)
    {
        if(s->length > 0 && isxdigit(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function ispunct
     * @native function ispunct( string s )
     * @brief Tests whether the first character of a string is a punctuation character
     * @returns True if it is a punctuation character, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is any non alphanumerical printable character. False
     *              is returned if it is not a punctuation character or if the
     *              string is empty.
     */
    native function ispunct(string s)
    {
        if(s->length > 0 && ispunct(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isspace
     * @native function isspace( string s )
     * @brief Tests whether the first character of a string is a whitespace character
     * @returns True if it is a whitespace character, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is one of ' ', '\f', '\n', '\r', '\t', or '\v'.
     *              False is returned if it is not a whitespace character or if
     *              the string is empty.
     */
    native function isspace(string s)
    {
        if(s->length > 0 && isspace(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isgraph
     * @native function isgraph( string s )
     * @brief Tests whether the first character of a string is a graphic character
     * @returns True if it is a graphic character, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is a graphic character, ie. one which has a glyph
     *              associated with it. Whitespace characters are not considered
     *              to be graphic characters. False is returned if it is not
     *              a graphic character or if the string is empty.
     */
    native function isgraph(string s)
    {
        if(s->length > 0 && isgraph(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function isprint
     * @native function isprint( string s )
     * @brief Tests whether the first character of a string is a printable character
     * @returns True if it is a printable character, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is a printable character. isprint() returns true for
     *              all the characters that isgraph() does, and for the space
     *              character as well. False is returned if it is not a
     *              printable character or if the string is empty.
     */
    native function isprint(string s)
    {
        if(s->length > 0 && isprint(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /**
     * @function iscntrl
     * @native function iscntrl( string s )
     * @brief Tests whether the first character of a string is a control character
     * @returns True if it is a control character, false if it is not or on error
     * @description This function returns true if the first letter of the
     *              string is a control character, ie. any character which is
     *              not printable. False is returned if it is not a control
     *              character or if the string is empty.
     */
    native function iscntrl(string s)
    {
        if(s->length > 0 && iscntrl(s->data[0]))
        {
            FE_RETURN_TRUE;
        }
        FE_RETURN_FALSE;
    }

    /* This is used as a utility function by sprintf to format a variable. You
     * probably shouldn't call this function directly from user scripts. The
     * format should be a printf type format for a single variable (eg. "%d"),
     * which is passed in in the void parameter. Some of the more rarely used
     * or Gnu specific conversions aren't supported. Printing of objects and
     * arrays is not yet supported. On error an empty string is returned (this
     * doesn't trigger an error in sprintf() though because some valid formats
     * such as %s with an empty string will also produce an empty output
     * string). */
    native function __printvar(string fmt, void var)
    {
        long l;
        int ret;
        double d;
        char *buf;
        unsigned char c;
        int buflen = 256;
        FeriteString *fs;
        FeriteVariable *fv;

        if(fmt->length == 0 || !var || !(buf = fmalloc(buflen))) goto err2;

        do
        {
            switch(fmt->data[fmt->length - 1])
            {
              case 'd':
              case 'i':
              case 'o':
              case 'u':
              case 'x':
              case 'X':
                if(var->type == F_VAR_LONG) l = VAI(var);
                else if(var->type == F_VAR_DOUBLE) l = (long)VAF(var);
                else goto err1;
                ret = snprintf(buf, buflen, fmt->data, l);
                break;
              case 'f':
              case 'e':
              case 'E':
              case 'g':
              case 'G':
              case 'a':
              case 'A':
                if(var->type == F_VAR_LONG) d =(double)VAI(var);
                else if(var->type == F_VAR_DOUBLE) d = VAF(var);
                else goto err1;
                ret = snprintf(buf, buflen, fmt->data, d);
                break;
              case 'c':
              case 'C':
                if(var->type == F_VAR_LONG) c = (unsigned char)VAI(var);
                else if(var->type == F_VAR_DOUBLE) c = (unsigned char)VAF(var);
                else if(var->type == F_VAR_STR)
                {
                    fs = VAS(var);
                    if(fs->length == 0) goto err1;
                    else c = fs->data[0];
                }
                else goto err1;
                ret = snprintf(buf, buflen, fmt->data, c);
                break;
              case 's':
              case 'S':
                if(var->type == F_VAR_STR) fs = VAS(var);
                else goto err1;
                ret = snprintf(buf, buflen, fmt->data, fs->data);
                break;
              default:
                goto err1;
            }
      /* Here we have to deal both with versions of snprintf() which
       * return -1 if the buffer isn't long enough and versions which
       * return the length of text that would have been generated if the
       * buffer had been long enough. */
            if(ret > -1 && ret < buflen) break;
            else if(ret > -1) buflen = ret + 1;
            else buflen *= 2;
        }
        while((buf = frealloc(buf, buflen)));

        if(!buf) goto err2;

        fv = ferite_create_string_variable_from_ptr(script, "", buf, ret,
                                                    FE_CHARSET_DEFAULT, FE_STATIC);
        ffree(buf);
        FE_RETURN_VAR(fv);

        err1:   ffree(buf);
        err2:   fv = ferite_create_string_variable_from_ptr(script, "", "", 0,
                                                            FE_CHARSET_DEFAULT, FE_STATIC);
        FE_RETURN_VAR(fv);
    }

    /* Another utility function for sprintf(). You definitely don't want to
     * call this from a user script. */
    native function __isfmt(string s)
    {
        char fmts[] = "diouxXfeEgGaAcCsS";

        if(!s->data[0] || !strchr(fmts, s->data[0]))
        {
            FE_RETURN_FALSE;
        }
        FE_RETURN_TRUE;
    }

    /**
     * @function sprintf
     * @declaration function sprintf( string fmt, ... )
     * @brief Prints formatted text to a string
     * @param string fmt The format string
     * @param void ... The variables
     * @returns The formatted string
     * @description This function is analagous to the libc sprintf() function.
     *              You should read your system libc manual for full details on
     *              the different output formats you can use. Printing of
     *              objects and arrays is not currently supported and various
     *              extensions such as %m are not supported (but may be in the
     *              future).
     * @example <nl/><code>
     <type>string</type> s = String.sprintf("fnar:%s:%d","Yes",32); &raquo; s = "fnar:Yes:32"</code><nl/>
     */
    function sprintf(string fmt, ...)
    {
        string ret = "";
        number fl = 0, fr = 0, ai = 1;
        array args = arguments();
        number alen = Array.size(args);
        number flen = String.length(fmt);

        /* Loop through the format string: */
        while(fr < flen)
        {

            /* Look for the start of a format definition: */
            if(fmt[fr] != '%')
            {
                fr++;
                continue;
            }

            /* Output anything that came before this format definition: */
            if(fl < fr)
            {
                ret += fmt[fl..(fr - 1)];
                fl = fr;
            }

            /* Make sure there's at least one more character in the format: */
            if(++fr == flen) break;

            /* Check for the special case of "%%": */
            if(fmt[fr] == '%')
            {
                /* Make left point to the second % in the sequence: */
                fl = fr;
                fr++;
                continue;
            }

            /* Look for the format character which goes with this %: */
            while(fr < flen)
            {
                if(String.__isfmt(fmt[fr]))
                {
                    /* We found it, so check we have a variable in the argument
                     * array to go with it: */
                    if(ai == alen) break;
                    /* We have a variable, so format the string and add it to
                     * the output: */
                    ret += String.__printvar(fmt[fl..fr], args[ai]);
                    fr++;
                    fl = fr;
                    ai++;
                    break;
                }
                fr++;
            }
        }

        /* Add any remaining characters from the format string: */
        if(fl < flen) ret += fmt[fl..(flen - 1)];

        return ret;
    }

    /**
     * @function escape
     * @declaration function escape( string str )
     * @brief Converts various characters in a string to escape sequences
     * @param string str The string
     * @return A copy of the string after escaping some characters
     * @description This function encodes a binary string using standard C
     *              escape sequences which can be used in many places where
     *              arbitrary binary data is not allowed.
     */
    native function escape( string str )
    {
        FeriteVariable *ret;
        char tmp[5], *buf, *newbuf;
        int i, len, bufsiz = 256, buflen = 0;

        if(str->length == 0 || !(buf = fmalloc(bufsiz)))
        {
            ret = ferite_create_string_variable_from_ptr(script, NULL, "", 0,
                                                         FE_CHARSET_DEFAULT, FE_STATIC);
            FE_RETURN_VAR(ret);
        }

        for(i = 0; i < str->length; i++)
        {

            /* Initialising these two here assuming that it is going to be a
             * letter escape sequence saves two lines of code for each of
             * the letter escape sequences below: */
            len = 2;
            tmp[0] = '\\';

            switch(str->data[i])
            {
                /* Handle all of the letter escapes first: */
              case '\a':
                tmp[1] = 'a';
                break;
              case '\b':
                tmp[1] = 'b';
                break;
              case '\f':
                tmp[1] = 'f';
                break;
              case '\n':
                tmp[1] = 'n';
                break;
              case '\r':
                tmp[1] = 'r';
                break;
              case '\t':
                tmp[1] = 't';
                break;
              case '\v':
                tmp[1] = 'v';
                break;
              case '\\':
                tmp[1] = '\\';
                break;
              case '\?':
                tmp[1] = '\?';
                break;
              case '\'':
                tmp[1] = '\'';
                break;
              case '\"':
                tmp[1] = '\"';
                break;
              default:
                    /* Handle any remaining non-printable characters by
                     * converting them to a hex escape number: */
                if(!isprint(str->data[i]))
                {
                    len = snprintf(tmp, 5, "\\x%.2x",
                                   (unsigned char)str->data[i]);
                }
                else
                {
                        /* Anything else is included directly: */
                    tmp[0] = str->data[i];
                    len = 1;
                }
                break;
            }

            /* Expand the output buffer if necessary: */
            if(buflen + len > bufsiz)
            {
                bufsiz *= 2;
                /* Shouldn't really happen, but handle it if it does: */
                if(buflen + len > bufsiz) bufsiz += len;
                if(!(newbuf = frealloc(buf, bufsiz)))
                {
                    ffree(buf);
                    ret = ferite_create_string_variable_from_ptr(script, NULL,
                                                                 "", 0, FE_CHARSET_DEFAULT, FE_STATIC);
                    FE_RETURN_VAR(ret);
                }
                else buf = newbuf;
            }

            memcpy(&buf[buflen], tmp, len);
            buflen += len;
        }

        ret = ferite_create_string_variable_from_ptr(script, "String:escape",
                                                     buf, buflen, FE_CHARSET_DEFAULT, FE_STATIC);
        ffree(buf);
        FE_RETURN_VAR(ret);
    }

    /**
     * @function unescape
     * @declaration function unescape( string str )
     * @brief Unescapes strings which contain standard C escape sequences
     * @param string str The string
     * @return A copy of the string after unescaping it
     */
    native function unescape( string str )
    {
        long l;
        int i, buflen = 0;
        FeriteVariable *ret;
        unsigned char *buf, c, tmp[4];

        /* We know the output can't be longer than the input when unescaping: */
        if(str->length == 0 || !(buf = fmalloc(str->length)))
        {
            ret = ferite_create_string_variable_from_ptr(script, NULL, "", 0,
                                                         FE_CHARSET_DEFAULT, FE_STATIC);
            FE_RETURN_VAR(ret);
        }

        for(i = 0; i < str->length; i++)
        {
            /* Check if this is the start of an escape sequence (being careful
             * to handle the special case of the string ending with a single
             * backslash): */
            if(str->data[i] == '\\' && i < str->length - 1)
            {
                switch(str->data[++i])
                {
                    /* Handle all the single letter escapes: */
                  case 'a':
                    c = '\a';
                    break;
                  case 'b':
                    c = '\b';
                    break;
                  case 'f':
                    c = '\f';
                    break;
                  case 'n':
                    c = '\n';
                    break;
                  case 'r':
                    c = '\r';
                    break;
                  case 't':
                    c = '\t';
                    break;
                  case 'v':
                    c = '\v';
                    break;
                  case '\\':
                    c = '\\';
                    break;
                  case '?':
                    c = '?';
                    break;
                  case '\'':
                    c = '\'';
                    break;
                  case '"':
                    c = '"';
                    break;
                    /* Handle hex numbers: */
                  case 'x':
                        /* We can safely do this because we know that
                         * str->data[str->length] is always '\0': */
                    if(isxdigit(str->data[i + 1]))
                    {
                        tmp[0] = str->data[++i];
                        if(isxdigit(str->data[i + 1]))
                        {
                            tmp[1] = str->data[++i];
                            tmp[2] = '\0';
                        }
                        else tmp[1] = '\0'; /* Only one hex digit */
                        l = strtol(tmp, NULL, 16);
                        if(l > 255 || l < 0) c = 0; /* Shouldn't happen */
                        else c = (unsigned char)l;
                    }
                    else c = 'x'; /* No hex digits after the x */
                    break;
                    /* Handle octal numbers: */
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                    tmp[0] = str->data[i];
                    tmp[1] = str->data[i + 1];
                    if(tmp[1] >= '0' && tmp[1] <= '7')
                    {
                        i++;
                        tmp[2] = str->data[i + 1];
                        if(tmp[2] >= '0' && tmp[2] <= '7')
                        {
                            i++;
                            tmp[3] = '\0'; /* Three octal digits */
                        }
                        else tmp[2] = '\0'; /* Two octal digits */
                    }
                    else tmp[1] = '\0'; /* One octal digit */
                    l = strtol(tmp, NULL, 8);
                    if(l > 255 || l < 0) c = 0;
                    else c = (unsigned char)l;
                    break;
                    /* Anything else just gets copied straight through: */
                  default:
                    c = str->data[i];
                    break;
                }
            }
            else c = str->data[i];
            buf[buflen++] = c;
        }

        ret = ferite_create_string_variable_from_ptr(script, "String:unescape",
                                                     buf, buflen, FE_CHARSET_DEFAULT, FE_STATIC);
        ffree(buf);
        FE_RETURN_VAR(ret);
    }

    /**
     * @function pad
     * @declaration function pad( string str, number pad, string padstr )
     * @brief Pads a string
     * @param string str The string
     * @param number pad The total length the string should be
     * @param string padchar The character to pad the extra space with
     * @return A copy of the string, padded to the specified length
     * @description If the specified string is less than "pad" characters long,
     *              copies of the first character of padchar are added to the
     *              end of the string to make it up to the specified length.
     *              The string is not truncated if it is already longer than
     *              the specified length. If the padchar parameter is omitted
     *              or is an empty string, spaces are used. If a number between
     *              0 and 255 is specified instead of the padchar, the string
     *              is padded with the ASCII character which that number
     *              represents.
     * @example <nl/><code>
     <type>string</type> s = String.pad("fnar",10,"."); &raquo; s = "fnar......"</code><nl/>
     */
    native function pad( string str, number pad, string padchar )
    {
        unsigned char pch;
        FeriteVariable *ret;
        long lpad = (long)pad;
        
        if( lpad < 0 )
            lpad = 0;
        
        if(lpad <= str->length)
        {
            ret = ferite_create_string_variable(script, "String::pad", str,
                                                FE_STATIC);
            FE_RETURN_VAR(ret);
        }

        if(!(ret = ferite_create_string_variable_from_ptr(script, "String::pad",
                                                          NULL, lpad, FE_CHARSET_DEFAULT, FE_STATIC)))
        {
            FE_RETURN_VOID;
        }

        memcpy(VAS(ret)->data, str->data, str->length);
        if(padchar->length < 1) pch = ' ';
        else pch = padchar->data[0];
        memset(&VAS(ret)->data[str->length], pch, lpad - str->length);

        FE_RETURN_VAR(ret);
    }
  /**
  * @function pad
   * @declaration function pad( string str, number pad )
   * @brief Pads a string
   * @param string str The string
   * @param number pad The total length the string should be
   * @return A copy of the string, padded to the specified length
   * @description If the specified string is less than "pad" characters long,
   *              copies of the first character of padchar are added to the
   *              end of the string to make it up to the specified length.
   *              The string is not truncated if it is already longer than
   *              the specified length. The character to pad will be a space.
   * @example <nl/><code>
   <type>string</type> s = String.pad("fnar",10); &raquo; s = "fnar      "</code><nl/>
   */
  function pad(string str, number pad)
    {
        return(String.pad(str, pad, ''));
    }
      /**
      * @function pad
       * @declaration function pad( string str, number pad, number ch )
       * @brief Pads a string
       * @param string str The string
       * @param number pad The total length the string should be
       * @param number ch The ASCII value to use.
       * @return A copy of the string, padded to the specified length
       * @description If the specified string is less than "pad" characters long,
       *              copies of the first character of padchar are added to the
       *              end of the string to make it up to the specified length.
       *              The string is not truncated if it is already longer than
       *              the specified length. The character to pad will be the value ch.
       * @example <nl/><code>
       <type>string</type> s = String.pad("fnar",10,32); &raquo; s = "fnar      "</code><nl/>
       */
                  
    function pad(string str, number pad, number ch)
    {
        string s;

        if(ch < 0 || ch > 255) s = ' ';
        else s = String.numToChar(ch);

        return(String.pad(str, pad, s));
    }

    /**
     * @function blocks
     * @declaration function blocks( string str, number size )
     * @brief Splits a string into an array of strings of no more than the specified size
     * @param string str The string
     * @param number size The maximum size of the output strings
     * @return The array of output strings
     * @description This function converts the specified string into an array
     *              of strings. Each string in the array will contain "size"
     *              characters of the input string, except for the last string
     *              which may be smaller if it runs out of characters in the
     *              input string. For example, String.blocks("hello world", 3)
     *              will produce the output [ "hel", "lo ", "wor", "ld" ].
     */
    native function blocks( string str, number size )
    {
        FeriteVariable *fv, *ret;
        long i, len, lsize = (long)size, asize;

        if(lsize < 1)
        {
            ret = ferite_create_uarray_variable(script, NULL, 0, FE_STATIC);
            FE_RETURN_VAR(ret);
        }

        asize = str->length / lsize;
        if(str->length % lsize) asize++;

        if(!(ret = ferite_create_uarray_variable(script, "String:blocks",
                                                 asize, FE_STATIC)))
        {
            FE_RETURN_VOID;
        }

        for(i = 0; i < str->length; i += len)
        {
            len = str->length - i;
            if(len > lsize) len = lsize;
            fv = ferite_create_string_variable_from_ptr(script,
                                                        "String::blocks", &str->data[i], len,
                                                        FE_CHARSET_DEFAULT, FE_STATIC);
            if(fv)
            {
                ferite_uarray_add(script, VAUA(ret), fv, NULL,
                                  FE_ARRAY_ADD_AT_END);
            }
        }

        FE_RETURN_VAR(ret);
    }

      /**
      * @function lines
       * @declaration function lines( string str )
       * @brief Itterate through the lines within a string or create an array containing the lines.
       * @param string str The string
       * @return The array containing the lines.
       * @description If you pass this function a closure, it will call it with each line.
       * @example <nl/><code>
       <type>array</type> lines = String.lines( "line1\nline2\nline3\n" );<nl/>
       String.lines( "line1\nline2\nline3\n" ) <keyword>using</keyword> ( line ) {<nl/>
       <tab/>Console.println( line );<nl/>
       }</code><nl/>
       */
    function lines( string str ) 
    {
      array a = String.toArray( str, "\n" );
      if( recipient() != null ) {
          Array.each( a ) using recipient();
      }
      else
          return a;
    }

    /**
     * @function base64encode
     * @brief Convert a string to base64 encoding. 
     * @description The base64 encoding makes it possible to transport binary information
     *              within an ascii system such as email.
     * @param string source The string to encode
     * @return The encoded version of source.
     */
    native function base64encode( string source )
    {
        static unsigned char dtable[256];
        int i, loc;
        FeriteBuffer *output = ferite_buffer_new( source->length * 2 );
        FeriteString *output_flat = NULL;
            
        for( i = 0; i < 9; i++ )
        {
            dtable[i] = 'A'+i;
            dtable[i+9] = 'J'+i;
            dtable[26+i] = 'a'+i;
            dtable[26+i+9] = 'j'+i;
        }
        for( i = 0 ; i < 8; i++ )
        {
            dtable[i+18] = 'S'+i;
            dtable[26+i+18] = 's'+i;
        }
        for( i = 0; i < 10; i++ )
        {
            dtable[52+i] = '0'+i;
        }
        dtable[62] = '+';
        dtable[63] = '/';
        
        for( loc = 0; loc < source->length; )
        {
            unsigned char igroup[3], ogroup[4];
            int c, n;
            
            igroup[0] = igroup[1] = igroup[2] = 0;
            for( n = 0; n < 3 && loc < source->length; n++, loc++ )
            {
                c = source->data[loc];
                igroup[n]= (unsigned char)c;
            }
            if( n > 0)
            {
                ogroup[0]= dtable[igroup[0]>>2];
                ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
                ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
                ogroup[3]= dtable[igroup[2]&0x3F];

                if( n < 3 )
                {
                    ogroup[3] = '=';
                    if( n < 2 )
                    {
                        ogroup[2] = '=';
                    }
                }
                for( i = 0; i < 4; i++ )
                    ferite_buffer_add_char( output, ogroup[i] );
            }
        }
        
        output_flat = ferite_buffer_to_str( output );
        ferite_buffer_delete( output );
        FE_RETURN_STR( output_flat, FE_TRUE );
    }
    /**
     * @function base64decode
     * @brief Convert a string from base64 encoding to normal. 
     * @description The base64 encoding makes it possible to transport binary information
     *              within an ascii system such as email.
     * @param string source The string to decode
     * @return The decoded version of source.
     */
    native function base64decode( string source )
    {
        static unsigned char dtable[256];
        int i, j, loc;
        FeriteBuffer *output = ferite_buffer_new( source->length * 2 );
        FeriteString *output_flat = NULL;        
        
        for(i= 0;i<255;i++){
            dtable[i]= 0x80;
        }
        for(i= 'A';i<='I';i++){
            dtable[i]= 0+(i-'A');
        }
        for(i= 'J';i<='R';i++){
            dtable[i]= 9+(i-'J');
        }
        for(i= 'S';i<='Z';i++){
            dtable[i]= 18+(i-'S');
        }
        for(i= 'a';i<='i';i++){
            dtable[i]= 26+(i-'a');
        }
        for(i= 'j';i<='r';i++){
            dtable[i]= 35+(i-'j');
        }
        for(i= 's';i<='z';i++){
            dtable[i]= 44+(i-'s');
        }
        for(i= '0';i<='9';i++){
            dtable[i]= 52+(i-'0');
        }
        dtable['+']= 62;
        dtable['/']= 63;
        dtable['=']= 0;
        
        for( loc = 0; loc < source->length; )
        {
            unsigned char a[4],b[4],o[3];
            
            for( i = 0; i < 4 && loc < source->length; i++, loc++ )
            {
                int c = source->data[loc];
                a[i]= (unsigned char)c;
                b[i]= (unsigned char)dtable[c];
            }
            o[0]= (b[0]<<2)|(b[1]>>4);
            o[1]= (b[1]<<4)|(b[2]>>2);
            o[2]= (b[2]<<6)|b[3];
            i= a[2]=='='?1:(a[3]=='='?2:3);
            
            for( j = 0; j < i; j++ )
                ferite_buffer_add_char( output, o[j] );

            if(i<3){
                break;
            }
        }
        
        output_flat = ferite_buffer_to_str( output );
        ferite_buffer_delete( output );
        FE_RETURN_STR( output_flat, FE_TRUE );        
    }
}
/**
 * @end
 */
