public void ParseNumbers()
{
    // ConsumeInteger()
    {
        Substring subs= new Substring();
        int result;
                                   UT_EQ( false,  subs.ConsumeInteger( out result                ) );   UT_EQ(       0,  result );
        subs.Set( ""            ); UT_EQ( false,  subs.ConsumeInteger( out result                ) );   UT_EQ(       0,  result );
        subs.Set( "  ABC"       ); UT_EQ( false,  subs.ConsumeInteger( out result                ) );   UT_EQ(       0,  result );
        subs.Set( "  12345"     ); UT_EQ( true ,  subs.ConsumeInteger( out result                ) );   UT_EQ(   12345,  result );
        subs.Set( "  12 45"     ); UT_EQ( true ,  subs.ConsumeInteger( out result                ) );   UT_EQ(      12,  result );
                                   UT_EQ( true ,  subs.ConsumeInteger( out result                ) );   UT_EQ(      45,  result );

        subs.Set( " 42 ; 7 ; 6 "); UT_EQ( true ,  subs.ConsumeInteger( out result                ) );   UT_EQ(      42,  result );
                                   UT_EQ( false,  subs.ConsumeInteger( out result                ) );   UT_EQ(       0,  result );
                                   UT_EQ( false,  subs.ConsumeInteger( out result                ) );   UT_EQ(       0,  result );

        char[] ws= " ;".ToCharArray();
        subs.Set( " 42 ; 7 ; 6 "); UT_EQ( true ,  subs.ConsumeInteger( out result, ws            ) );   UT_EQ(      42,  result );
                                   UT_EQ( true ,  subs.ConsumeInteger( out result, ws            ) );   UT_EQ(       7,  result );
                                   UT_EQ( true ,  subs.ConsumeInteger( out result, ws            ) );   UT_EQ(       6,  result );
                                   UT_EQ( false,  subs.ConsumeInteger( out result, ws            ) );   UT_EQ(       0,  result );
                                   UT_EQ( false,  subs.ConsumeInteger( out result, ws            ) );   UT_EQ(       0,  result );
    }

    // ConsumeFloat()
    {
        Substring subs= new Substring();
        double result;
                                        UT_EQ( false,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(      0.0,  result );
        subs.Set( ""        )         ; UT_EQ( false,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(      0.0,  result );
        subs.Set( "  ABC"   )         ; UT_EQ( false,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(      0.0,  result );
        subs.Set( "  12345" )         ; UT_EQ( true ,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(  12345.0,  result );
        subs.Set( " 12.45 " )         ; UT_EQ( true ,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(     12.45, result );
        subs.Set( "  12 45" )         ; UT_EQ( true ,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(     12.0,  result );
                                        UT_EQ( true ,  subs.ConsumeFloat  ( out result             ) );   UT_EQ(     45.0,  result );

        char[] ws= " ;".ToCharArray();
        subs.Set( " 42.3 ; 0.7 ; 6 " ); UT_EQ( true ,  subs.ConsumeFloat  ( out result, null, ws   ) );   UT_EQ(     42.3,  result );
                                        UT_EQ( true ,  subs.ConsumeFloat  ( out result, null, ws   ) );   UT_EQ(      0.7,  result );
                                        UT_EQ( true ,  subs.ConsumeFloat  ( out result, null, ws   ) );   UT_EQ(      6.0,  result );
                                        UT_EQ( false,  subs.ConsumeFloat  ( out result, null, ws   ) );   UT_EQ(      0.0,  result );
                                        UT_EQ( false,  subs.ConsumeFloat  ( out result, null, ws   ) );   UT_EQ(      0.0,  result );
    }

}
    /** ********************************************************************************************
     *  Processes the next command found in the format string, by writing formatted information
     *  into the given buffer.
     *  The given Substring holds the next command. When method returns, the command is cut
     *  from the front.
     *
     * @param logger    The logger that we are embedded in.
     * @param domain    The <em>Log Domain</em>.
     * @param verbosity The verbosity. This has been checked to be active already on this
     *                  stage and is provided to be able to be logged out only.
     * @param scope     Information about the scope of the <em>Log Statement</em>..
     * @param dest      The buffer to write meta information into.
     * @param variable  The variable to read (may have more characters appended)
     *
     * @return The number of tab sequences that were written (by adding ESC::TAB to the buffer).
     **********************************************************************************************/
    protected virtual int processVariable( TextLogger logger, Domain  domain, Verbosity verbosity,
                                           ScopeInfo  scope,  AString dest,   Substring variable )
    {
        // process commands
        char c2;
        switch ( variable.Consume() )
        {
            // scope info
            case 'S':
            {
                    // read sub command
                AString val;
                switch( c2= variable.Consume() )
                {
                    case 'P':   // SP: full path
                    {
                        int    length;
                        String path= scope.GetFullPath( out length );
                        if ( length > 0 )
                        {
                            dest._( path, 0, length );
                            return 0;
                        }
                        val= NoSourceFileInfo;

                    } break;

                    case 'p':   // Sp: trimmed path
                    {
                        val= scope.GetTrimmedPath();
                        if ( val.IsEmpty() )
                            val= NoSourceFileInfo;
                    } break;

                    case 'F':   // file name
                    {
                        val= scope.GetFileName();
                        if ( val.IsEmpty() )
                            val= NoSourceFileInfo;
                    } break;

                    case 'f':   // file name without extension
                    {
                        val= scope.GetFileNameWithoutExtension();
                        if ( val.IsEmpty() )
                            val= NoSourceFileInfo;
                    } break;


                    case 'M':   // method name
                    {
                        String method= scope.GetMethod();
                        if ( method.Length == 0 )
                            dest._( NoMethodInfo );
                        else
                            dest._( method );
                        return 0;
                    }

                    case 'L':  // line number
                    {
                        dest._( scope.GetLineNumber() );
                        return 0;
                    }

                    default:
                    {
                        if( !warnedOnce )
                        {
                            warnedOnce= true;
                            ALIB.WARNING( "Unknown format variable '%S" + c2 + "\' (only one warning)" );
                        }
                        dest._( "%ERROR" );
                        return 0;
                    }
                }
                dest._( val );
                return 0;
            }

            // %Tx: Time
            case 'T':
            {
                // read sub command
                c2= variable.Consume();

                // %TD: Date
                if ( c2 == 'D' )
                {
                    // get time stamp as DateTime once
                    if ( callerDateTime == null )
                        callerDateTime= scope.GetTimeStamp().InDotNetDateTime();

                    // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!)
                    // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow.
                    if ( DateFormat.Equals( "yyyy-MM-dd" ) )
                    {
                        dest ._( callerDateTime.Value.Year,  4 )._( '-' )
                             ._( callerDateTime.Value.Month, 2 )._( '-' )
                             ._( callerDateTime.Value.Day,   2 );
                    }

                    // support user defined standards
                    else
                    {
                        // detect changes of format string since last log
                        if ( detectDateFormatChanges != DateFormat )
                        {
                            detectDateFormatChanges= DateFormat;
                            dateFormatString= "{0:" + DateFormat + "}";
                        }

                        // get date string from system and append to log buffer
                        formatSB.Clear();
                        formatSB.AppendFormat( CultureInfo.InvariantCulture, dateFormatString, callerDateTime );
                        dest._( formatSB );
                    }
                }

                // %TT: Time of Day
                else if ( c2 == 'T' )
                {
                    // get time stamp as DateTime once
                    if ( callerDateTime == null )
                        callerDateTime= scope.GetTimeStamp().InDotNetDateTime();

                    // avoid the allocation of a) a StringBuilder (yes, a string builder is allocated inside StringBuilder.AppendFormat!)
                    // and b) a DateTime object, if the format is the unchanged standard. And it is faster anyhow.
                    if ( TimeOfDayFormat.Equals( "HH:mm:ss" ) )
                    {
                        dest ._( callerDateTime.Value.Hour,   2 )._( ':' )
                             ._( callerDateTime.Value.Minute, 2 )._( ':' )
                             ._( callerDateTime.Value.Second, 2 );
                    }

                    // support user defined standards
                    else
                    {
                        // detect changes of format string since last log
                        if ( detectTimeOfDayFormatChanges != TimeOfDayFormat )
                        {
                            detectTimeOfDayFormatChanges= TimeOfDayFormat;
                            timeOfDayFormatString= "{0:" + TimeOfDayFormat + "}";
                        }

                        // get time string from system and append to log buffer
                        formatSB.Clear();
                        formatSB.AppendFormat( CultureInfo.InvariantCulture, timeOfDayFormatString, callerDateTime);
                        dest     ._( formatSB );
                    }
                }

                // %TC: Time elapsed since created
                else if ( c2 == 'C' )
                {
                    elapsedTime.Set( scope.GetTimeStamp()  );
                    elapsedTime.Sub( logger.TimeOfCreation );

                    if( MaxElapsedTime.Raw() < elapsedTime.Raw() )
                        MaxElapsedTime.Set( elapsedTime );

                    long      maxElapsedSecs= MaxElapsedTime.InSeconds();
                    TimeSpan  elapsed= new TimeSpan( elapsedTime.Raw() );

                    if ( maxElapsedSecs >= 60*60*24 )  dest._( elapsed.Days  )._NC( TimeElapsedDays );
                    if ( maxElapsedSecs >= 60*60    )  dest._( elapsed.Hours  ,  maxElapsedSecs >= 60*60*10 ?  2 : 1 )._( ':' );
                    if ( maxElapsedSecs >= 60       )  dest._( elapsed.Minutes,  maxElapsedSecs >= 10*60    ?  2 : 1 )._( ':' );
                    dest._( elapsed.Seconds,  maxElapsedSecs > 9 ? 2 : 1          )._NC( '.' );
                    dest._( elapsed.Milliseconds,  3 );
                }

                // %TL: Time elapsed since last log call
                else if ( c2 == 'L' )
                    writeTimeDiff( dest, scope.GetTimeStamp().Since(logger.TimeOfLastLog).InNanos() );

                else
                {
                    if( !warnedOnce )
                    {
                        warnedOnce= true;
                        ALIB.WARNING( "Unknown format variable '%T" + c2 + "\' (only one warning)" );
                    }
                    dest._( "%ERROR" );
                }
                return 0;
            }


            // thread name / ID
            case 't':
            {
                c2= variable.Consume();

                if ( c2 == 'N' )
                {
                    dest.Field()
                        ._( scope.GetThreadName() )
                        .Field( logger.AutoSizes.Next( scope.GetThreadName().Length(), 0 ), Alignment.Center );
                }
                else if ( c2 == 'I' )
                {
                    tmpAString._()._( scope.GetThreadID() );
                    dest.Field()
                        ._( tmpAString )
                        .Field( logger.AutoSizes.Next( tmpAString           .Length(), 0 ), Alignment.Center );
                }
                else
                {
                    if( !warnedOnce )
                    {
                        warnedOnce= true;
                        ALIB.WARNING( "Unknown format variable '%t" + c2 + "\' (only one warning)" );
                    }
                    dest._( "%ERROR" );
                }
                return 0;
            }

            case 'L':
            {
                c2= variable.Consume();
                     if ( c2 == 'G' )     dest._NC( logger.GetName() );
                else if ( c2 == 'X' )     dest._NC( scope.GetLoxName() );
                else
                {
                    if( !warnedOnce )
                    {
                        warnedOnce= true;
                        ALIB.WARNING( "Unknown format variable '%L" + c2 + "\' (only one warning)" );
                    }
                    dest._( "%ERROR" );
                    return 0;
                }
                return 0;
            }

            case 'P':
            {
                dest._NC( Util.GetProcessName() );
                return 0;
            }

            case 'V':   dest._ (    verbosity == Verbosity.Error    ? VerbosityError
                                  : verbosity == Verbosity.Warning  ? VerbosityWarning
                                  : verbosity == Verbosity.Info     ? VerbosityInfo
                                  :                               VerbosityVerbose    );
                        return 0;

            case 'D':
            {
                dest.Field()._( domain.FullPath ).Field( logger.AutoSizes.Next( domain.FullPath.Length(), 0 ), Alignment.Left );
                return 0;
            }

            case '#':    dest._( logger.CntLogs, LogNumberMinDigits );
                         return 0;

            // A: Auto tab
            case 'A':
            {
                // read extra space from format string
                int oldStart=    variable.Start;
                int extraSpace;  variable.ConsumeInteger( out extraSpace );
                if ( oldStart == variable.Start )
                    extraSpace=         1;

                // insert ESC code to jump to next tab level
                extraSpace= Math.Min( extraSpace, 10 + ('Z'-'A') );
                char escNo= extraSpace < 10 ?   (char) ( '0' + extraSpace )
                                            :   (char) ( 'A' + extraSpace );

                dest._( "\x1Bt" )._( escNo );
                return 1;
            }

            default:
            {
                if( !warnedOnce )
                {
                    warnedOnce= true;
                    ALIB.WARNING( "Unknown format variable \'" + variable.Buf[variable.Start - 1] + "\'" );
                }
                dest._( "%ERROR" );
            }
            return 0;
        }

    }
 /** ****************************************************************************************
  *  Imports values from the given substring by parsing it. The numbers in the string have
  *  to be separated by ' ' characters (space).
  *
  * @param source    The Substring that is parsed for the numbers
  * @param session   If \c CurrentData::Clear, which is the default, the current values
  *                  are taken from the last session stored and the sessions data is set to 0.
  *                  If \c CurrentData::Keep, both, current values and
  *                  session values are taken from the string.
  ******************************************************************************************/
 public void     Import( Substring source, CurrentData session = CurrentData.Clear )
 {
     Reset();
     length= 0;
     for(;;)
     {
         int actStart=    source.Start;
         int value;       source.ConsumeInteger( out value );
         int lastSession; source.ConsumeInteger( out lastSession );
         if ( actStart == source.Start )
             break;
         ensureArraySize( length + 1 );
         values       [length]=   session == CurrentData.Clear ? lastSession : value;
         sessionValues[length]=   session == CurrentData.Clear ? 0           : lastSession;
         length++;
     }
 }