PStack.cpp

Go to the documentation of this file.
00001 #include "MercuryUtil.h"
00002 #include "PStack.h"
00003 #include "MercuryLog.h"
00004 
00005 static PSElement NILElement;
00006 
00007 MString PSElement::GetValueS( ) const
00008 {
00009     if ( type == STRING )
00010         return *data.dataS;
00011     else if ( type == INTEGER )
00012         return ssprintf( "%li",data.l );
00013     else if ( type == FLOAT )
00014         return ssprintf( "%f",data.f );
00015     else if ( type == BOOL )
00016         return ssprintf( "%d",data.b );
00017     else if ( type == NIL )
00018         return "NIL";
00019     else
00020         return "";
00021 }
00022 
00023 bool PSElement::GetValueS( MString & ret )
00024 {
00025     if ( type == STRING )
00026         ret = *data.dataS;
00027     else if ( type == INTEGER )
00028         ret = ssprintf( "%li",data.l );
00029     else if ( type == FLOAT )
00030         ret = ssprintf( "%f",data.f );
00031     else if ( type == BOOL )
00032         ret = ssprintf( "%d",data.b );
00033     else if ( type == NIL )
00034         ret = "NIL";
00035     else
00036         return false;
00037     return true;
00038 }
00039 
00040 int PSElement::GetValueI( ) const
00041 {
00042     if ( type == INTEGER || type == PARAMETER )
00043         return data.l;
00044     else if ( type == FLOAT )
00045         return int(data.f);
00046     else if ( type == BOOL )
00047         return data.b;
00048     else if ( type == STRING )
00049         return atoi( data.dataS->c_str() );
00050     else
00051         return 0;
00052 }
00053 
00054 bool PSElement::GetValueI( long &ret )
00055 {
00056     if ( type == INTEGER || type == PARAMETER )
00057     {
00058         ret = data.l;
00059         return true;
00060     }
00061     else if ( type == FLOAT )
00062     {
00063         ret = type;
00064         return true;
00065     }
00066     else if ( type == BOOL )
00067     {
00068         ret = data.b;
00069         return true;
00070     }
00071         return false;
00072 }
00073 
00074 float PSElement::GetValueF( ) const
00075 {
00076     if ( type == INTEGER )
00077         return float(data.l);
00078     else if ( type == FLOAT )
00079         return data.f;
00080     else if ( type == STRING )
00081         return float(atof(data.dataS->c_str()));
00082     else
00083         return 0;
00084 }
00085 
00086 bool PSElement::GetValueF( float & ret )
00087 {
00088     if ( type == INTEGER )
00089     {
00090         ret = float(data.l);
00091         return true;
00092     }
00093     else if ( type == FLOAT )
00094     {
00095         ret = data.f;
00096         return true;
00097     }
00098     else if ( type == STRING )
00099     {
00100         ret = float(atof(data.dataS->c_str()));
00101         return true;
00102     }
00103     else
00104         return false;
00105 }
00106 
00107 bool PSElement::GetValueB( ) const
00108 {
00109     if ( type == BOOL )
00110         return data.b;
00111     if ( type == INTEGER )
00112         return data.l!=0;
00113     else if ( type == FLOAT )
00114         return data.f!=0;
00115     else
00116         return 0;
00117 }
00118 
00119 bool PSElement::GetValueB( bool & ret )
00120 {
00121     if ( type == BOOL )
00122     {
00123         ret = data.b;
00124         return true;
00125     } 
00126     else if ( type == INTEGER )
00127     {
00128         ret = data.l!=0;
00129         return true;
00130     }
00131     else if ( type == FLOAT )
00132     {
00133         ret = data.f!=0;
00134         return true;
00135     }
00136     else
00137         return false;
00138 }
00139 
00140 void * PSElement::GetValueV( ) const
00141 {
00142     if ( type == USERDATA )
00143         return data.v;
00144     else
00145         return NULL;
00146 }
00147 
00148 bool PSElement::GetValueV( void * & ret )
00149 {
00150     if ( type == USERDATA )
00151     {
00152         ret = data.v;
00153         return true;
00154     } else
00155         return false;
00156 }
00157 
00158 PSElement::ParmType PSElement::GetType() const
00159 {
00160     return type;
00161 }
00162 
00163 PStack::PStack( const char * sIn, int iMaxLength )
00164 {
00165     if ( iMaxLength <= 0 )
00166         return;
00167 
00168     int iCtr = 0;
00169     int iParenDepth = 0;
00170     char cCurrent = sIn[iCtr];
00171     
00172     MString sCurrent;
00173     PSElement::ParmType pt = PSElement::FLOAT;
00174     bool bInString = false;
00175 
00176     while ( ( cCurrent != '\0' ) && ( iCtr < iMaxLength ) )
00177     {
00178         if ( cCurrent == '\"' )
00179         {
00180             pt = PSElement::STRING;
00181             bInString = !bInString;
00182             iCtr++;
00183             cCurrent = sIn[iCtr];
00184         }
00185 
00186         if ( bInString )
00187             sCurrent += cCurrent;
00188         else
00189             switch ( cCurrent )
00190             {
00191             case ',':
00192                 if ( iParenDepth > 0 )
00193                 {
00194                     sCurrent += cCurrent;
00195                     break;
00196                 }
00197                 if ( pt == PSElement::STRING )
00198                     PushItemBack( PSElement( sCurrent ) );
00199                 else if ( pt == PSElement::FLOAT )
00200                     PushItemBack( PSElement( float( atof( sCurrent.c_str() ) ) ) );
00201                 else
00202                 {
00203                     PSElement PM = PSElement( atoi( sCurrent.c_str() ) );
00204                     PM.ForceType( pt );
00205                     PushItemBack( PM );
00206                 }
00207                 pt = PSElement::FLOAT;
00208                 sCurrent = "";
00209                 break;
00210             case '(':
00211                 iParenDepth++;
00212                 break;
00213             case ')':
00214                 iParenDepth--;
00215                 break;
00216             case '%':
00217                 if ( iParenDepth > 0 )
00218                 {
00219                     sCurrent += cCurrent;
00220                     break;
00221                 }
00222                 if ( sCurrent.empty() )
00223                     pt = PSElement::PARAMETER;
00224                 break;
00225             default:
00226                 sCurrent += cCurrent;
00227             }
00228         iCtr++;
00229         cCurrent = sIn[iCtr];
00230     }
00231     if ( pt == PSElement::STRING )
00232         PushItemBack( PSElement( sCurrent ) );
00233     else if ( pt == PSElement::FLOAT )
00234         PushItemBack( PSElement( float( atof( sCurrent.c_str() ) ) ) );
00235     else
00236     {
00237         PSElement PM = PSElement( atoi( sCurrent.c_str() ) );
00238         PM.ForceType( pt );
00239         PushItemBack( PM );
00240     }
00241 }
00242 
00243 PStack::PStack( PSElement p1 )
00244 {
00245     PushItem( p1 );
00246 }
00247 
00248 PStack::PStack( PSElement p1, PSElement p2 )
00249 {
00250     PushItem( p2 );
00251     PushItem( p1 );
00252 }
00253 
00254 PStack::PStack( PSElement p1, PSElement p2, PSElement p3 )
00255 {
00256     PushItem( p3 );
00257     PushItem( p2 );
00258     PushItem( p1 );
00259 }
00260 
00261 PStack::PStack( PSElement p1, PSElement p2, PSElement p3, PSElement p4 )
00262 {
00263     PushItem( p4 );
00264     PushItem( p3 );
00265     PushItem( p2 );
00266     PushItem( p1 );
00267 }
00268 
00269 PStack::~PStack()
00270 {
00271 }
00272 
00273 void PStack::PushItem(const PSElement & in )
00274 {
00275     m_stack.insert( 0, in );//push_front( in );
00276 }
00277 
00278 void PStack::PushItemBack(const PSElement & in )
00279 {
00280     m_stack.push_back( in );
00281 }
00282 
00283 bool PStack::PopItem( PSElement & out )
00284 {
00285     if ( m_stack.empty() )
00286         return false;
00287 
00288     out = m_stack[0];
00289     //m_stack.pop_front();
00290     m_stack.remove( 0 );
00291     return true;
00292 }
00293 
00294 bool PStack::PeekItem( PSElement & out )
00295 {
00296     if ( m_stack.empty() )
00297         return false;
00298     out = m_stack[0];
00299     return true;
00300 }
00301 
00302 PSElement PStack::PopItem()
00303 {
00304     if ( m_stack.empty() )
00305         return NILElement;
00306     PSElement ret = m_stack[0];
00307     m_stack.remove( 0 );
00308     return ret;
00309 }
00310 
00311 const PSElement& PStack::PeekItem( unsigned int iAhead ) const
00312 {
00313     if (iAhead < m_stack.size())
00314         return m_stack[iAhead];
00315     return NILElement;
00316 }
00317 
00318 
00319 MVector< PStack * > g_vMPStacks;
00320 
00321 int HGEXPORT CreateMPStack()
00322 {
00323     unsigned i;
00324     for( i = 0; i < g_vMPStacks.size(); i++ )
00325         if ( !g_vMPStacks[i] )
00326             break;
00327 
00328     if ( i == g_vMPStacks.size() )
00329         g_vMPStacks.push_back( new PStack );
00330     else
00331         g_vMPStacks[i] = new PStack;
00332 
00333     g_vMPStacks[i]->Clear();
00334 
00335     return i;
00336 }
00337 
00338 int HGEXPORT GetMPStackSize(  unsigned int iMPS )
00339 {
00340     if ( iMPS >= g_vMPStacks.size() )
00341         return -1;
00342     else if ( g_vMPStacks[iMPS] )
00343         return g_vMPStacks[iMPS]->GetSize();
00344     else
00345         return -1;
00346 }
00347 
00348 const char * HGEXPORT GetMPStackAtS(  unsigned int iMPS,  unsigned int iPL )
00349 {
00350     if ( iMPS >= g_vMPStacks.size() )
00351         return NULL;
00352     else if ( !g_vMPStacks[ iMPS ] )
00353         return NULL;
00354     else if ( iPL >= g_vMPStacks[ iMPS ]->GetSize() )
00355         return NULL;
00356     return g_vMPStacks[ iMPS ]->PeekItem( iPL ).GetValueS().c_str();
00357 }
00358 
00359 int HGEXPORT GetMPStackAtI(  unsigned int iMPS,  unsigned int iPL )
00360 {
00361     if ( iMPS >= g_vMPStacks.size() )
00362         return 0;
00363     else if ( !g_vMPStacks[ iMPS ] )
00364         return 0;
00365     else if ( iPL >= g_vMPStacks[ iMPS ]->GetSize() )
00366         return 0;
00367     return g_vMPStacks[ iMPS ]->PeekItem( iPL ).GetValueI();
00368 }
00369 
00370 float HGEXPORT GetMPStackAtF(  unsigned int iMPS,  unsigned int iPL )
00371 {
00372     if ( iMPS >= g_vMPStacks.size() )
00373         return (float)NULL;
00374     else if ( !g_vMPStacks[ iMPS ] )
00375         return (float)NULL;
00376     else if ( iPL >= g_vMPStacks[ iMPS ]->GetSize() )
00377         return (float)NULL;
00378     return g_vMPStacks[ iMPS ]->PeekItem( iPL ).GetValueF();
00379 }
00380 
00381 void HGEXPORT PushMPStackBackS( unsigned int iMPS, const char * s )
00382 {
00383     if ( iMPS >= g_vMPStacks.size() )
00384         return;
00385     else if ( !g_vMPStacks[ iMPS ] )
00386         return;
00387     g_vMPStacks[iMPS]->PushItem( PSElement( MString( s ) ) );
00388 }
00389 
00390 void HGEXPORT PushMPStackBackI( unsigned int iMPS, int i )
00391 {
00392     if ( iMPS >= g_vMPStacks.size() )
00393         return;
00394     else if ( !g_vMPStacks[ iMPS ] )
00395         return;
00396     g_vMPStacks[iMPS]->PushItem( PSElement( i ) );
00397 }
00398 
00399 void HGEXPORT PushMPStackBackF( unsigned int iMPS, float f )
00400 {
00401     if ( iMPS >= g_vMPStacks.size() )
00402         return;
00403     else if ( !g_vMPStacks[ iMPS ] )
00404         return;
00405     g_vMPStacks[iMPS]->PushItem( PSElement( f ) );
00406 }
00407 
00408 void HGEXPORT DeleteMPStack( unsigned int iMPS )
00409 {
00410     SAFE_DELETE( g_vMPStacks[iMPS] );
00411     g_vMPStacks[iMPS] = NULL;   //For good measure, if it is not set to null, we WILL crash.
00412 }
00413 
00414 void HGEXPORT FlushMPStacks()
00415 {
00416     for( unsigned i = 0; i < g_vMPStacks.size(); i++ )
00417         if( g_vMPStacks[i] )
00418             SAFE_DELETE( g_vMPStacks[i] );
00419 }
00420 
00421 PStack * GetMPStack( unsigned int iMPS )
00422 {
00423     if ( iMPS >= g_vMPStacks.size() )
00424         return NULL;
00425     else if ( !g_vMPStacks[ iMPS ] )
00426         return NULL;
00427     return g_vMPStacks[iMPS];
00428 }
00429 
00430 
00431 
00432 
00433 PSElement PSElement::Operate( const MString & sOperator, const PSElement & pRHS )
00434 {
00435     PSElement ret;
00436     if( sOperator.length() == 0 )
00437     {
00438         LOG.Warn( "NULL Operator used!" );
00439         return NILElement;
00440     }
00441 
00442     char sFirstChar = sOperator.c_str()[0];
00443     char sSecondChar = sOperator.c_str()[1];
00444     long lLength = sOperator.length();
00445 
00446     if( sFirstChar <= '.' )
00447     {
00448         if( sFirstChar == '!' )
00449         {
00450             if( sSecondChar == '=' && lLength == 2 )
00451             {
00452                 ret.type = BOOL;
00453                 ret.data.b = ( GetValueS().compare( pRHS.GetValueS() ) != 0 );
00454                 return ret;
00455             }
00456             if( lLength == 1 )
00457             {
00458                 ret.type = BOOL;
00459                 ret.data.b = !GetValueB();
00460                 return ret;
00461             }
00462         }
00463         if( sFirstChar == '*' )
00464         {
00465             if( sSecondChar == '=' && lLength == 2 )
00466             {
00467                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00468                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00469                 {
00470                     LOG.Warn( "Incompatible types for operator *" );
00471                     return NILElement;
00472                 }
00473                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00474                 {
00475                     data.f = GetValueF() * pRHS.GetValueF();
00476                     type = FLOAT;
00477                 } else {
00478                     data.l = GetValueI() * pRHS.GetValueI();
00479                     type = INTEGER;
00480                 }
00481                 return (*this);
00482             } else if( lLength == 1 )
00483             {
00484                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00485                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00486                 {
00487                     LOG.Warn( "Incompatible types for operator *" );
00488                     return NILElement;
00489                 }
00490                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00491                 {
00492                     ret.data.f = GetValueF() * pRHS.GetValueF();
00493                     ret.type = FLOAT;
00494                 } else {
00495                     ret.data.l = GetValueI() * pRHS.GetValueI();
00496                     ret.type = INTEGER;
00497                 }
00498                 return ret;
00499             }
00500         }
00501         if( sFirstChar == '+' )
00502         {
00503             if( sSecondChar == '=' && lLength == 2 )
00504             {
00505                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00506                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00507                 {
00508                     LOG.Warn( "Incompatible types for operator +" );
00509                     return NILElement;
00510                 }
00511                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00512                 {
00513                     data.f = GetValueF() + pRHS.GetValueF();
00514                     type = FLOAT;
00515                 } else {
00516                     data.l = GetValueI() + pRHS.GetValueI();
00517                     type = INTEGER;
00518                 }
00519                 return (*this);
00520             } else if( lLength == 1 )
00521             {
00522                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00523                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00524                 {
00525                     LOG.Warn( "Incompatible types for operator +" );
00526                     return NILElement;
00527                 }
00528                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00529                 {
00530                     ret.data.f = GetValueF() + pRHS.GetValueF();
00531                     ret.type = FLOAT;
00532                 } else {
00533                     ret.data.l = GetValueI() + pRHS.GetValueI();
00534                     ret.type = INTEGER;
00535                 }
00536                 return ret;
00537             }       
00538         }
00539         if( sFirstChar == '-' )
00540         {
00541             if( sSecondChar == '=' && lLength == 2 )
00542             {
00543                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00544                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00545                 {
00546                     LOG.Warn( "Incompatible types for operator -" );
00547                     return NILElement;
00548                 }
00549                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00550                 {
00551                     data.f = GetValueF() - pRHS.GetValueF();
00552                     type = FLOAT;
00553                 } else {
00554                     data.l = GetValueI() - pRHS.GetValueI();
00555                     type = INTEGER;
00556                 }
00557                 return (*this);
00558             } else if( lLength == 1 )
00559             {
00560                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00561                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00562                 {
00563                     LOG.Warn( "Incompatible types for operator -" );
00564                     return NILElement;
00565                 }
00566                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00567                 {
00568                     ret.data.f = GetValueF() - pRHS.GetValueF();
00569                     ret.type = FLOAT;
00570                 } else {
00571                     ret.data.l = GetValueI() - pRHS.GetValueI();
00572                     ret.type = INTEGER;
00573                 }
00574                 return ret;
00575             }       
00576         }
00577         if( sFirstChar == '.' )
00578         {
00579             if( sSecondChar == '=' && lLength == 2 )
00580             {
00581                 if( type == STRING )
00582                     delete data.dataS;
00583                 type = STRING;
00584                 data.dataS = new MString( GetValueS() + pRHS.GetValueS() );
00585                 return (*this);
00586 
00587             } else if( lLength == 1 )
00588             {
00589                 if( ret.type == STRING )
00590                     delete data.dataS;
00591                 ret.type = STRING;
00592                 ret.data.dataS = new MString( GetValueS() + pRHS.GetValueS() );
00593                 return ret;
00594             }   
00595         }
00596     } else {
00597         if( sFirstChar == '=' )
00598         {
00599             if( sSecondChar == '=' && lLength == 2 )
00600             {
00601                 ret.type = BOOL;
00602                 ret.data.b = ( GetValueS().compare( pRHS.GetValueS() ) == 0 );
00603                 return ret;
00604             }
00605             if( lLength == 1 )
00606             {
00607                 type = pRHS.type;
00608                 if( pRHS.type == STRING )
00609                 {
00610                     if( type == STRING )
00611                         *data.dataS = *pRHS.data.dataS;
00612                     else
00613                         data.dataS = new MString( *pRHS.data.dataS );
00614                 }
00615                 else
00616                     data = pRHS.data;
00617                 return (*this);
00618             }
00619         }
00620         if( sFirstChar == '>' )
00621         {
00622             if( sSecondChar == '=' && lLength == 2 )
00623             {
00624                 ret.type = BOOL;
00625                 ret.data.b = GetValueF() >= pRHS.GetValueF();
00626                 return ret;
00627             }
00628             if( lLength == 1 )
00629             {
00630                 ret.type = BOOL;
00631                 ret.data.b = GetValueF() > pRHS.GetValueF();
00632                 return ret;
00633             }
00634         }
00635         if( sFirstChar == '<' )
00636         {
00637             if( sSecondChar == '=' && lLength == 2 )
00638             {
00639                 ret.type = BOOL;
00640                 ret.data.b = GetValueF() <= pRHS.GetValueF();
00641                 return ret; 
00642             }
00643             if( lLength == 1 )
00644             {
00645                 ret.type = BOOL;
00646                 ret.data.b = GetValueF() < pRHS.GetValueF();
00647                 return ret;
00648             }
00649         }
00650         if( sFirstChar == '/' )
00651         {
00652             if( sSecondChar == '=' && lLength == 2 )
00653             {
00654                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00655                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00656                 {
00657                     LOG.Warn( "Incompatible types for operator /" );
00658                     return NILElement;
00659                 }
00660                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00661                 {
00662                     data.f = GetValueF() / pRHS.GetValueF();
00663                     type = FLOAT;
00664                 } else {
00665                     data.l = GetValueI() / pRHS.GetValueI();
00666                     type = INTEGER;
00667                 }
00668                 return (*this);
00669             } else if( lLength == 1 )
00670             {
00671                 if( ( GetType() != INTEGER && GetType() != FLOAT ) || 
00672                     ( pRHS.GetType() != INTEGER && pRHS.GetType() != FLOAT ) )
00673                 {
00674                     LOG.Warn( "Incompatible types for operator /" );
00675                     return NILElement;
00676                 }
00677                 if( GetType() == FLOAT || pRHS.GetType() == FLOAT )
00678                 {
00679                     ret.data.f = GetValueF() / pRHS.GetValueF();
00680                     ret.type = FLOAT;
00681                 } else {
00682                     ret.data.l = GetValueI() / pRHS.GetValueI();
00683                     ret.type = INTEGER;
00684                 }
00685                 return ret;
00686             }
00687         }
00688     }
00689 
00690     LOG.Warn( "Operator '" + sOperator + "' unknown." );
00691     return NILElement;
00692 }
00693 
00694 
00695 
00696 
00697 
00698 
00699 /* 
00700  * Copyright (c) 2006, Charles Lohr
00701  * All rights reserved.
00702  *
00703  * Redistribution and use in source and binary forms, with or
00704  * without modification, are permitted provided that the following
00705  * conditions are met:
00706  *  -   Redistributions of source code must retain the above
00707  *      copyright notice, this list of conditions and the following disclaimer.
00708  *  -   Redistributions in binary form must reproduce the above copyright
00709  *      notice, this list of conditions and the following disclaimer in
00710  *      the documentation and/or other materials provided with the distribution.
00711  *  -   Neither the name of the <ORGANIZATION> nor the names of its
00712  *      contributors may be used to endorse or promote products derived from
00713  *      this software without specific prior written permission.
00714  *
00715  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00716  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00717  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00718  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
00719  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00720  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00721  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00722  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00723  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
00724  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00725  */
00726 

Hosted by SourceForge.net Logo