/* ** $Id: lstrlib.c,v 1.132.1.4 2008/07/11 17:27:21 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Diagnostics; namespace KopiLua { using ptrdiff_t = System.Int32; using lua_Integer = System.Int32; using LUA_INTFRM_T = System.Int64; using UNSIGNED_LUA_INTFRM_T = System.UInt64; public partial class Lua { private static int str_len (lua_State L) { uint l; luaL_checklstring(L, 1, out l); lua_pushinteger(L, (int)l); return 1; } private static ptrdiff_t posrelat (ptrdiff_t pos, uint len) { /* relative string position: negative means back from end */ if (pos < 0) pos += (ptrdiff_t)len + 1; return (pos >= 0) ? pos : 0; } private static int str_sub (lua_State L) { uint l; CharPtr s = luaL_checklstring(L, 1, out l); ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); if (start < 1) start = 1; if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; if (start <= end) lua_pushlstring(L, s+start-1, (uint)(end-start+1)); else lua_pushliteral(L, ""); return 1; } private static int str_reverse (lua_State L) { uint l; luaL_Buffer b = new luaL_Buffer(); CharPtr s = luaL_checklstring(L, 1, out l); luaL_buffinit(L, b); while ((l--) != 0) luaL_addchar(b, s[l]); luaL_pushresult(b); return 1; } private static int str_lower (lua_State L) { uint l; uint i; luaL_Buffer b = new luaL_Buffer(); CharPtr s = luaL_checklstring(L, 1, out l); luaL_buffinit(L, b); for (i=0; i 0) luaL_addlstring(b, s, l); luaL_pushresult(b); return 1; } private static int str_byte (lua_State L) { uint l; CharPtr s = luaL_checklstring(L, 1, out l); ptrdiff_t posi = posrelat(luaL_optinteger(L, 2, 1), l); ptrdiff_t pose = posrelat(luaL_optinteger(L, 3, posi), l); int n, i; if (posi <= 0) posi = 1; if ((uint)pose > l) pose = (int)l; if (posi > pose) return 0; /* empty interval; return no values */ n = (int)(pose - posi + 1); if (posi + n <= pose) /* overflow? */ luaL_error(L, "string slice too long"); luaL_checkstack(L, n, "string slice too long"); for (i=0; i= ms.level || ms.capture[l].len == CAP_UNFINISHED) return luaL_error(ms.L, "invalid capture index"); return l; } private static int capture_to_close (MatchState ms) { int level = ms.level; for (level--; level>=0; level--) if (ms.capture[level].len == CAP_UNFINISHED) return level; return luaL_error(ms.L, "invalid pattern capture"); } private static CharPtr classend (MatchState ms, CharPtr p) { p = new CharPtr(p); char c = p[0]; p = p.next(); switch (c) { case L_ESC: { if (p[0] == '\0') luaL_error(ms.L, "malformed pattern (ends with " + LUA_QL("%%") + ")"); return p+1; } case '[': { if (p[0] == '^') p = p.next(); do { /* look for a `]' */ if (p[0] == '\0') luaL_error(ms.L, "malformed pattern (missing " + LUA_QL("]") + ")"); c = p[0]; p = p.next(); if (c == L_ESC && p[0] != '\0') p = p.next(); /* skip escapes (e.g. `%]') */ } while (p[0] != ']'); return p+1; } default: { return p; } } } private static int match_class (int c, int cl) { bool res; switch (tolower(cl)) { case 'a' : res = isalpha(c); break; case 'c' : res = iscntrl(c); break; case 'd' : res = isdigit(c); break; case 'l' : res = islower(c); break; case 'p' : res = ispunct(c); break; case 's' : res = isspace(c); break; case 'u' : res = isupper(c); break; case 'w' : res = isalnum(c); break; case 'x' : res = isxdigit((char)c); break; case 'z' : res = (c == 0); break; default: return (cl == c) ? 1 : 0; } return (islower(cl) ? (res ? 1 : 0) : ((!res) ? 1 : 0)); } private static int matchbracketclass (int c, CharPtr p, CharPtr ec) { int sig = 1; if (p[1] == '^') { sig = 0; p = p.next(); /* skip the `^' */ } while ((p=p.next()) < ec) { if (p == L_ESC) { p = p.next(); if (match_class(c, (byte)(p[0])) != 0) return sig; } else if ((p[1] == '-') && (p + 2 < ec)) { p+=2; if ((byte)((p[-2])) <= c && (c <= (byte)p[0])) return sig; } else if ((byte)(p[0]) == c) return sig; } return (sig == 0) ? 1 : 0; } private static int singlematch (int c, CharPtr p, CharPtr ep) { switch (p[0]) { case '.': return 1; /* matches any char */ case L_ESC: return match_class(c, (byte)(p[1])); case '[': return matchbracketclass(c, p, ep-1); default: return ((byte)(p[0]) == c) ? 1 : 0; } } private static CharPtr matchbalance (MatchState ms, CharPtr s, CharPtr p) { if ((p[0] == 0) || (p[1] == 0)) luaL_error(ms.L, "unbalanced pattern"); if (s[0] != p[0]) return null; else { int b = p[0]; int e = p[1]; int cont = 1; while ((s=s.next()) < ms.src_end) { if (s[0] == e) { if (--cont == 0) return s+1; } else if (s[0] == b) cont++; } } return null; /* string ends out of balance */ } private static CharPtr max_expand (MatchState ms, CharPtr s, CharPtr p, CharPtr ep) { ptrdiff_t i = 0; /* counts maximum expand for item */ while ( (s+i < ms.src_end) && (singlematch((byte)(s[i]), p, ep) != 0) ) i++; /* keeps trying to match with the maximum repetitions */ while (i>=0) { CharPtr res = match(ms, (s+i), ep+1); if (res != null) return res; i--; /* else didn't match; reduce 1 repetition to try again */ } return null; } private static CharPtr min_expand (MatchState ms, CharPtr s, CharPtr p, CharPtr ep) { for (;;) { CharPtr res = match(ms, s, ep+1); if (res != null) return res; else if ( (s < ms.src_end) && (singlematch((byte)(s[0]), p, ep) != 0) ) s = s.next(); /* try with one more repetition */ else return null; } } private static CharPtr start_capture (MatchState ms, CharPtr s, CharPtr p, int what) { CharPtr res; int level = ms.level; if (level >= LUA_MAXCAPTURES) luaL_error(ms.L, "too many captures"); ms.capture[level].init = s; ms.capture[level].len = what; ms.level = level+1; if ((res=match(ms, s, p)) == null) /* match failed? */ ms.level--; /* undo capture */ return res; } private static CharPtr end_capture(MatchState ms, CharPtr s, CharPtr p) { int l = capture_to_close(ms); CharPtr res; ms.capture[l].len = s - ms.capture[l].init; /* close capture */ if ((res = match(ms, s, p)) == null) /* match failed? */ ms.capture[l].len = CAP_UNFINISHED; /* undo capture */ return res; } private static CharPtr match_capture(MatchState ms, CharPtr s, int l) { uint len; l = check_capture(ms, l); len = (uint)ms.capture[l].len; if ((uint)(ms.src_end-s) >= len && memcmp(ms.capture[l].init, s, len) == 0) return s+len; else return null; } private static CharPtr match (MatchState ms, CharPtr s, CharPtr p) { s = new CharPtr(s); p = new CharPtr(p); init: /* using goto's to optimize tail recursion */ switch (p[0]) { case '(': { /* start capture */ if (p[1] == ')') /* position capture? */ return start_capture(ms, s, p+2, CAP_POSITION); else return start_capture(ms, s, p+1, CAP_UNFINISHED); } case ')': { /* end capture */ return end_capture(ms, s, p+1); } case L_ESC: { switch (p[1]) { case 'b': { /* balanced string? */ s = matchbalance(ms, s, p+2); if (s == null) return null; p+=4; goto init; /* else return match(ms, s, p+4); */ } case 'f': { /* frontier? */ CharPtr ep; char previous; p += 2; if (p[0] != '[') luaL_error(ms.L, "missing " + LUA_QL("[") + " after " + LUA_QL("%%f") + " in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms.src_init) ? '\0' : s[-1]; if ((matchbracketclass((byte)(previous), p, ep-1)!=0) || (matchbracketclass((byte)(s[0]), p, ep-1)==0)) return null; p=ep; goto init; /* else return match(ms, s, ep); */ } default: { if (isdigit((byte)(p[1]))) { /* capture results (%0-%9)? */ s = match_capture(ms, s, (byte)(p[1])); if (s == null) return null; p+=2; goto init; /* else return match(ms, s, p+2) */ } //ismeretlen hiba miatt lett ide átmásolva { /* it is a pattern item */ CharPtr ep = classend(ms, p); /* points to what is next */ int m = (s l1) return null; /* avoids a negative `l1' */ else { CharPtr init; /* to search for a `*s2' inside `s1' */ l2--; /* 1st char will be checked by `memchr' */ l1 = l1-l2; /* `s2' cannot be found after that */ while (l1 > 0 && (init = memchr(s1, s2[0], l1)) != null) { init = init.next(); /* 1st char is already checked */ if (memcmp(init, s2+1, l2) == 0) return init-1; else { /* correct `l1' and `s1' to try again */ l1 -= (uint)(init-s1); s1 = init; } } return null; /* not found */ } } private static void push_onecapture (MatchState ms, int i, CharPtr s, CharPtr e) { if (i >= ms.level) { if (i == 0) /* ms.level == 0, too */ lua_pushlstring(ms.L, s, (uint)(e - s)); /* add whole match */ else luaL_error(ms.L, "invalid capture index"); } else { ptrdiff_t l = ms.capture[i].len; if (l == CAP_UNFINISHED) luaL_error(ms.L, "unfinished capture"); if (l == CAP_POSITION) lua_pushinteger(ms.L, ms.capture[i].init - ms.src_init + 1); else lua_pushlstring(ms.L, ms.capture[i].init, (uint)l); } } private static int push_captures (MatchState ms, CharPtr s, CharPtr e) { int i; int nlevels = ((ms.level == 0) && (s!=null)) ? 1 : ms.level; luaL_checkstack(ms.L, nlevels, "too many captures"); for (i = 0; i < nlevels; i++) push_onecapture(ms, i, s, e); return nlevels; /* number of strings pushed */ } private static int str_find_aux (lua_State L, int find) { uint l1, l2; CharPtr s = luaL_checklstring(L, 1, out l1); CharPtr p = luaL_checklstring(L, 2, out l2); ptrdiff_t init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1; if (init < 0) init = 0; else if ((uint)(init) > l1) init = (ptrdiff_t)l1; if ((find!=0) && ((lua_toboolean(L, 4)!=0) || /* explicit request? */ strpbrk(p, SPECIALS) == null)) { /* or no special characters? */ /* do a plain search */ CharPtr s2 = lmemfind(s+init, (uint)(l1-init), p, (uint)(l2)); if (s2 != null) { lua_pushinteger(L, s2-s+1); lua_pushinteger(L, (int)(s2-s+l2)); return 2; } } else { MatchState ms = new MatchState(); int anchor = 0; if (p[0] == '^') { p = p.next(); anchor = 1; } CharPtr s1=s+init; ms.L = L; ms.src_init = s; ms.src_end = s+l1; do { CharPtr res; ms.level = 0; if ((res=match(ms, s1, p)) != null) { if (find != 0) { lua_pushinteger(L, s1-s+1); /* start */ lua_pushinteger(L, res-s); /* end */ return push_captures(ms, null, null) + 2; } else return push_captures(ms, s1, res); } } while (((s1=s1.next()) <= ms.src_end) && (anchor==0)); } lua_pushnil(L); /* not found */ return 1; } private static int str_find (lua_State L) { return str_find_aux(L, 1); } private static int str_match (lua_State L) { return str_find_aux(L, 0); } private static int gmatch_aux (lua_State L) { MatchState ms = new MatchState(); uint ls; CharPtr s = lua_tolstring(L, lua_upvalueindex(1), out ls); CharPtr p = lua_tostring(L, lua_upvalueindex(2)); CharPtr src; ms.L = L; ms.src_init = s; ms.src_end = s+ls; for (src = s + (uint)lua_tointeger(L, lua_upvalueindex(3)); src <= ms.src_end; src = src.next()) { CharPtr e; ms.level = 0; if ((e = match(ms, src, p)) != null) { lua_Integer newstart = e-s; if (e == src) newstart++; /* empty match? go at least one position */ lua_pushinteger(L, newstart); lua_replace(L, lua_upvalueindex(3)); return push_captures(ms, src, e); } } return 0; /* not found */ } private static int gmatch (lua_State L) { luaL_checkstring(L, 1); luaL_checkstring(L, 2); lua_settop(L, 2); lua_pushinteger(L, 0); lua_pushcclosure(L, gmatch_aux, 3); return 1; } private static int gfind_nodef (lua_State L) { return luaL_error(L, LUA_QL("string.gfind") + " was renamed to " + LUA_QL("string.gmatch")); } private static void add_s (MatchState ms, luaL_Buffer b, CharPtr s, CharPtr e) { uint l, i; CharPtr news = lua_tolstring(ms.L, 3, out l); for (i = 0; i < l; i++) { if (news[i] != L_ESC) luaL_addchar(b, news[i]); else { i++; /* skip ESC */ if (!isdigit((byte)(news[i]))) luaL_addchar(b, news[i]); else if (news[i] == '0') luaL_addlstring(b, s, (uint)(e - s)); else { push_onecapture(ms, news[i] - '1', s, e); luaL_addvalue(b); /* add capture to accumulated result */ } } } } private static void add_value (MatchState ms, luaL_Buffer b, CharPtr s, CharPtr e) { lua_State L = ms.L; switch (lua_type(L, 3)) { case LUA_TNUMBER: case LUA_TSTRING: { add_s(ms, b, s, e); return; } case LUA_TFUNCTION: { int n; lua_pushvalue(L, 3); n = push_captures(ms, s, e); lua_call(L, n, 1); break; } case LUA_TTABLE: { push_onecapture(ms, 0, s, e); lua_gettable(L, 3); break; } } if (lua_toboolean(L, -1)==0) { /* nil or false? */ lua_pop(L, 1); lua_pushlstring(L, s, (uint)(e - s)); /* keep original text */ } else if (lua_isstring(L, -1)==0) luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); luaL_addvalue(b); /* add result to accumulator */ } private static int str_gsub (lua_State L) { uint srcl; CharPtr src = luaL_checklstring(L, 1, out srcl); CharPtr p = luaL_checkstring(L, 2); int tr = lua_type(L, 3); int max_s = luaL_optint(L, 4, (int)(srcl+1)); int anchor = 0; if (p[0] == '^') { p = p.next(); anchor = 1; } int n = 0; MatchState ms = new MatchState(); luaL_Buffer b = new luaL_Buffer(); luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, "string/function/table expected"); luaL_buffinit(L, b); ms.L = L; ms.src_init = src; ms.src_end = src+srcl; while (n < max_s) { CharPtr e; ms.level = 0; e = match(ms, src, p); if (e != null) { n++; add_value(ms, b, src, e); } if ((e!=null) && e>src) /* non empty match? */ src = e; /* skip it */ else if (src < ms.src_end) { char c = src[0]; src = src.next(); luaL_addchar(b, c); } else break; if (anchor != 0) break; } luaL_addlstring(b, src, (uint)(ms.src_end-src)); luaL_pushresult(b); lua_pushinteger(L, n); /* number of substitutions */ return 2; } /* }====================================================== */ /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ public const int MAX_ITEM = 512; /* valid flags in a format specification */ public const string FLAGS = "-+ #0"; /* ** maximum size of each format specification (such as '%-099.99d') ** (+10 accounts for %99.99x plus margin of error) */ public static readonly int MAX_FORMAT = (FLAGS.Length+1) + (LUA_INTFRMLEN.Length+1) + 10; private static void addquoted (lua_State L, luaL_Buffer b, int arg) { uint l; CharPtr s = luaL_checklstring(L, arg, out l); luaL_addchar(b, '"'); while ((l--) != 0) { switch (s[0]) { case '"': case '\\': case '\n': { luaL_addchar(b, '\\'); luaL_addchar(b, s[0]); break; } case '\r': { luaL_addlstring(b, "\\r", 2); break; } case '\0': { luaL_addlstring(b, "\\000", 4); break; } default: { luaL_addchar(b, s[0]); break; } } s = s.next(); } luaL_addchar(b, '"'); } private static CharPtr scanformat (lua_State L, CharPtr strfrmt, CharPtr form) { CharPtr p = strfrmt; while (p[0] != '\0' && strchr(FLAGS, p[0]) != null) p = p.next(); /* skip flags */ if ((uint)(p - strfrmt) >= (FLAGS.Length+1)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit((byte)(p[0]))) p = p.next(); /* skip width */ if (isdigit((byte)(p[0]))) p = p.next(); /* (2 digits at most) */ if (p[0] == '.') { p = p.next(); if (isdigit((byte)(p[0]))) p = p.next(); /* skip precision */ if (isdigit((byte)(p[0]))) p = p.next(); /* (2 digits at most) */ } if (isdigit((byte)(p[0]))) luaL_error(L, "invalid format (width or precision too long)"); form[0] = '%'; form = form.next(); strncpy(form, strfrmt, p - strfrmt + 1); form += p - strfrmt + 1; form[0] = '\0'; return p; } private static void addintlen (CharPtr form) { uint l = (uint)strlen(form); char spec = form[l - 1]; strcpy(form + l - 1, LUA_INTFRMLEN); form[l + (LUA_INTFRMLEN.Length + 1) - 2] = spec; form[l + (LUA_INTFRMLEN.Length + 1) - 1] = '\0'; } private static int str_format (lua_State L) { int arg = 1; uint sfl; CharPtr strfrmt = luaL_checklstring(L, arg, out sfl); CharPtr strfrmt_end = strfrmt+sfl; luaL_Buffer b = new luaL_Buffer(); luaL_buffinit(L, b); while (strfrmt < strfrmt_end) { if (strfrmt[0] != L_ESC) { luaL_addchar(b, strfrmt[0]); strfrmt = strfrmt.next(); } else if (strfrmt[1] == L_ESC) { luaL_addchar(b, strfrmt[0]); /* %% */ strfrmt = strfrmt + 2; } else { /* format item */ strfrmt = strfrmt.next(); CharPtr form = new char[MAX_FORMAT]; /* to store the format (`%...') */ CharPtr buff = new char[MAX_ITEM]; /* to store the formatted item */ arg++; strfrmt = scanformat(L, strfrmt, form); char ch = strfrmt[0]; strfrmt = strfrmt.next(); switch (ch) { case 'c': { sprintf(buff, form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addintlen(form); sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'o': case 'u': case 'x': case 'X': { addintlen(form); sprintf(buff, form, (UNSIGNED_LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'e': case 'E': case 'f': case 'g': case 'G': { sprintf(buff, form, (double)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, b, arg); continue; /* skip the 'addsize' at the end */ } case 's': { uint l; CharPtr s = luaL_checklstring(L, arg, out l); if ((strchr(form, '.') == null) && l >= 100) { /* no precision and string is too long to be formatted; keep original string */ lua_pushvalue(L, arg); luaL_addvalue(b); continue; /* skip the `addsize' at the end */ } else { sprintf(buff, form, s); break; } } default: { /* also treat cases `pnLlh' */ return luaL_error(L, "invalid option " + LUA_QL("%%%c") + " to " + LUA_QL("format"), strfrmt[-1]); } } luaL_addlstring(b, buff, (uint)strlen(buff)); } } luaL_pushresult(b); return 1; } private readonly static luaL_Reg[] strlib = { new luaL_Reg("byte", str_byte), new luaL_Reg("char", str_char), new luaL_Reg("dump", str_dump), new luaL_Reg("find", str_find), new luaL_Reg("format", str_format), new luaL_Reg("gfind", gfind_nodef), new luaL_Reg("gmatch", gmatch), new luaL_Reg("gsub", str_gsub), new luaL_Reg("len", str_len), new luaL_Reg("lower", str_lower), new luaL_Reg("match", str_match), new luaL_Reg("rep", str_rep), new luaL_Reg("reverse", str_reverse), new luaL_Reg("sub", str_sub), new luaL_Reg("upper", str_upper), new luaL_Reg(null, null) }; private static void createmetatable (lua_State L) { lua_createtable(L, 0, 1); /* create metatable for strings */ lua_pushliteral(L, ""); /* dummy string */ lua_pushvalue(L, -2); lua_setmetatable(L, -2); /* set string metatable */ lua_pop(L, 1); /* pop dummy string */ lua_pushvalue(L, -2); /* string library... */ lua_setfield(L, -2, "__index"); /* ...is the __index metamethod */ lua_pop(L, 1); /* pop metatable */ } /* ** Open string library */ public static int luaopen_string (lua_State L) { luaL_register(L, LUA_STRLIBNAME, strlib); #if LUA_COMPAT_GFIND lua_getfield(L, -1, "gmatch"); lua_setfield(L, -2, "gfind"); #endif createmetatable(L); return 1; } } }