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