namespace UniLua { using StringBuilder = System.Text.StringBuilder; using Char = System.Char; using Convert = System.Convert; internal static class LuaStrLib { public const string LIB_NAME = "string"; private const int CAP_UNFINISHED = -1; private const int CAP_POSITION = -2; private const int LUA_MAXCAPTURES = 32; private const char L_ESC = '%'; private const string FLAGS = "-+ #0"; private static readonly char[] SPECIALS; static LuaStrLib() { SPECIALS = "^$*+?.([%-".ToCharArray(); } public static int OpenLib( ILuaState lua ) { NameFuncPair[] define = new NameFuncPair[] { new NameFuncPair( "byte", Str_Byte ), new NameFuncPair( "char", Str_Char ), //new NameFuncPair( "dump", Str_Dump ), new NameFuncPair( "find", Str_Find ), new NameFuncPair( "format", Str_Format ), new NameFuncPair( "gmatch", Str_Gmatch ), new NameFuncPair( "gsub", Str_Gsub ), new NameFuncPair( "len", Str_Len ), new NameFuncPair( "lower", Str_Lower ), new NameFuncPair( "match", Str_Match ), new NameFuncPair( "rep", Str_Rep ), new NameFuncPair( "reverse", Str_Reverse ), new NameFuncPair( "sub", Str_Sub ), new NameFuncPair( "upper", Str_Upper ), }; lua.L_NewLib( define ); CreateMetaTable( lua ); return 1; } private static void CreateMetaTable( ILuaState lua ) { lua.CreateTable(0, 1); // table to be metatable for strings lua.PushString( "" ); // dummy string lua.PushValue( -2 ); // copy table lua.SetMetaTable( -2 ); // set table as metatable for strings lua.Pop( 1 ); lua.PushValue( -2 ); // get string library lua.SetField( -2, "__index" ); // metatable.__index = string lua.Pop( 1 ); // pop metatable } private static int PosRelative( int pos, int len ) { if( pos >= 0 ) return pos; else if( 0 - pos > len ) return 0; else return len - (-pos) + 1; } private static int Str_Byte( ILuaState lua ) { string s = lua.L_CheckString(1); int posi = PosRelative( lua.L_OptInt(2, 1), s.Length ); int pose = PosRelative( lua.L_OptInt(3, posi), s.Length ); if( posi < 1 ) posi = 1; if( pose > s.Length ) pose = s.Length; if( posi > pose ) return 0; // empty interval; return no values int n = pose - posi + 1; if( posi + n <= pose) // overflow? return lua.L_Error( "string slice too long" ); lua.L_CheckStack(n, "string slice too long"); for( int i=0; i= ms.PatternEnd - 1 ) lua.L_Error( "malformed pattern (missing arguments to '%b')" ); if( ms.Src[s] != ms.Pattern[p] ) return -1; else { char b = ms.Pattern[p]; char e = ms.Pattern[p+1]; int count = 1; while( ++s < ms.SrcEnd ) { if( ms.Src[s] == e ) { if( --count == 0 ) return s+1; } else if( ms.Src[s] == b ) count++; } } return -1; //string ends out of balance } private static int MaxExpand( MatchState ms, int s, int p, int ep ) { int i = 0; // counts maximum expand for item while( (s+i) < ms.SrcEnd && SingleMatch( ms, ms.Src[s+i], p, ep ) ) i++; // keeps trying to match with the maximum repetitions while( i >= 0 ) { int res = Match( ms, (s+i), (ep+1) ); if( res >= 0 ) return res; i--; // else didn't match; reduce 1 repetition to try again } return -1; } private static int MinExpand( MatchState ms, int s, int p, int ep ) { for(;;) { int res = Match( ms, s, ep+1 ); if( res >= 0 ) return res; else if( s < ms.SrcEnd && SingleMatch( ms, ms.Src[s], p, ep ) ) s++; // try with one more repetition else return -1; } } private static int CaptureToClose( MatchState ms ) { var lua = ms.Lua; int level=ms.Level; for( level--; level>=0; level-- ) { if( ms.Capture[level].Len == CAP_UNFINISHED ) return level; } return lua.L_Error( "invalid pattern capture" ); } private static int StartCapture( MatchState ms, int s, int p, int what ) { var lua = ms.Lua; int level = ms.Level; if( level >= LUA_MAXCAPTURES ) lua.L_Error( "too many captures" ); ms.Capture[level].Init = s; ms.Capture[level].Len = what; ms.Level = level + 1; int res = Match( ms, s, p ); if( res == -1 ) // match failed? ms.Level--; return res; } private static int EndCapture( MatchState ms, int s, int p ) { int l = CaptureToClose( ms ); ms.Capture[l].Len = s - ms.Capture[l].Init; // close capture int res = Match( ms, s, p ); if( res == -1 ) // match failed? ms.Capture[l].Len = CAP_UNFINISHED; // undo capture return res; } private static int CheckCapture( MatchState ms, char l ) { var lua = ms.Lua; int i = (int)(l - '1'); if( i < 0 || i >= ms.Level || ms.Capture[i].Len == CAP_UNFINISHED ) return lua.L_Error( "invalid capture index %d", i+1 ); return i; } private static int MatchCapture( MatchState ms, int s, char l ) { int i = CheckCapture( ms, l ); int len = ms.Capture[i].Len; if( ms.SrcEnd - s >= len && string.Compare(ms.Src, ms.Capture[i].Init, ms.Src, s, len) == 0 ) return s + len; else return -1; } private static int Match( MatchState ms, int s, int p ) { var lua = ms.Lua; init: // using goto's to optimize tail recursion if( p == ms.PatternEnd ) return s; switch( ms.Pattern[p] ) { case '(': // start capture { if( ms.Pattern[p+1] == ')' ) // position capture? return StartCapture( ms, s, p+2, CAP_POSITION ); else return StartCapture( ms, s, p+1, CAP_UNFINISHED ); } case ')': // end capture { return EndCapture( ms, s, p+1 ); } case '$': { if( p+1 == ms.PatternEnd ) // is the `$' the last char in pattern? return (s == ms.SrcEnd) ? s : -1; // check end of string else goto default; } case L_ESC: // escaped sequences not in the format class[*+?-]? { switch( ms.Pattern[p+1] ) { case 'b': // balanced string? { s = MatchBalance( ms, s, p+2 ); if( s == -1 ) return -1; p += 4; goto init; // else return match(ms, s, p+4); } case 'f': // frontier? { p += 2; if( ms.Pattern[p] != '[' ) lua.L_Error( "missing '[' after '%f' in pattern" ); int ep = ClassEnd( ms, p ); //points to what is next char previous = (s == ms.SrcInit) ? '\0' : ms.Src[s-1]; if( MatchBreaketClass(ms, previous, p, ep-1) || !MatchBreaketClass(ms, ms.Src[s], p, ep-1) ) return -1; p = ep; goto init; // else return match( ms, s, ep ); } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // capture results (%0-%9)? { s = MatchCapture( ms, s, ms.Pattern[p+1] ); if( s == -1 ) return -1; p+=2; goto init; // else return match(ms, s, p+2); } default: goto dflt; } } default: dflt: // pattern class plus optional suffix { int ep = ClassEnd( ms, p ); bool m = s < ms.SrcEnd && SingleMatch(ms, ms.Src[s], p, ep); if(ep < ms.PatternEnd){ switch(ms.Pattern[ep]) //fix gmatch bug patten is [^a] { case '?': // optional { if( m ) { int res = Match(ms, s+1, ep+1); if( res != -1 ) return res; } p=ep+1; goto init; // else return match(ms, s, ep+1); } case '*': // 0 or more repetitions { return MaxExpand(ms, s, p, ep); } case '+': // 1 or more repetitions { return (m ? MaxExpand(ms, s+1, p, ep) : -1); } case '-': // 0 or more repetitions (minimum) { return MinExpand(ms, s, p, ep); } } } if(!m) return -1; s++; p=ep; goto init; // else return match(ms, s+1, ep); } } } private static void PushOneCapture ( MatchState ms , int i , int start , int end ) { var lua = ms.Lua; if( i >= ms.Level ) { if( i == 0 ) // ms.Level == 0, too lua.PushString( ms.Src.Substring( start, end-start ) ); else lua.L_Error( "invalid capture index" ); } else { int l = ms.Capture[i].Len; if( l == CAP_UNFINISHED ) lua.L_Error( "unfinished capture" ); if( l == CAP_POSITION ) lua.PushInteger( ms.Capture[i].Init - ms.SrcInit + 1 ); else lua.PushString( ms.Src.Substring( ms.Capture[i].Init, l ) ); } } private static int PushCaptures(ILuaState lua, MatchState ms, int spos, int epos ) { int nLevels = (ms.Level == 0 && spos >= 0) ? 1 : ms.Level; lua.L_CheckStack(nLevels, "too many captures"); for( int i=0; i s.Length + 1 ) // start after string's end? { lua.PushNil(); // cannot find anything return 1; } // explicit request or no special characters? if( find && (lua.ToBoolean(4) || NoSpecials(p)) ) { // do a plain search int pos = s.IndexOf( p, init-1 ); if( pos >= 0 ) { lua.PushInteger( pos+1 ); lua.PushInteger( pos+p.Length ); return 2; } } else { int s1 = init-1; int ppos = 0; bool anchor = p[ppos] == '^'; if( anchor ) ppos++; // skip anchor character MatchState ms = new MatchState(); ms.Lua = lua; ms.Src = s; ms.SrcInit = s1; ms.SrcEnd = s.Length; ms.Pattern = p; ms.PatternEnd = p.Length; do { ms.Level = 0; int res = Match( ms, s1, ppos ); if( res != -1 ) { if(find) { lua.PushInteger( s1+1 ); // start lua.PushInteger( res ); // end return PushCaptures(lua, ms, -1, 0) + 2; } else return PushCaptures(lua, ms, s1, res); } } while( s1++ < ms.SrcEnd && !anchor ); } lua.PushNil(); // not found return 1; } private static int Str_Find( ILuaState lua ) { return StrFindAux( lua, true ); } private static int ScanFormat( ILuaState lua, string format, int s, out string form ) { int p = s; // skip flags while( p < format.Length && format[p] != '\0' && FLAGS.IndexOf(format[p]) != -1 ) p++; if( p - s > FLAGS.Length ) lua.L_Error( "invalid format (repeat flags)" ); if( Char.IsDigit( format[p] ) ) p++; // skip width if( Char.IsDigit( format[p] ) ) p++; // (2 digits at most) if( format[p] == '.' ) { p++; if( Char.IsDigit( format[p] ) ) p++; // skip precision if( Char.IsDigit( format[p] ) ) p++; // (2 digits at most) } if( Char.IsDigit( format[p] ) ) lua.L_Error( "invalid format (width of precision too long)" ); form = "%" + format.Substring( s, (p-s+1) ); return p; } private static int Str_Format( ILuaState lua ) { int top = lua.GetTop(); StringBuilder sb = new StringBuilder(); int arg = 1; string format = lua.L_CheckString( arg ); int s = 0; int e = format.Length; while(s < e) { if( format[s] != L_ESC ) { sb.Append( format[s++] ); continue; } if( format[++s] == L_ESC ) { sb.Append( format[s++] ); continue; } // else format item if( ++arg > top ) lua.L_ArgError( arg, "no value" ); string form; s = ScanFormat( lua, format, s, out form ); switch( format[s++] ) // TODO: properly handle form { case 'c': { sb.Append( (char)lua.L_CheckInteger(arg) ); break; } case 'd': case 'i': { int n = lua.L_CheckInteger(arg); sb.Append( n.ToString() ); break; } case 'u': { int n = lua.L_CheckInteger(arg); lua.L_ArgCheck( n >= 0, arg, "not a non-negative number is proper range" ); sb.Append( n.ToString() ); break; } case 'o': { int n = lua.L_CheckInteger(arg); lua.L_ArgCheck( n >= 0, arg, "not a non-negative number is proper range" ); sb.Append( Convert.ToString(n, 8) ); break; } case 'x': { int n = lua.L_CheckInteger(arg); lua.L_ArgCheck( n >= 0, arg, "not a non-negative number is proper range" ); // sb.Append( string.Format("{0:x}", n) ); sb.AppendFormat("{0:x}", n); break; } case 'X': { int n = lua.L_CheckInteger(arg); lua.L_ArgCheck( n >= 0, arg, "not a non-negative number is proper range" ); // sb.Append( string.Format("{0:X}", n) ); sb.AppendFormat("{0:X}", n); break; } case 'e': case 'E': { sb.AppendFormat("{0:E}", lua.L_CheckNumber(arg)); break; } case 'f': { sb.AppendFormat("{0:F}", lua.L_CheckNumber(arg)); break; } #if LUA_USE_AFORMAT case 'a': case 'A': #endif case 'g': case 'G': { sb.AppendFormat("{0:G}", lua.L_CheckNumber(arg)); break; } case 'q': { AddQuoted(lua, sb, arg); break; } case 's': { sb.Append(lua.L_CheckString(arg)); break; } default: // also treat cases `pnLlh' { return lua.L_Error( "invalid option '{0}' to 'format'", format[s-1] ); } } } lua.PushString( sb.ToString() ); return 1; } private static void AddQuoted(ILuaState lua, StringBuilder sb, int arg) { var s = lua.L_CheckString(arg); sb.Append('"'); for(var i=0; i=s.Length || !Char.IsDigit(s[i+1])) { sb.AppendFormat("\\{0:D}", (int)c); } else { sb.AppendFormat("\\{0:D3}", (int)c); } } else { sb.Append(c); } } sb.Append('"'); } private static int GmatchAux( ILuaState lua ) { MatchState ms = new MatchState(); string src = lua.ToString( lua.UpvalueIndex(1) ); string pattern = lua.ToString( lua.UpvalueIndex(2) ); ms.Lua = lua; ms.Src = src; ms.SrcInit = 0; ms.SrcEnd = src.Length; ms.Pattern = pattern; ms.PatternEnd = pattern.Length; for( int s = lua.ToInteger( lua.UpvalueIndex(3) ) ; s <= ms.SrcEnd ; s++ ) { ms.Level = 0; int e = Match( ms, s, 0 ); if( e != -1 ) { int newStart = (e == 0) ? e+1: e; lua.PushInteger( newStart ); lua.Replace( lua.UpvalueIndex(3) ); return PushCaptures(lua, ms, s, e); } } return 0; // not found } private static int Str_Gmatch( ILuaState lua ) { lua.L_CheckString(1); lua.L_CheckString(2); lua.SetTop(2); lua.PushInteger(0); lua.PushCSharpClosure( GmatchAux, 3 ); return 1; } private static void Add_S (MatchState ms, StringBuilder b, int s, int e) { string news = ms.Lua.ToString(3); for (int i = 0; i < news.Length; i++) { if (news[i] != L_ESC) b.Append(news[i]); else { i++; /* skip ESC */ if (!Char.IsDigit((news[i]))) b.Append(news[i]); else if (news[i] == '0') b.Append(ms.Src.Substring(s, (e - s))); else { PushOneCapture(ms, news[i] - '1', s, e); b.Append(ms.Lua.ToString(-1)); /* add capture to accumulated result */ } } } } private static void Add_Value (MatchState ms, StringBuilder b, int s, int e) { ILuaState lua = ms.Lua; switch (lua.Type(3)) { case LuaType.LUA_TNUMBER: case LuaType.LUA_TSTRING: { Add_S(ms, b, s, e); return; } case LuaType.LUA_TFUNCTION: { int n; lua.PushValue(3); n = PushCaptures(lua, ms, s, e); lua.Call(n, 1); break; } case LuaType.LUA_TTABLE: { PushOneCapture(ms, 0, s, e); lua.GetTable(3); break; } } if (lua.ToBoolean(-1)==false) { /* nil or false? */ lua.Pop(1); b.Append(ms.Src.Substring(s, (e - s))); /* keep original text */ } else if (!lua.IsString(-1)) lua.L_Error("invalid replacement value (a %s)", lua.L_TypeName(-1)); else b.Append(lua.ToString(-1)); } private static int Str_Gsub( ILuaState lua ) { string src = lua.L_CheckString(1); int srcl = src.Length; string p = lua.L_CheckString(2); LuaType tr = lua.Type(3); int max_s = lua.L_OptInt(4, srcl + 1); int anchor = 0; if (p[0] == '^') { p = p.Substring(1); anchor = 1; } int n = 0; MatchState ms = new MatchState(); StringBuilder b = new StringBuilder(srcl); lua.L_ArgCheck(tr == LuaType.LUA_TNUMBER || tr == LuaType.LUA_TSTRING || tr == LuaType.LUA_TFUNCTION || tr == LuaType.LUA_TTABLE, 3, "string/function/table expected"); ms.Lua = lua; ms.Src = src; ms.SrcInit = 0; ms.SrcEnd = srcl; ms.Pattern = p; ms.PatternEnd = p.Length; int s = 0; while (n < max_s) { ms.Level = 0; int e = Match(ms, s, 0); if (e != -1) { n++; Add_Value(ms, b, s, e); } if ((e != -1) && e > s) /* non empty match? */ s = e; /* skip it */ else if (s < ms.SrcEnd) { char c = src[s]; ++s; b.Append(c); } else break; if (anchor != 0) break; } b.Append(src.Substring(s, ms.SrcEnd - s)); lua.PushString(b.ToString()); lua.PushInteger(n); /* number of substitutions */ return 2; } private static int Str_Len( ILuaState lua ) { string s = lua.L_CheckString(1); lua.PushInteger( s.Length ); return 1; } private static int Str_Lower( ILuaState lua ) { string s = lua.L_CheckString(1); lua.PushString( s.ToLower() ); return 1; } private static int Str_Match( ILuaState lua ) { return StrFindAux( lua, false ); } private static int Str_Rep(ILuaState lua) { string s = lua.L_CheckString(1); int n = lua.L_CheckInteger(2); StringBuilder sb = new StringBuilder(s.Length * n); for (int i = 0; i < n; ++i) sb.Append(s); lua.PushString(sb.ToString()); return 1; } private static int Str_Reverse( ILuaState lua ) { string s = lua.L_CheckString(1); StringBuilder sb = new StringBuilder(s.Length); for( int i=s.Length-1; i>=0; --i ) sb.Append( s[i] ); lua.PushString( sb.ToString() ); return 1; } private static int Str_Sub( ILuaState lua ) { string s = lua.L_CheckString(1); int start = PosRelative( lua.L_CheckInteger(2), s.Length ); int end = PosRelative( lua.L_OptInt(3, -1), s.Length ); if( start < 1 ) start = 1; if( end > s.Length ) end = s.Length; if( start <= end ) lua.PushString( s.Substring(start-1, end-start+1) ); else lua.PushString( "" ); return 1; } private static int Str_Upper( ILuaState lua ) { string s = lua.L_CheckString(1); lua.PushString( s.ToUpper() ); return 1; } } }