Files
itgmania212121/src/RageUtil.cpp
T
Arthur Eubanks ecfcb11a00 Remove implicit conversion operator from RString to const char*
This is required for the RString to std::string migration.

Mostly automated from https://github.com/aeubanks/rewriter/blob/main/c_str.cc, with some manual intervention required for fixing up `a + b.c_str()` to `(a + b).c_str()`.

Added some overloads for some common global functions like sm_crash to reduce the number of changes required here.
2025-05-15 21:14:54 -07:00

2774 lines
68 KiB
C++

#include "global.h"
#include "RageUtil.h"
#include "RageMath.h"
#include "RageLog.h"
#include "RageFile.h"
#include "RageFileDriverDirectHelpers.h"
#include "RageSoundReader_FileReader.h"
#include "LocalizedString.h"
#include "LuaBinding.h"
#include "LuaManager.h"
#include <json/json.h>
#include <pcre.h>
#include <cfloat>
#include <cinttypes>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <functional>
#include <iomanip>
#include <map>
#include <numeric>
#include <sstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <random>
#include <climits>
const RString CUSTOM_SONG_PATH= "/@mem/";
bool HexToBinary(const RString&, RString&);
void utf8_sanitize(RString &);
void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *);
RandomGen g_RandomNumberGenerator;
/* Extend MersenneTwister into Lua space. This is intended to replace
* math.randomseed and math.random, so we conform to their behavior. */
namespace
{
MersenneTwister g_LuaPRNG;
/* To map from [0..2^31-1] to [0..1), we divide by 2^31. */
const double DIVISOR = std::pow( double(2), double(31) );
static int Seed( lua_State *L )
{
g_LuaPRNG = MersenneTwister( IArg(1) );
return 0;
}
static int Random( lua_State *L )
{
switch( lua_gettop(L) )
{
/* [0..1) */
case 0:
{
std::uniform_real_distribution<> dist( 0, 1 );
double r = dist( g_LuaPRNG );
lua_pushnumber( L, r );
return 1;
}
/* [1..u] */
case 1:
{
int upper = IArg(1);
luaL_argcheck( L, 1 <= upper, 1, "interval is empty" );
std::uniform_int_distribution<> dist( 1, upper );
lua_pushnumber( L, dist( g_LuaPRNG ) );
return 1;
}
/* [l..u] */
case 2:
{
int lower = IArg(1);
int upper = IArg(2);
luaL_argcheck( L, lower < upper, 2, "interval is empty" );
std::uniform_int_distribution<> dist( lower, upper );
lua_pushnumber( L, dist( g_LuaPRNG ) );
return 1;
}
/* wrong amount of arguments */
default:
{
return luaL_error( L, "wrong number of arguments" );
}
}
}
const luaL_Reg MersenneTwisterTable[] =
{
LIST_METHOD( Seed ),
LIST_METHOD( Random ),
{ nullptr, nullptr }
};
}
LUA_REGISTER_NAMESPACE( MersenneTwister );
void fapproach( float& val, float other_val, float to_move )
{
ASSERT_M( to_move >= 0, ssprintf("to_move: %f < 0", to_move) );
if( val == other_val )
return;
float fDelta = other_val - val;
float fSign = fDelta / std::abs( fDelta );
float fToMove = fSign*to_move;
if( std::abs(fToMove) > std::abs(fDelta) )
fToMove = fDelta; // snap
val += fToMove;
}
/* Return a positive x mod y. */
float fmodfp(float x, float y)
{
x = std::fmod(x, y); /* x is [-y,y] */
x += y; /* x is [0,y*2] */
x = std::fmod(x, y); /* x is [0,y] */
return x;
}
/* https://graphics.stanford.edu/%7Eseander/bithacks.html#RoundUpPowerOf2 */
int power_of_two( int v )
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
/* Always be sure to return a value of at least 1. In the event of any edge
* cases, such as a zero or negative input, the returned value will be `1`. */
v += (v == 0);
return v;
}
bool IsAnInt( const RString &s )
{
if( !s.size() )
return false;
for( size_t i=0; i < s.size(); ++i )
if( s[i] < '0' || s[i] > '9' )
return false;
return true;
}
bool IsHexVal( const RString &s )
{
if( !s.size() )
return false;
for( size_t i=0; i < s.size(); ++i )
if( !(s[i] >= '0' && s[i] <= '9') &&
!(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F'))
return false;
return true;
}
RString BinaryToHex( const void *pData_, size_t iNumBytes )
{
const unsigned char *pData = (const unsigned char *) pData_;
RString s;
for( size_t i=0; i<iNumBytes; i++ )
{
unsigned val = pData[i];
s += ssprintf( "%02x", val );
}
return s;
}
RString BinaryToHex( const RString &sString )
{
return BinaryToHex( sString.data(), sString.size() );
}
bool HexToBinary( const RString &s, unsigned char *stringOut )
{
if( !IsHexVal(s) )
return false;
for( int i=0; true; i++ )
{
if( (int)s.size() <= i*2 )
break;
RString sByte = s.substr( i*2, 2 );
uint8_t val = 0;
if( sscanf( sByte.c_str(), "%hhx", &val ) != 1 )
return false;
stringOut[i] = val;
}
return true;
}
bool HexToBinary( const RString &s, RString &sOut )
{
sOut.resize(s.size() / 2);
return HexToBinary(s, (unsigned char *) sOut.data());
}
float HHMMSSToSeconds( const RString &sHHMMSS )
{
std::vector<RString> arrayBits;
split( sHHMMSS, ":", arrayBits, false );
while( arrayBits.size() < 3 )
arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits
float fSeconds = 0;
fSeconds += StringToInt( arrayBits[0] ) * 60 * 60;
fSeconds += StringToInt( arrayBits[1] ) * 60;
fSeconds += StringToFloat( arrayBits[2] );
return fSeconds;
}
RString SecondsToHHMMSS(float fSecs)
{
const int iMinsDisplay = static_cast<int>(fSecs / 60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
RString sReturn = ssprintf("%02d:%02d:%02d", iMinsDisplay / 60, iMinsDisplay % 60, iSecsDisplay);
return sReturn;
}
RString SecondsToMMSSMsMs(float fSecs)
{
const int iMinsDisplay = static_cast<int>(fSecs / 60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
const int iLeftoverDisplay = static_cast<int>((fSecs - iMinsDisplay * 60 - iSecsDisplay) * 100);
RString sReturn = ssprintf("%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, std::min(99, iLeftoverDisplay));
return sReturn;
}
RString SecondsToMSSMsMs( float fSecs )
{
const int iMinsDisplay = static_cast<int>(fSecs/60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
const int iLeftoverDisplay = static_cast<int>((fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 );
RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, std::min(99,iLeftoverDisplay) );
return sReturn;
}
RString MicrosecondsToMMSSMsMs(uint64_t usecs)
{
const uint64_t totalSeconds = usecs / 1000000;
const uint64_t iMinsDisplay = totalSeconds / 60;
const uint64_t iSecsDisplay = totalSeconds % 60;
const uint64_t iLeftoverDisplay = (usecs % 1000000) / 10000; // Adjusted for two decimal places
RString sReturn = ssprintf("%02" PRIu64 ":%02" PRIu64 ".%02" PRIu64, iMinsDisplay, iSecsDisplay, std::min<uint64_t>(99, iLeftoverDisplay));
return sReturn;
}
RString SecondsToMMSSMsMsMs( float fSecs )
{
const int iMinsDisplay = static_cast<int>(fSecs/60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
const int iLeftoverDisplay = static_cast<int>((fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 );
RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, std::min(999,iLeftoverDisplay) );
return sReturn;
}
RString MicrosecondsToMMSSMsMsMs(uint64_t usecs)
{
const uint64_t totalSeconds = usecs / 1000000;
const uint64_t iMinsDisplay = totalSeconds / 60;
const uint64_t iSecsDisplay = totalSeconds % 60;
const uint64_t iLeftoverDisplay = (usecs % 1000000) / 1000;
RString sReturn = ssprintf("%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, iMinsDisplay, iSecsDisplay, std::min<uint64_t>(999, iLeftoverDisplay));
return sReturn;
}
RString SecondsToMSS( float fSecs )
{
const int iMinsDisplay = static_cast<int>(fSecs/60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay);
return sReturn;
}
RString SecondsToMMSS( float fSecs )
{
const int iMinsDisplay = static_cast<int>(fSecs/60);
const int iSecsDisplay = static_cast<int>(fmod(fSecs, 60));
RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay);
return sReturn;
}
RString PrettyPercent( float fNumerator, float fDenominator)
{
return ssprintf("%0.2f%%",fNumerator/fDenominator*100);
}
RString Commify( int iNum )
{
RString sNum = ssprintf("%d",iNum);
return Commify( sNum );
}
RString Commify(const RString& num, const RString& sep, const RString& dot)
{
size_t num_start= 0;
size_t num_end= num.size();
size_t dot_pos= num.find(dot);
size_t dash_pos= num.find('-');
if(dot_pos != std::string::npos)
{
num_end= dot_pos;
}
if(dash_pos != std::string::npos)
{
num_start= dash_pos + 1;
}
size_t num_size= num_end - num_start;
size_t commies= (num_size / 3) - (!(num_size % 3));
if(commies < 1)
{
return num;
}
size_t commified_len= num.size() + (commies * sep.size());
RString ret;
ret.resize(commified_len);
size_t dest= 0;
size_t next_comma= (num_size % 3) + (3 * (!(num_size % 3))) + num_start;
for(size_t c= 0; c < num.size(); ++c)
{
if(c == next_comma && c < num_end)
{
for(size_t s= 0; s < sep.size(); ++s)
{
ret[dest]= sep[s];
++dest;
}
next_comma+= 3;
}
ret[dest]= num[c];
++dest;
}
return ret;
}
static LocalizedString NUM_PREFIX ( "RageUtil", "NumPrefix" );
static LocalizedString NUM_ST ( "RageUtil", "NumSt" );
static LocalizedString NUM_ND ( "RageUtil", "NumNd" );
static LocalizedString NUM_RD ( "RageUtil", "NumRd" );
static LocalizedString NUM_TH ( "RageUtil", "NumTh" );
RString FormatNumberAndSuffix( int i )
{
RString sSuffix;
switch( i%10 )
{
case 1: sSuffix = NUM_ST; break;
case 2: sSuffix = NUM_ND; break;
case 3: sSuffix = NUM_RD; break;
default: sSuffix = NUM_TH; break;
}
// "11th", "113th", etc.
if( ((i%100) / 10) == 1 )
sSuffix = NUM_TH;
return NUM_PREFIX.GetValue() + ssprintf("%i", i) + sSuffix;
}
RString NormalizeDecimal(float num)
{
float mult = 1000.0f;
float rounded = std::round(num * mult) / mult;
std::ostringstream os;
os << std::fixed << std::setprecision(3) << rounded;
return os.str();
}
struct tm GetLocalTime()
{
const time_t t = time(nullptr);
struct tm tm;
localtime_r( &t, &tm );
return tm;
}
RString ssprintf( const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
RString sRet = vssprintf(fmt, va);
va_end(va);
return sRet;
}
RString vssprintf( const char *szFormat, va_list argList )
{
va_list tmp;
va_copy( tmp, argList );
int iNeeded = std::vsnprintf( nullptr, 0, szFormat, tmp );
va_end(tmp);
RString sRet;
sRet.resize(iNeeded);
std::vsnprintf( &sRet.front(), iNeeded+1, szFormat, argList );
return sRet;
}
/* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/php/code_list.php
* We don't use 3-letter codes, so we don't bother supporting them. */
static const LanguageInfo g_langs[] =
{
{"aa", "Afar"},
{"ab", "Abkhazian"},
{"af", "Afrikaans"},
{"am", "Amharic"},
{"ar", "Arabic"},
{"as", "Assamese"},
{"ay", "Aymara"},
{"az", "Azerbaijani"},
{"ba", "Bashkir"},
{"be", "Byelorussian"},
{"bg", "Bulgarian"},
{"bh", "Bihari"},
{"bi", "Bislama"},
{"bn", "Bengali"},
{"bo", "Tibetan"},
{"br", "Breton"},
{"ca", "Catalan"},
{"co", "Corsican"},
{"cs", "Czech"},
{"cy", "Welsh"},
{"da", "Danish"},
{"de", "German"},
{"dz", "Bhutani"},
{"el", "Greek"},
{"en", "English"},
{"eo", "Esperanto"},
{"es", "Spanish"},
{"et", "Estonian"},
{"eu", "Basque"},
{"fa", "Persian"},
{"fi", "Finnish"},
{"fj", "Fiji"},
{"fo", "Faeroese"},
{"fr", "French"},
{"fy", "Frisian"},
{"ga", "Irish"},
{"gd", "Gaelic"},
{"gl", "Galician"},
{"gn", "Guarani"},
{"gu", "Gujarati"},
{"ha", "Hausa"},
{"he", "Hebrew"},
{"hi", "Hindi"},
{"hr", "Croatian"},
{"hu", "Hungarian"},
{"hy", "Armenian"},
{"ia", "Interlingua"},
{"id", "Indonesian"},
{"ie", "Interlingue"},
{"ik", "Inupiak"},
{"in", "Indonesian"}, // compatibility
{"is", "Icelandic"},
{"it", "Italian"},
{"iw", "Hebrew"}, // compatibility
{"ja", "Japanese"},
{"ji", "Yiddish"}, // compatibility
{"jw", "Javanese"},
{"ka", "Georgian"},
{"kk", "Kazakh"},
{"kl", "Greenlandic"},
{"km", "Cambodian"},
{"kn", "Kannada"},
{"ko", "Korean"},
{"ks", "Kashmiri"},
{"ku", "Kurdish"},
{"ky", "Kirghiz"},
{"la", "Latin"},
{"ln", "Lingala"},
{"lo", "Laothian"},
{"lt", "Lithuanian"},
{"lv", "Latvian"},
{"mg", "Malagasy"},
{"mi", "Maori"},
{"mk", "Macedonian"},
{"ml", "Malayalam"},
{"mn", "Mongolian"},
{"mo", "Moldavian"},
{"mr", "Marathi"},
{"ms", "Malay"},
{"mt", "Maltese"},
{"my", "Burmese"},
{"na", "Nauru"},
{"ne", "Nepali"},
{"nl", "Dutch"},
{"no", "Norwegian"},
{"oc", "Occitan"},
{"om", "Oromo"},
{"or", "Oriya"},
{"pa", "Punjabi"},
{"pl", "Polish"},
{"ps", "Pashto"},
{"pt", "Portuguese"},
{"qu", "Quechua"},
{"rm", "Rhaeto-Romance"},
{"rn", "Kirundi"},
{"ro", "Romanian"},
{"ru", "Russian"},
{"rw", "Kinyarwanda"},
{"sa", "Sanskrit"},
{"sd", "Sindhi"},
{"sg", "Sangro"},
{"sh", "Serbo-Croatian"},
{"si", "Singhalese"},
{"sk", "Slovak"},
{"sl", "Slovenian"},
{"sm", "Samoan"},
{"sn", "Shona"},
{"so", "Somali"},
{"sq", "Albanian"},
{"sr", "Serbian"},
{"ss", "Siswati"},
{"st", "Sesotho"},
{"su", "Sudanese"},
{"sv", "Swedish"},
{"sw", "Swahili"},
{"ta", "Tamil"},
{"te", "Tegulu"},
{"tg", "Tajik"},
{"th", "Thai"},
{"ti", "Tigrinya"},
{"tk", "Turkmen"},
{"tl", "Tagalog"},
{"tn", "Setswana"},
{"to", "Tonga"},
{"tr", "Turkish"},
{"ts", "Tsonga"},
{"tt", "Tatar"},
{"tw", "Twi"},
{"uk", "Ukrainian"},
{"ur", "Urdu"},
{"uz", "Uzbek"},
{"vi", "Vietnamese"},
{"vo", "Volapuk"},
{"wo", "Wolof"},
{"xh", "Xhosa"},
{"yi", "Yiddish"},
{"yo", "Yoruba"},
{"zh-Hans", "Chinese (Simplified)"},
{"zh-Hant", "Chinese (Traditional)"},
{"zu", "Zulu"},
};
void GetLanguageInfos( std::vector<const LanguageInfo*> &vAddTo )
{
for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
vAddTo.push_back( &g_langs[i] );
}
const LanguageInfo *GetLanguageInfo( const RString &sIsoCode )
{
for( unsigned i=0; i<ARRAYLEN(g_langs); ++i )
{
if( sIsoCode.EqualsNoCase(g_langs[i].szIsoCode) )
return &g_langs[i];
}
return nullptr;
}
RString join( const RString &sDeliminator, const std::vector<RString> &sSource)
{
if( sSource.empty() )
return RString();
RString sTmp;
size_t final_size= 0;
size_t delim_size= sDeliminator.size();
for(size_t n= 0; n < sSource.size()-1; ++n)
{
final_size+= sSource[n].size() + delim_size;
}
final_size+= sSource.back().size();
sTmp.reserve(final_size);
for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ )
{
sTmp += sSource[iNum];
sTmp += sDeliminator;
}
sTmp += sSource.back();
return sTmp;
}
RString join( const RString &sDelimitor, std::vector<RString>::const_iterator begin, std::vector<RString>::const_iterator end )
{
if( begin == end )
return RString();
RString sRet;
size_t final_size= 0;
size_t delim_size= sDelimitor.size();
for(std::vector<RString>::const_iterator curr= begin; curr != end; ++curr)
{
final_size+= curr->size();
if(curr != end)
{
final_size+= delim_size;
}
}
sRet.reserve(final_size);
while( begin != end )
{
sRet += *begin;
++begin;
if( begin != end )
sRet += sDelimitor;
}
return sRet;
}
RString serialize(const std::vector<float> & sSource, const RString &sDelimitor, int precision)
{
std::vector<RString> values;
values.reserve(sSource.size());
RString precisionStr = ssprintf("%%.%df", precision);
for(float s : sSource)
{
values.push_back(ssprintf(precisionStr.c_str(), s));
}
return join(sDelimitor, values);
}
RString serialize(const std::vector<int> & sSource, const RString &sDelimitor)
{
std::vector<RString> values;
values.reserve(sSource.size());
for(int s : sSource)
{
values.push_back(ssprintf("%d", s));
}
return join(sDelimitor, values);
}
RString SmEscape( const RString &sUnescaped, const std::vector<char> charsToEscape )
{
return SmEscape(sUnescaped.c_str(), sUnescaped.size(), charsToEscape);
}
RString SmEscape ( const char *cUnescaped, int len, const std::vector<char> charsToEscape )
{
RString answer = "";
for( int i = 0; i < len; ++i )
{
if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' )
{
answer += "\\/\\/";
++i; // increment here so we skip both //s
continue;
}
for(char escapeChar: charsToEscape)
{
if(cUnescaped[i] == escapeChar)
{
answer += "\\";
break;
}
}
answer += cUnescaped[i];
}
return answer;
}
std::vector<RString> SmEscape(const std::vector<RString> &vUnescaped, const std::vector<char> charsToEscape)
{
std::vector<RString> escaped;
for(RString unescaped: vUnescaped)
{
escaped.push_back(SmEscape(unescaped, charsToEscape));
}
return escaped;
}
RString SmUnescape( const RString &sEscaped )
{
RString unescaped = sEscaped;
unescaped.Replace("\\\\", "||escaped-backslash||");
unescaped.Replace("\\", "");
unescaped.Replace("||escaped-backslash||", "\\");
return unescaped;
}
RString DwiEscape( const RString &sUnescaped )
{
return DwiEscape( sUnescaped.c_str(), sUnescaped.size() );
}
RString DwiEscape( const char *cUnescaped, int len )
{
RString answer = "";
for( int i = 0; i < len; ++i )
{
switch( cUnescaped[i] )
{
// TODO: Which of these characters actually affect DWI?
case '\\':
case ':':
case ';': answer += '|'; break;
case '[': answer += '('; break;
case ']': answer += ')'; break;
default: answer += cUnescaped[i];
}
}
return answer;
}
template <class S>
static int DelimitorLength( const S &Delimitor )
{
return Delimitor.size();
}
static int DelimitorLength( char Delimitor )
{
return 1;
}
static int DelimitorLength( wchar_t Delimitor )
{
return 1;
}
template <class S, class C>
void do_split( const S &Source, const C Delimitor, std::vector<S> &AddIt, const bool bIgnoreEmpty )
{
/* Short-circuit if the source is empty; we want to return an empty vector if
* the string is empty, even if bIgnoreEmpty is true. */
if( Source.empty() )
return;
size_t startpos = 0;
do {
size_t pos;
pos = Source.find( Delimitor, startpos );
if( pos == Source.npos )
pos = Source.size();
if( pos > startpos || !bIgnoreEmpty )
{
/* Optimization: if we're copying the whole string, avoid substr; this
* allows this copy to be refcounted, which is much faster. */
if( startpos == 0 && pos-startpos == Source.size() )
AddIt.push_back(Source);
else
{
const S AddRString = Source.substr(startpos, pos-startpos);
AddIt.push_back(AddRString);
}
}
startpos = pos+DelimitorLength(Delimitor);
} while ( startpos <= Source.size() );
}
void split( const RString &sSource, const RString &sDelimitor, std::vector<RString> &asAddIt, const bool bIgnoreEmpty )
{
if( sDelimitor.size() == 1 )
do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
else
do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
}
void split( const std::wstring &sSource, const std::wstring &sDelimitor, std::vector<std::wstring> &asAddIt, const bool bIgnoreEmpty )
{
if( sDelimitor.size() == 1 )
do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty );
else
do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty );
}
std::vector<RString> split( const RString& sSource, const char delimiter, const bool bIgnoreEmpty )
{
std::vector<RString> result;
RString sDelimiter(1, delimiter); // Create an RString from the char delimiter
split(sSource, sDelimiter, result, bIgnoreEmpty);
return result;
}
/* Use:
RString str="a,b,c";
int start = 0, size = -1;
for(;;)
{
do_split( str, ",", start, size );
if( start == str.size() )
break;
str[start] = 'Q';
}
*/
template <class S>
void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
if( size != -1 )
{
// Start points to the beginning of the last delimiter. Move it up.
begin += size+Delimitor.size();
begin = std::min( begin, len );
}
size = 0;
if( bIgnoreEmpty )
{
// Skip delims.
while( begin + Delimitor.size() < Source.size() &&
!Source.compare( begin, Delimitor.size(), Delimitor ) )
++begin;
}
/* Where's the string function to find within a substring?
* C++ strings apparently are missing that ... */
size_t pos;
if( Delimitor.size() == 1 )
pos = Source.find( Delimitor[0], begin );
else
pos = Source.find( Delimitor, begin );
if( pos == Source.npos || (int) pos > len )
pos = len;
size = pos - begin;
}
void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
}
void split( const std::wstring &Source, const std::wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty )
{
do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty );
}
void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
{
do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
}
void split( const std::wstring &Source, const std::wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty )
{
do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty );
}
/*
* foo\fum\ -> "foo\fum\", "", ""
* c:\foo\bar.txt -> "c:\foo\", "bar", ".txt"
* \\foo\fum -> "\\foo\", "fum", ""
*/
void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt )
{
sDir = sFilename = sExt = "";
std::vector<RString> asMatches;
/*
* One level of escapes for the regex, one for C. Ew.
* This is really:
* ^(.*[\\/])?(.*)$
*/
static Regex sep("^(.*[\\\\/])?(.*)$");
bool bCheck = sep.Compare( sPath, asMatches );
ASSERT( bCheck );
sDir = asMatches[0];
const RString sBase = asMatches[1];
/* ^(.*)(\.[^\.]+)$ */
static Regex SplitExt("^(.*)(\\.[^\\.]+)$");
if( SplitExt.Compare(sBase, asMatches) )
{
sFilename = asMatches[0];
sExt = asMatches[1];
}
else
{
sFilename = sBase;
}
}
RString custom_songify_path(RString const& path)
{
std::vector<RString> parts;
split(path, "/", parts, false);
if(parts.size() < 2)
{
return CUSTOM_SONG_PATH + path;
}
return CUSTOM_SONG_PATH + parts[parts.size()-2] + "/" + parts[parts.size()-1];
}
/* "foo.bar", "baz" -> "foo.baz"
* "foo", "baz" -> "foo.baz"
* "foo.bar", "" -> "foo" */
RString SetExtension( const RString &sPath, const RString &sExt )
{
RString sDir, sFileName, sOldExt;
splitpath( sPath, sDir, sFileName, sOldExt );
return sDir + sFileName + (sExt.size()? ".":"") + sExt;
}
RString GetExtension( const RString &sPath )
{
size_t pos = sPath.rfind( '.' );
if( pos == sPath.npos )
return RString();
size_t slash = sPath.find( '/', pos );
if( slash != sPath.npos )
return RString(); /* rare: path/dir.ext/fn */
return sPath.substr( pos+1, sPath.size()-pos+1 );
}
RString GetFileNameWithoutExtension( const RString &sPath )
{
RString sThrowAway, sFName;
splitpath( sPath, sThrowAway, sFName, sThrowAway );
return sFName;
}
void MakeValidFilename( RString &sName )
{
std::wstring wsName = RStringToWstring( sName );
std::wstring wsInvalid = L"/\\:*?\"<>|";
for( unsigned i = 0; i < wsName.size(); ++i )
{
wchar_t w = wsName[i];
if( w >= 32 &&
w < 126 &&
wsInvalid.find_first_of(w) == wsInvalid.npos )
continue;
if( w == L'"' )
{
wsName[i] = L'\'';
continue;
}
/* We could replace with closest matches in ASCII: convert the character
* to UTF-8 NFD (decomposed) (maybe NFKD?), and see if the first
* character is ASCII. This is useless for non-Western languages,
* since we'll replace the whole filename. */
wsName[i] = '_';
}
sName = WStringToRString( wsName );
}
bool FindFirstFilenameContaining(const std::vector<RString>& filenames,
RString& out, const std::vector<RString>& starts_with,
const std::vector<RString>& contains, const std::vector<RString>& ends_with)
{
for(size_t i= 0; i < filenames.size(); ++i)
{
RString lower= GetFileNameWithoutExtension(filenames[i]);
lower.MakeLower();
for(size_t s= 0; s < starts_with.size(); ++s)
{
if(!lower.compare(0, starts_with[s].size(), starts_with[s]))
{
out= filenames[i];
return true;
}
}
size_t lower_size= lower.size();
for(size_t s= 0; s < ends_with.size(); ++s)
{
if(lower_size >= ends_with[s].size())
{
size_t end_pos= lower_size - ends_with[s].size();
if(!lower.compare(end_pos, std::string::npos, ends_with[s]))
{
out= filenames[i];
return true;
}
}
}
for(size_t s= 0; s < contains.size(); ++s)
{
if(lower.find(contains[s]) != std::string::npos)
{
out= filenames[i];
return true;
}
}
}
return false;
}
int g_argc = 0;
char **g_argv = nullptr;
void SetCommandlineArguments( int argc, char **argv )
{
g_argc = argc;
g_argv = argv;
}
void GetCommandLineArguments( int &argc, char **&argv )
{
argc = g_argc;
argv = g_argv;
}
/* Search for the commandline argument given; eg. "test" searches for the
* option "--test". All commandline arguments are getopt_long style: --foo;
* short arguments (-x) are not supported. (These are not intended for
* common, general use, so having short options isn't currently needed.)
* If argument is non-nullptr, accept an argument. */
bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex )
{
const RString optstr = "--" + option;
for( int arg = 1; arg < g_argc; ++arg )
{
const RString CurArgument = g_argv[arg];
const size_t i = CurArgument.find( "=" );
RString CurOption = CurArgument.substr(0,i);
if( CurOption.CompareNoCase(optstr) )
continue; // no match
// Found it.
if( iIndex )
{
--iIndex;
continue;
}
if( argument )
{
if( i != RString::npos )
*argument = CurArgument.substr( i+1 );
else
*argument = "";
}
return true;
}
return false;
}
RString GetCwd()
{
char buf[PATH_MAX];
bool ret = DoGetCwd(buf, PATH_MAX) != nullptr;
ASSERT(ret);
return buf;
}
/*
* Calculate a standard CRC32. iCRC should be initialized to 0.
* References:
* http://www.theorem.com/java/CRC32.java,
* http://www.faqs.org/rfcs/rfc1952.html
*/
void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize )
{
static unsigned tab[256];
static bool initted = false;
if( !initted )
{
initted = true;
const unsigned POLY = 0xEDB88320;
for( int i = 0; i < 256; ++i )
{
tab[i] = i;
for( int j = 0; j < 8; ++j )
{
if( tab[i] & 1 )
tab[i] = (tab[i] >> 1) ^ POLY;
else
tab[i] >>= 1;
}
}
}
iCRC ^= 0xFFFFFFFF;
const char *pBuffer = (const char *) pVoidBuffer;
for( unsigned i = 0; i < iSize; ++i )
iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF];
iCRC ^= 0xFFFFFFFF;
}
unsigned int GetHashForString( const RString &s )
{
unsigned crc = 0;
CRC32( crc, s.data(), s.size() );
return crc;
}
/* Return true if "dir" is empty or does not exist. */
bool DirectoryIsEmpty( const RString &sDir )
{
if( sDir.empty() )
return true;
if( !DoesFileExist(sDir) )
return true;
std::vector<RString> asFileNames;
GetDirListing( sDir, asFileNames );
return asFileNames.empty();
}
bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 )
{
return sStr1.CompareNoCase( sStr2 ) < 0;
}
bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 )
{
return sStr1.CompareNoCase( sStr2 ) > 0;
}
void SortRStringArray( std::vector<RString> &arrayRStrings, const bool bSortAscending )
{
sort( arrayRStrings.begin(), arrayRStrings.end(),
bSortAscending?CompareRStringsAsc:CompareRStringsDesc );
}
float calc_mean(const float* pStart, const float* pEnd)
{
/* The Kahan summation algorithm is used here to prevent
* situations where the low order bits may be lost.
* https://en.wikipedia.org/wiki/Kahan_summation_algorithm */
if (pStart == pEnd)
return 0.0f;
float sum = 0.0f;
float c = 0.0f;
for (const float* p = pStart; p != pEnd; ++p)
{
float y = *p - c;
float t = sum + y;
c = (t - sum) - y;
sum = t;
}
return sum / (pEnd - pStart);
}
float calc_stddev( const float *pStart, const float *pEnd, bool bSample )
{
/* Calculate the mean. */
float fMean = calc_mean( pStart, pEnd );
/* Calculate stddev. */
float fDev = 0.0f;
for( const float *i=pStart; i != pEnd; ++i )
fDev += (*i - fMean) * (*i - fMean);
fDev /= std::distance( pStart, pEnd ) - (bSample ? 1 : 0);
fDev = std::sqrt( fDev );
return fDev;
}
bool CalcLeastSquares( const std::vector<std::pair<float, float>> &vCoordinates,
float &fSlope, float &fIntercept, float &fError )
{
if( vCoordinates.empty() )
return false;
float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f;
for( unsigned i = 0; i < vCoordinates.size(); ++i )
{
fSumXX += vCoordinates[i].first * vCoordinates[i].first;
fSumXY += vCoordinates[i].first * vCoordinates[i].second;
fSumX += vCoordinates[i].first;
fSumY += vCoordinates[i].second;
}
const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX;
fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator;
fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator;
fError = 0.0f;
for( unsigned i = 0; i < vCoordinates.size(); ++i )
{
const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second;
fError += fOneError * fOneError;
}
fError /= vCoordinates.size();
fError = std::sqrt( fError );
return true;
}
void FilterHighErrorPoints( std::vector<std::pair<float, float>> &vCoordinates,
float fSlope, float fIntercept, float fCutoff )
{
unsigned int iOut = 0;
for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn )
{
const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second;
if( std::abs(fError) < fCutoff )
{
vCoordinates[iOut] = vCoordinates[iIn];
++iOut;
}
}
vCoordinates.resize( iOut );
}
void TrimLeft( RString &sStr, const char *s )
{
int n = 0;
while( n < int(sStr.size()) && strchr(s, sStr[n]) )
n++;
sStr.erase( sStr.begin(), sStr.begin()+n );
}
void TrimRight( RString &sStr, const char *s )
{
int n = sStr.size();
while( n > 0 && strchr(s, sStr[n-1]) )
n--;
/* Delete from n to the end. If n == sStr.size(), nothing is deleted;
* if n == 0, the whole string is erased. */
sStr.erase( sStr.begin()+n, sStr.end() );
}
void Trim( RString &sStr, const char *s )
{
RString::size_type b = 0, e = sStr.size();
while( b < e && strchr(s, sStr[b]) )
++b;
while( b < e && strchr(s, sStr[e-1]) )
--e;
sStr.assign( sStr.substr(b, e-b) );
}
void StripCrnl( RString &s )
{
while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') )
s.erase( s.size()-1 );
}
bool BeginsWith( const RString &sTestThis, const RString &sBeginning )
{
ASSERT( !sBeginning.empty() );
return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0;
}
bool EndsWith( const RString &sTestThis, const RString &sEnding )
{
ASSERT( !sEnding.empty() );
if( sTestThis.size() < sEnding.size() )
return false;
return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0;
}
RString URLEncode( const RString &sStr )
{
RString sOutput;
for( unsigned k = 0; k < sStr.size(); k++ )
{
char t = sStr[k];
if( t >= '!' && t <= 'z' )
sOutput += t;
else
sOutput += "%" + ssprintf( "%02X", t );
}
return sOutput;
}
// remove various version control-related files
static bool CVSOrSVN( const RString& s )
{
return s.Right(3).EqualsNoCase("CVS") ||
s.Right(4) == ".svn" ||
s.Right(3).EqualsNoCase(".hg");
}
void StripCvsAndSvn( std::vector<RString> &vs )
{
RemoveIf( vs, CVSOrSVN );
}
static bool MacResourceFork( const RString& s )
{
return s.Left(2).EqualsNoCase("._");
}
void StripMacResourceForks( std::vector<RString> &vs )
{
RemoveIf( vs, MacResourceFork );
}
// path is a .redir pathname. Read it and return the real one.
RString DerefRedir( const RString &_path )
{
RString sPath = _path;
for( int i=0; i<100; i++ )
{
if( GetExtension(sPath) != "redir" )
return sPath;
RString sNewFileName;
GetFileContents( sPath, sNewFileName, true );
// Empty is invalid.
if( sNewFileName == "" )
return RString();
RString sPath2 = Dirname(sPath) + sNewFileName;
CollapsePath( sPath2 );
sPath2 += "*";
std::vector<RString> matches;
GetDirListing( sPath2, matches, false, true );
if( matches.empty() )
RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() );
else if( matches.size() > 1 )
RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() );
sPath = matches[0];
}
RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() );
}
bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine )
{
// Don't warn if the file doesn't exist, but do warn if it exists and fails to open.
if( !IsAFile(sPath) )
return false;
RageFile file;
if( !file.Open(sPath) )
{
LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
return false;
}
// todo: figure out how to make this UTF-8 safe. -aj
RString sData;
int iGot;
if( bOneLine )
iGot = file.GetLine( sData );
else
iGot = file.Read( sData, file.GetFileSize() );
if( iGot == -1 )
{
LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() );
return false;
}
if( bOneLine )
StripCrnl( sData );
sOut = sData;
return true;
}
bool GetFileContents( const RString &sFile, std::vector<RString> &asOut )
{
RageFile file;
if( !file.Open(sFile) )
{
LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() );
return false;
}
RString sLine;
while( file.GetLine(sLine) )
asOut.push_back( sLine );
return true;
}
void Regex::Compile()
{
const char *error;
int offset;
m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, nullptr );
if( m_pReg == nullptr )
RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error );
int iRet = pcre_fullinfo( (pcre *) m_pReg, nullptr, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs );
ASSERT( iRet >= 0 );
++m_iBackrefs;
ASSERT( m_iBackrefs < 128 );
}
void Regex::Set( const RString &sStr )
{
Release();
m_sPattern = sStr;
Compile();
}
void Regex::Release()
{
pcre_free( m_pReg );
m_pReg = nullptr;
m_sPattern = RString();
}
Regex::Regex( const RString &sStr ): m_pReg(nullptr), m_iBackrefs(0), m_sPattern(RString())
{
Set( sStr );
}
Regex::Regex( const Regex &rhs ): m_pReg(nullptr), m_iBackrefs(0), m_sPattern(RString())
{
Set( rhs.m_sPattern );
}
Regex &Regex::operator=( const Regex &rhs )
{
if( this != &rhs )
Set( rhs.m_sPattern );
return *this;
}
Regex::~Regex()
{
Release();
}
bool Regex::Compare( const RString &sStr )
{
int iMat[128*3];
int iRet = pcre_exec( (pcre *) m_pReg, nullptr, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );
if( iRet < -1 )
RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );
return iRet >= 0;
}
bool Regex::Compare( const RString &sStr, std::vector<RString> &asMatches )
{
asMatches.clear();
int iMat[128*3];
int iRet = pcre_exec( (pcre *) m_pReg, nullptr, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 );
if( iRet < -1 )
RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet );
if( iRet == -1 )
return false;
for( unsigned i = 1; i < m_iBackrefs; ++i )
{
const int iStart = iMat[i*2], end = iMat[i*2+1];
if( iStart == -1 )
asMatches.push_back( RString() ); /* no match */
else
asMatches.push_back( sStr.substr(iStart, end - iStart) );
}
return true;
}
// Arguments and behavior are the same are similar to
// http://us3.php.net/manual/en/function.preg-replace.php
bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut )
{
std::vector<RString> asMatches;
if( !Compare(sSubject, asMatches) )
return false;
sOut = sReplacement;
// TODO: optimize me by iterating only once over the string
for( unsigned i=0; i<asMatches.size(); i++ )
{
RString sFrom = ssprintf( "\\${%d}", i );
RString sTo = asMatches[i];
sOut.Replace(sFrom, sTo);
}
return true;
}
/* Given a UTF-8 byte, return the length of the codepoint (if a start code)
* or 0 if it's a continuation byte. */
int utf8_get_char_len( char p )
{
if( !(p & 0x80) ) return 1; /* 0xxxxxxx - 1 */
if( !(p & 0x40) ) return 1; /* 10xxxxxx - continuation */
if( !(p & 0x20) ) return 2; /* 110xxxxx */
if( !(p & 0x10) ) return 3; /* 1110xxxx */
if( !(p & 0x08) ) return 4; /* 11110xxx */
if( !(p & 0x04) ) return 5; /* 111110xx */
if( !(p & 0x02) ) return 6; /* 1111110x */
return 1; /* 1111111x */
}
static inline bool is_utf8_continuation_byte( char c )
{
return (c & 0xC0) == 0x80;
}
/* Decode one codepoint at start; advance start and place the result in ch.
* If the encoded string is invalid, false is returned. */
bool utf8_to_wchar_ec( const RString &s, unsigned &start, wchar_t &ch )
{
if( start >= s.size() )
return false;
if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */
(s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */
{
start += 1;
return false;
}
int len = utf8_get_char_len( s[start] );
const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
ch = wchar_t(s[start] & first_byte_mask[len]);
for( int i = 1; i < len; ++i )
{
if( start+i >= s.size() )
{
/* We expected a continuation byte, but didn't get one. Return error, and point
* start at the unexpected byte; it's probably a new sequence. */
start += i;
return false;
}
char byte = s[start+i];
if( !is_utf8_continuation_byte(byte) )
{
/* We expected a continuation byte, but didn't get one. Return error, and point
* start at the unexpected byte; it's probably a new sequence. */
start += i;
return false;
}
ch = (ch << 6) | (byte & 0x3F);
}
bool bValid = true;
{
unsigned c1 = (unsigned) s[start] & 0xFF;
unsigned c2 = (unsigned) s[start+1] & 0xFF;
int c = (c1 << 8) + c2;
if( (c & 0xFE00) == 0xC000 ||
(c & 0xFFE0) == 0xE080 ||
(c & 0xFFF0) == 0xF080 ||
(c & 0xFFF8) == 0xF880 ||
(c & 0xFFFC) == 0xFC80 )
{
bValid = false;
}
}
if( ch == 0xFFFE || ch == 0xFFFF )
bValid = false;
start += len;
return bValid;
}
/* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */
bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch )
{
if( start >= iLength )
return false;
int len = utf8_get_char_len( s[start] );
if( start+len > iLength )
{
// We don't have room for enough continuation bytes. Return error.
start += len;
ch = L'?';
return false;
}
switch( len )
{
case 1:
ch = (s[start+0] & 0x7F);
break;
case 2:
ch = ( (s[start+0] & 0x1F) << 6 ) |
(s[start+1] & 0x3F);
break;
case 3:
ch = ( (s[start+0] & 0x0F) << 12 ) |
( (s[start+1] & 0x3F) << 6 ) |
(s[start+2] & 0x3F);
break;
case 4:
ch = ( (s[start+0] & 0x07) << 18 ) |
( (s[start+1] & 0x3F) << 12 ) |
( (s[start+2] & 0x3F) << 6 ) |
(s[start+3] & 0x3F);
break;
case 5:
ch = ( (s[start+0] & 0x03) << 24 ) |
( (s[start+1] & 0x3F) << 18 ) |
( (s[start+2] & 0x3F) << 12 ) |
( (s[start+3] & 0x3F) << 6 ) |
(s[start+4] & 0x3F);
break;
case 6:
ch = ( (s[start+0] & 0x01) << 30 ) |
( (s[start+1] & 0x3F) << 24 ) |
( (s[start+2] & 0x3F) << 18 ) |
( (s[start+3] & 0x3F) << 12) |
( (s[start+4] & 0x3F) << 6 ) |
(s[start+5] & 0x3F);
break;
}
start += len;
return true;
}
// UTF-8 encode ch and append to out.
void wchar_to_utf8( wchar_t ch, RString &out )
{
if( ch < 0x80 ) { out.append( 1, (char) ch ); return; }
int cbytes = 0;
if( ch < 0x800 ) cbytes = 1;
else if( ch < 0x10000 ) cbytes = 2;
else if( ch < 0x200000 ) cbytes = 3;
else if( ch < 0x4000000 ) cbytes = 4;
else cbytes = 5;
{
int shift = cbytes*6;
const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) );
}
for( int i = 0; i < cbytes; ++i )
{
int shift = (cbytes-i-1)*6;
out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) );
}
}
wchar_t utf8_get_char( const RString &s )
{
unsigned start = 0;
wchar_t ret;
if( !utf8_to_wchar_ec( s, start, ret ) )
return INVALID_CHAR;
return ret;
}
// Replace invalid sequences in s.
void utf8_sanitize( RString &s )
{
RString ret;
for( unsigned start = 0; start < s.size(); )
{
wchar_t ch;
if( !utf8_to_wchar_ec( s, start, ch ) )
ch = INVALID_CHAR;
wchar_to_utf8( ch, ret );
}
s = ret;
}
bool utf8_is_valid( const RString &s )
{
for( unsigned start = 0; start < s.size(); )
{
wchar_t ch;
if( !utf8_to_wchar_ec( s, start, ch ) )
return false;
}
return true;
}
/* Windows tends to drop garbage BOM characters at the start of UTF-8 text files.
* Remove them. */
void utf8_remove_bom( RString &sLine )
{
if( !sLine.compare(0, 3, "\xef\xbb\xbf") )
sLine.erase(0, 3);
}
static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] )
{
// Note: this has problems with certain accented characters. -aj
wchar_t wc = L'\0';
unsigned iStart = 0;
if( !utf8_to_wchar(p, iLen, iStart, wc) )
return 1;
wchar_t iUpper = wc;
if( wc < 256 )
iUpper = pMapping[wc];
if( iUpper != wc )
{
RString sOut;
wchar_to_utf8( iUpper, sOut );
if( sOut.size() == iStart )
memcpy( p, sOut.data(), sOut.size() );
else
WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) );
}
return iStart;
}
/* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8
* length, so we never have to move the whole string. This is optimized for strings that have no
* non-ASCII characters. */
void MakeUpper( char *p, size_t iLen )
{
char *pStart = p;
char *pEnd = p + iLen;
while( p < pEnd )
{
// Fast path:
if( likely( !(*p & 0x80) ) )
{
if( unlikely(*p >= 'a' && *p <= 'z') )
*p += 'A' - 'a';
++p;
continue;
}
int iRemaining = iLen - (p-pStart);
p += UnicodeDoUpper( p, iRemaining, g_UpperCase );
}
}
void MakeLower( char *p, size_t iLen )
{
char *pStart = p;
char *pEnd = p + iLen;
while( p < pEnd )
{
// Fast path:
if( likely( !(*p & 0x80) ) )
{
if( unlikely(*p >= 'A' && *p <= 'Z') )
*p -= 'A' - 'a';
++p;
continue;
}
int iRemaining = iLen - (p-pStart);
p += UnicodeDoUpper( p, iRemaining, g_LowerCase );
}
}
void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] )
{
wchar_t *pEnd = p + iLen;
while( p != pEnd )
{
// wchar_t can be signed or unsigned depending on the platform and the compiler.
// We use WCHAR_MIN to determine a valid condition that won't emit a type-limits warning.
#if WCHAR_MIN != 0
if( *p >= 0 && *p < 256 ) {
#else
if( *p < 256 ) {
#endif
*p = pMapping[*p];
}
++p;
}
}
void MakeUpper( wchar_t *p, size_t iLen )
{
UnicodeUpperLower( p, iLen, g_UpperCase );
}
void MakeLower( wchar_t *p, size_t iLen )
{
UnicodeUpperLower( p, iLen, g_LowerCase );
}
float StringToFloat( const RString &sString )
{
float fOut = std::strtof(sString.c_str(), nullptr);
if (!std::isfinite(fOut))
{
fOut = 0.0f;
}
return fOut;
}
bool StringToFloat( const RString &sString, float &fOut )
{
char *endPtr = nullptr;
fOut = std::strtof(sString.c_str(), &endPtr);
return sString.size() && *endPtr == '\0' && std::isfinite(fOut);
}
RString FloatToString( const float &num )
{
std::stringstream ss;
ss << num;
return ss.str();
}
int StringToInt( const std::string& str, size_t* pos, int base, int exceptVal )
{
try
{
return std::stoi(str, pos, base);
}
catch (const std::invalid_argument & e) {
LOG->Warn( "stoi(%s): %s", str.c_str(), e.what() );
}
catch (const std::out_of_range & e) {
LOG->Warn( "stoi(%s): %s", str.c_str(), e.what() );
}
return exceptVal;
}
long StringToLong( const std::string& str, size_t* pos, int base, long exceptVal )
{
try
{
return std::stol(str, pos, base);
}
catch (const std::invalid_argument & e) {
LOG->Warn( "stol(%s): %s", str.c_str(), e.what() );
}
catch (const std::out_of_range & e) {
LOG->Warn( "stol(%s): %s", str.c_str(), e.what() );
}
return exceptVal;
}
long long StringToLLong( const std::string& str, size_t* pos, int base, long long exceptVal )
{
try
{
return std::stoll(str, pos, base);
}
catch (const std::invalid_argument & e) {
LOG->Warn( "stoll(%s): %s", str.c_str(), e.what() );
}
catch (const std::out_of_range & e) {
LOG->Warn( "stoll(%s): %s", str.c_str(), e.what() );
}
return exceptVal;
}
const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */
std::wstring RStringToWstring( const RString &s )
{
std::wstring ret;
ret.reserve( s.size() );
for( unsigned start = 0; start < s.size(); )
{
char c = s[start];
if( !(c&0x80) )
{
// ASCII fast path
ret += c;
++start;
continue;
}
wchar_t ch = L'\0';
if( !utf8_to_wchar( s.data(), s.size(), start, ch ) )
ch = INVALID_CHAR;
ret += ch;
}
return ret;
}
RString WStringToRString( const std::wstring &sStr )
{
RString sRet;
for( unsigned i = 0; i < sStr.size(); ++i )
wchar_to_utf8( sStr[i], sRet );
return sRet;
}
RString WcharToUTF8( wchar_t c )
{
RString ret;
wchar_to_utf8( c, ret );
return ret;
}
// &a; -> a
void ReplaceEntityText( RString &sText, const std::map<RString, RString> &m )
{
RString sRet;
size_t iOffset = 0;
while( iOffset != sText.size() )
{
size_t iStart = sText.find( '&', iOffset );
if( iStart == sText.npos )
{
// Optimization: if we didn't replace anything at all, do nothing.
if( iOffset == 0 )
return;
// Append the rest of the string.
sRet.append( sText, iOffset, sRet.npos );
break;
}
// Append the text between iOffset and iStart.
sRet.append( sText, iOffset, iStart-iOffset );
iOffset += iStart-iOffset;
// Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2.
size_t iEnd = sText.find_first_of( "&;", iStart+1 );
if( iEnd == sText.npos || sText[iEnd] == '&' )
{
// & with no matching ;, or two & in a row. Append the & and continue.
sRet.append( sText, iStart, 1 );
++iOffset;
continue;
}
RString sElement = sText.substr( iStart+1, iEnd-iStart-1 );
sElement.MakeLower();
std::map<RString, RString>::const_iterator it = m.find( sElement );
if( it == m.end() )
{
sRet.append( sText, iStart, iEnd-iStart+1 );
iOffset = iEnd + 1;
continue;
}
const RString &sTo = it->second;
sRet.append( sTo );
iOffset = iEnd + 1;
}
sText = sRet;
}
// abcd -> &a; &b; &c; &d;
void ReplaceEntityText( RString &sText, const std::map<char, RString> &m )
{
RString sFind;
for (const std::pair<const char, RString> &c : m)
sFind.append( 1, c.first );
RString sRet;
size_t iOffset = 0;
while( iOffset != sText.size() )
{
size_t iStart = sText.find_first_of( sFind, iOffset );
if( iStart == sText.npos )
{
// Optimization: if we didn't replace anything at all, do nothing.
if( iOffset == 0 )
return;
// Append the rest of the string.
sRet.append( sText, iOffset, sRet.npos );
break;
}
// Append the text between iOffset and iStart.
sRet.append( sText, iOffset, iStart-iOffset );
iOffset += iStart-iOffset;
char sElement = sText[iStart];
std::map<char, RString>::const_iterator it = m.find( sElement );
ASSERT( it != m.end() );
const RString &sTo = it->second;
sRet.append( 1, '&' );
sRet.append( sTo );
sRet.append( 1, ';' );
++iOffset;
}
sText = sRet;
}
// Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters.
void Replace_Unicode_Markers( RString &sText )
{
unsigned iStart = 0;
while( iStart < sText.size() )
{
// Look for &#digits;
bool bHex = false;
size_t iPos = sText.find( "&#", iStart );
if( iPos == sText.npos )
{
bHex = true;
iPos = sText.find( "&x", iStart );
}
if( iPos == sText.npos )
break;
iStart = iPos+1;
unsigned p = iPos;
p += 2;
// Found &# or &x. Is it followed by digits and a semicolon?
if( p >= sText.size() )
continue;
int iNumDigits = 0;
while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) )
{
p++;
iNumDigits++;
}
if( !iNumDigits )
continue; // must have at least one digit
if( p >= sText.size() || sText[p] != ';' )
continue;
p++;
unsigned int iNum;
sscanf( sText.c_str()+iPos, bHex ? "&x%x;" : "&#%u;", &iNum );
if( iNum > 0xFFFF )
iNum = INVALID_CHAR;
sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) );
}
}
// Form a string to identify a wchar_t with ASCII.
RString WcharDisplayText( wchar_t c )
{
RString sChr;
sChr = ssprintf( "U+%4.4x", c );
if( c < 128 )
sChr += ssprintf( " ('%c')", char(c) );
return sChr;
}
/* Return the last named component of dir:
* a/b/c -> c
* a/b/c/ -> c
*/
RString Basename( const RString &sDir )
{
size_t iEnd = sDir.find_last_not_of( "/\\" );
if( iEnd == sDir.npos )
return RString();
size_t iStart = sDir.find_last_of( "/\\", iEnd );
if( iStart == sDir.npos )
iStart = 0;
else
++iStart;
return sDir.substr( iStart, iEnd-iStart+1 );
}
/* Return all but the last named component of dir:
*
* a/b/c -> a/b/
* a/b/c/ -> a/b/
* c/ -> ./
* /foo -> /
* / -> /
*/
RString Dirname( const RString &dir )
{
// Special case: "/" -> "/".
if( dir.size() == 1 && dir[0] == '/' )
return "/";
int pos = dir.size()-1;
// Skip trailing slashes.
while( pos >= 0 && dir[pos] == '/' )
--pos;
// Skip the last component.
while( pos >= 0 && dir[pos] != '/' )
--pos;
if( pos < 0 )
return "./";
return dir.substr(0, pos+1);
}
RString Capitalize( const RString &s )
{
if( s.empty() )
return RString();
char *buf = const_cast<char *>(s.c_str());
UnicodeDoUpper( buf, s.size(), g_UpperCase );
return buf;
}
unsigned char g_UpperCase[256] =
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
};
unsigned char g_LowerCase[256] =
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF,
};
void FixSlashesInPlace( RString &sPath )
{
for( unsigned i = 0; i < sPath.size(); ++i )
if( sPath[i] == '\\' )
sPath[i] = '/';
}
/* Keep trailing slashes, since that can be used to illustrate that a path always
* represents a directory.
*
* foo/bar -> foo/bar
* foo/bar/ -> foo/bar/
* foo///bar/// -> foo/bar/
* foo/bar/./baz -> foo/bar/baz
* foo/bar/../baz -> foo/baz
* ../foo -> ../foo
* ../../foo -> ../../foo
* ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot)
* ./ -> ./
* ./// -> ./
*/
void CollapsePath( RString &sPath, bool bRemoveLeadingDot )
{
RString sOut;
sOut.reserve( sPath.size() );
size_t iPos = 0;
size_t iNext;
for( ; iPos < sPath.size(); iPos = iNext )
{
// Find the next slash.
iNext = sPath.find( '/', iPos );
if( iNext == RString::npos )
iNext = sPath.size();
else
++iNext;
/* Strip extra slashes, but don't remove slashes from the beginning of the string. */
if( iNext - iPos == 1 && sPath[iPos] == '/' )
{
if( !sOut.empty() )
continue;
}
// If this is a dot, skip it.
if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' )
{
if( bRemoveLeadingDot || !sOut.empty() )
continue;
}
// If this is two dots,
if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' )
{
/* If this is the first path element (nothing to delete),
* or all we have is a slash, leave it. */
if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') )
{
sOut.append( sPath, iPos, iNext-iPos );
continue;
}
// Search backwards for the previous path element.
size_t iPrev = sOut.rfind( '/', sOut.size()-2 );
if( iPrev == RString::npos )
iPrev = 0;
else
++iPrev;
// If the previous element is also .., leave it.
bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' );
if( bLastIsTwoDots )
{
sOut.append( sPath, iPos, iNext-iPos );
continue;
}
sOut.erase( iPrev );
continue;
}
sOut.append( sPath, iPos, iNext-iPos );
}
sOut.swap( sPath );
}
namespace StringConversion
{
template<> bool FromString<int>( const RString &sValue, int &out )
{
if( sscanf( sValue.c_str(), "%d", &out ) == 1 )
return true;
out = 0;
return false;
}
template<> bool FromString<unsigned>( const RString &sValue, unsigned &out )
{
if( sscanf( sValue.c_str(), "%u", &out ) == 1 )
return true;
out = 0;
return false;
}
template<> bool FromString<float>( const RString &sValue, float &out )
{
const char *endptr = sValue.data() + sValue.size();
out = strtof( sValue.c_str(), (char **) &endptr );
if( endptr != sValue.data() && std::isfinite( out ) )
return true;
out = 0;
return false;
}
template<> bool FromString<bool>( const RString &sValue, bool &out )
{
if( sValue.size() == 0 )
return false;
out = StringToInt(sValue) != 0;
return true;
}
template<> RString ToString<int>( const int &value )
{
return ssprintf( "%i", value );
}
template<> RString ToString<unsigned>( const unsigned &value )
{
return ssprintf( "%u", value );
}
template<> RString ToString<float>( const float &value )
{
return ssprintf( "%f", value );
}
template<> RString ToString<bool>( const bool &value )
{
return ssprintf( "%i", value );
}
}
bool FileCopy( const RString &sSrcFile, const RString &sDstFile )
{
if( !sSrcFile.CompareNoCase(sDstFile) )
{
LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() );
return false;
}
RageFile in;
if( !in.Open(sSrcFile, RageFile::READ) )
return false;
RageFile out;
if( !out.Open(sDstFile, RageFile::WRITE) )
return false;
RString sError;
if( !FileCopy(in, out, sError) )
{
LOG->Warn( "FileCopy(%s,%s): %s",
sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() );
return false;
}
return true;
}
bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError )
{
for(;;)
{
RString data;
if( in.Read(data, 1024*32) == -1 )
{
sError = ssprintf( "read error: %s", in.GetError().c_str() );
if( bReadError != nullptr )
{
*bReadError = true;
}
return false;
}
if( data.empty() )
{
break;
}
int i = out.Write(data);
if( i == -1 )
{
sError = ssprintf( "write error: %s", out.GetError().c_str() );
if( bReadError != nullptr )
{
*bReadError = false;
}
return false;
}
}
if( out.Flush() == -1 )
{
sError = ssprintf( "write error: %s", out.GetError().c_str() );
if( bReadError != nullptr )
{
*bReadError = false;
}
return false;
}
return true;
}
LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) )
LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) )
LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) )
LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) )
LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) )
LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) )
LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) )
LuaFunction( Basename, Basename( SArg(1) ) )
static RString MakeLower( RString s ) { s.MakeLower(); return s; }
LuaFunction( Lowercase, MakeLower( SArg(1) ) )
static RString MakeUpper( RString s ) { s.MakeUpper(); return s; }
LuaFunction( Uppercase, MakeUpper( SArg(1) ) )
LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() )
LuaFunction( URLEncode, URLEncode( SArg(1) ) );
LuaFunction( PrettyPercent, PrettyPercent( FArg(1), FArg(2) ) );
//LuaFunction( IsHexVal, IsHexVal( SArg(1) ) );
LuaFunction( lerp, lerp(FArg(1), FArg(2), FArg(3)) );
int LuaFunc_BinaryToHex(lua_State* L);
int LuaFunc_BinaryToHex(lua_State* L)
{
size_t l;
const char *s = luaL_checklstring(L, 1, &l);
RString hex = BinaryToHex(s, l);
LuaHelpers::Push(L, hex);
return 1;
}
LUAFUNC_REGISTER_COMMON(BinaryToHex);
int LuaFunc_commify(lua_State* L);
int LuaFunc_commify(lua_State* L)
{
RString num= SArg(1);
RString sep= ",";
RString dot= ".";
if(!lua_isnoneornil(L, 2))
{
sep= lua_tostring(L, 2);
}
if(!lua_isnoneornil(L, 3))
{
dot= lua_tostring(L, 3);
}
RString ret= Commify(num, sep, dot);
LuaHelpers::Push(L, ret);
return 1;
}
LUAFUNC_REGISTER_COMMON(commify);
int LuaFunc_JsonEncode(lua_State* L);
int LuaFunc_JsonEncode(lua_State* L)
{
int argc = lua_gettop(L);
bool minified = false;
if (argc < 1 || argc > 2)
{
luaL_error(L, "JsonEncode must be called with one or two arguments");
}
if (argc == 2)
{
minified = lua_toboolean(L, 2);
}
std::function<Json::Value(int)> convert = [&L, &convert](int index) -> Json::Value
{
switch (lua_type(L, index))
{
case LUA_TNIL:
return Json::Value(Json::nullValue);
case LUA_TBOOLEAN:
return Json::Value(static_cast<bool>(lua_toboolean(L, index)));
case LUA_TNUMBER: {
double val = lua_tonumber(L, index);
if (val == static_cast<Json::UInt>(val))
{
return Json::Value(static_cast<Json::UInt>(val));
}
else if (val == static_cast<Json::Int>(val))
{
return Json::Value(static_cast<Json::Int>(val));
}
return Json::Value(val);
}
case LUA_TSTRING: {
size_t len;
const char *s = lua_tolstring(L, index, &len);
return Json::Value(std::string(s, len));
}
case LUA_TTABLE: {
// if the index is relative to the top of the stack,
// then calculate the absolute index, so we have a
// stable reference
if (index < 0)
{
index = lua_gettop(L) + index + 1;
}
size_t len = lua_objlen(L, index);
if (len > 0)
{
// array
Json::Value array(Json::arrayValue);
array.resize(len);
for (unsigned int i = 0; i < len; i++)
{
lua_rawgeti(L, index, i + 1);
array[i] = convert(-1);
lua_pop(L, 1);
}
return array;
}
else
{
// object
Json::Value obj(Json::objectValue);
lua_pushnil(L);
while (lua_next(L, index) != 0)
{
if (!lua_isstring(L, -2))
{
luaL_error(L, "object keys must be strings");
}
size_t keylen;
const char *key = lua_tolstring(L, -2, &keylen);
obj[std::string(key, keylen)] = convert(-1);
lua_pop(L, 1);
}
if (obj.size() < 1)
{
return Json::Value(Json::arrayValue);
}
return obj;
}
}
}
int tp = lua_type(L, index);
luaL_error(L, "%s is not JSON serializable", lua_typename(L, tp));
return Json::Value(Json::nullValue); /* not reached */
};
Json::Value root = convert(1);
std::string data;
if(!minified)
{
Json::StyledWriter writer;
data = writer.write(root);
}
else
{
Json::FastWriter writer;
data = writer.write(root);
}
lua_pushlstring(L, data.c_str(), data.length());
return 1;
}
LUAFUNC_REGISTER_COMMON(JsonEncode);
int LuaFunc_JsonDecode(lua_State* L);
int LuaFunc_JsonDecode(lua_State* L)
{
int argc = lua_gettop(L);
if (argc < 1)
{
luaL_error(L, "JsonDecode requires an argument");
}
size_t datalen;
const char *data = lua_tolstring(L, 1, &datalen);
Json::Reader reader;
Json::Value root;
bool ok = reader.parse(std::string(data, datalen), root, true);
if (!ok)
{
std::string error = reader.getFormattedErrorMessages();
luaL_error(L, "failed to parse JSON: %s", error.c_str());
}
std::function<void(const Json::Value&)> convert = [&L, &convert](const Json::Value& val)
{
if (val.isNull())
{
lua_pushnil(L);
}
else if (val.isInt() || val.isUInt() || val.isDouble())
{
lua_pushnumber(L, val.asDouble());
}
else if (val.isString())
{
std::string s = val.asString();
lua_pushlstring(L, s.c_str(), s.length());
}
else if (val.isBool())
{
lua_pushboolean(L, val.asBool());
}
else if (val.isArray())
{
lua_createtable(L, val.size(), 0);
for (unsigned int i = 0; i < val.size(); i++)
{
convert(val[i]);
lua_rawseti(L, -2, i + 1);
}
}
else if (val.isObject())
{
lua_createtable(L, 0, val.size());
for (const std::string& member : val.getMemberNames())
{
lua_pushlstring(L, member.c_str(), member.length());
convert(val[member]);
lua_rawset(L, -3);
}
}
else
{
luaL_error(L, "failed to parse JSON: invalid type");
}
};
convert(root);
return 1;
}
LUAFUNC_REGISTER_COMMON(JsonDecode);
void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index);
void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index)
{
#define TONUMBER_NICE(dest, num_name, index) \
if(!lua_isnumber(L, index)) \
{ \
luaL_error(L, "approach: " #num_name " for approach %d is not a number.", process_index); \
} \
dest= lua_tonumber(L, index);
float val= 0;
float goal= 0;
float speed= 0;
TONUMBER_NICE(val, current, valind);
TONUMBER_NICE(goal, goal, goalind);
TONUMBER_NICE(speed, speed, speedind);
#undef TONUMBER_NICE
if(speed < 0)
{
luaL_error(L, "approach: speed %d is negative.", process_index);
}
fapproach(val, goal, speed*mult);
lua_pushnumber(L, val);
}
int LuaFunc_approach(lua_State* L);
int LuaFunc_approach(lua_State* L)
{
// Args: current, goal, speed
// Returns: new_current
luafunc_approach_internal(L, 1, 2, 3, 1.0f, 1);
return 1;
}
LUAFUNC_REGISTER_COMMON(approach);
int LuaFunc_multiapproach(lua_State* L);
int LuaFunc_multiapproach(lua_State* L)
{
// Args: {currents}, {goals}, {speeds}, speed_multiplier
// speed_multiplier is optional, and is intended to be the delta time for
// the frame, so that this can be used every frame and have the current
// approach the goal at a framerate independent speed.
// Returns: {currents}
// Modifies the values in {currents} in place.
if(lua_gettop(L) < 3)
{
luaL_error(L, "multiapproach: A table of current values, a table of goal values, and a table of speeds must be passed.");
}
size_t currents_len= lua_objlen(L, 1);
size_t goals_len= lua_objlen(L, 2);
size_t speeds_len= lua_objlen(L, 3);
float mult= 1.0f;
if(lua_isnumber(L, 4))
{
mult= lua_tonumber(L, 4);
}
if(currents_len != goals_len || currents_len != speeds_len)
{
luaL_error(L, "multiapproach: There must be the same number of current values, goal values, and speeds.");
}
if(!lua_istable(L, 1) || !lua_istable(L, 2) || !lua_istable(L, 3))
{
luaL_error(L, "multiapproach: current, goal, and speed must all be tables.");
}
for(size_t i= 1; i <= currents_len; ++i)
{
lua_rawgeti(L, 1, i);
lua_rawgeti(L, 2, i);
lua_rawgeti(L, 3, i);
luafunc_approach_internal(L, -3, -2, -1, mult, i);
lua_rawseti(L, 1, i);
lua_pop(L, 3);
}
lua_pushvalue(L, 1);
return 1;
}
LUAFUNC_REGISTER_COMMON(multiapproach);
int LuaFunc_get_music_file_length(lua_State* L);
int LuaFunc_get_music_file_length(lua_State* L)
{
// Args: file_path
// Returns: The length of the music in seconds.
RString path= SArg(1);
RString error;
RageSoundReader* sample= RageSoundReader_FileReader::OpenFile(path, error);
if(sample == nullptr)
{
luaL_error(L, "The music file '%s' does not exist.", path.c_str());
}
lua_pushnumber(L, sample->GetLength() / 1000.0f);
return 1;
}
LUAFUNC_REGISTER_COMMON(get_music_file_length);
/* stubs for deprecated network functions */
LuaFunction(CloseConnection, false);
LuaFunction(ConnectToServer, false);
LuaFunction(GetServerName, std::string(""));
LuaFunction(IsNetConnected, false);
LuaFunction(IsNetSMOnline, false);
LuaFunction(IsSMOnlineLoggedIn, false);
LuaFunction(ReportStyle, false);
/*
* Copyright (c) 2001-2005 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/