ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

lstrlib.cpp

Go to the documentation of this file.
00001 /*
00002 ** Standard library for strings and pattern-matching
00003 ** See Copyright Notice in lua.h
00004 */
00005 
00006 #define FORBIDDEN_SYMBOL_EXCEPTION_iscntrl
00007 #define FORBIDDEN_SYMBOL_EXCEPTION_ispunct
00008 
00009 #include "common/util.h"
00010 
00011 #include "engines/grim/lua/lauxlib.h"
00012 #include "engines/grim/lua/lua.h"
00013 #include "engines/grim/lua/lualib.h"
00014 
00015 namespace Grim {
00016 
00017 static void addnchar(const char *s, int32 n) {
00018     char *b = luaL_openspace(n);
00019     strncpy(b, s, n);
00020     luaL_addsize(n);
00021 }
00022 
00023 static void addstr(const char *s) {
00024     addnchar(s, strlen(s));
00025 }
00026 
00027 static void str_len() {
00028     lua_pushnumber(strlen(luaL_check_string(1)));
00029 }
00030 
00031 static void closeandpush() {
00032     luaL_addchar(0);
00033     lua_pushstring(luaL_buffer());
00034 }
00035 
00036 static void str_sub() {
00037     const char *s = luaL_check_string(1);
00038     int l = strlen(s);
00039     int start = (int)luaL_check_number(2);
00040     int end = (int)luaL_opt_number(3, -1);
00041     if (start < 0)
00042         start = l + start + 1;
00043     if (end < 0)
00044         end = l + end + 1;
00045     if (1 <= start && start <= end && end <= l) {
00046         luaL_resetbuffer();
00047         addnchar(s + start - 1, end - start + 1);
00048         closeandpush();
00049     } else
00050         lua_pushstring("");
00051 }
00052 
00053 static void str_lower() {
00054     const char *s;
00055     luaL_resetbuffer();
00056     for (s = luaL_check_string(1); *s; s++)
00057         luaL_addchar(tolower((byte)*s));
00058     closeandpush();
00059 }
00060 
00061 static void str_upper() {
00062     const char *s;
00063     luaL_resetbuffer();
00064     for (s = luaL_check_string(1); *s; s++)
00065         luaL_addchar(toupper((byte)*s));
00066     closeandpush();
00067 }
00068 
00069 static void str_rep() {
00070     const char *s = luaL_check_string(1);
00071     int32 n = (int32)luaL_check_number(2);
00072     luaL_resetbuffer();
00073     while (n-- > 0)
00074         addstr(s);
00075     closeandpush();
00076 }
00077 
00078 #define MAX_CAPT 9
00079 
00080 struct Capture {
00081     int32 level;  // total number of captures (finished or unfinished)
00082     struct {
00083         const char *init;
00084         int32 len;  // -1 signals unfinished capture
00085     } capture[MAX_CAPT];
00086 };
00087 
00088 #define ESC '%'
00089 #define SPECIALS  "^$*?.([%-"
00090 
00091 static void push_captures(Capture *cap) {
00092     int i;
00093 
00094     for (i = 0; i < cap->level; i++) {
00095         int l = cap->capture[i].len;
00096         char *buff = luaL_openspace(l+1);
00097         if (l == -1)
00098             lua_error("unfinished capture");
00099         strncpy(buff, cap->capture[i].init, l);
00100         buff[l] = 0;
00101         lua_pushstring(buff);
00102     }
00103 }
00104 
00105 static int32 check_cap (int32 l, Capture *cap) {
00106     l -= '1';
00107     if (!(0 <= l && l < cap->level && cap->capture[l].len != -1))
00108         lua_error("invalid capture index");
00109     return l;
00110 }
00111 
00112 static int32 capture_to_close(Capture *cap) {
00113     int32 level = cap->level;
00114     for (level--; level >= 0; level--)
00115         if (cap->capture[level].len == -1) return level;
00116     lua_error("invalid pattern capture");
00117     return 0;  // to avoid warnings
00118 }
00119 
00120 
00121 static const char *bracket_end(const char *p) {
00122     return (*p == 0) ? nullptr : strchr((*p == '^') ? p + 2 : p + 1, ']');
00123 }
00124 
00125 static int32 matchclass(int32 c, int32 cl) {
00126     int32 res;
00127     if (c == 0)
00128         return 0;
00129     switch (tolower((int)cl)) {
00130     case 'a' :
00131         res = Common::isAlpha(c);
00132         break;
00133     case 'c' :
00134         res = iscntrl(c);
00135         break;
00136     case 'd' :
00137         res = Common::isDigit(c);
00138         break;
00139     case 'l' :
00140         res = Common::isLower(c);
00141         break;
00142     case 'p' :
00143         res = ispunct(c);
00144         break;
00145     case 's' :
00146         res = Common::isSpace(c);
00147         break;
00148     case 'u' :
00149         res = Common::isUpper(c);
00150         break;
00151     case 'w' :
00152         res = Common::isAlnum(c);
00153         break;
00154     default:
00155         return (cl == c);
00156     }
00157     return (Common::isLower((byte)cl) ? res : !res);
00158 }
00159 
00160 int32 luaI_singlematch(int32 c, const char *p, const char **ep) {
00161     switch (*p) {
00162     case '.':  // matches any char
00163         *ep = p + 1;
00164         return c != 0;
00165     case '\0':  // end of pattern; matches nothing
00166         *ep = p;
00167         return 0;
00168     case ESC:
00169         if (*(++p) == '\0')
00170             luaL_verror("incorrect pattern (ends with `%c')", ESC);
00171         *ep = p+1;
00172         return matchclass(c, (byte)*p);
00173     case '[': {
00174         const char *end = bracket_end(p + 1);
00175         int32 sig = *(p + 1) == '^' ? (p++, 0) : 1;
00176         if (!end)
00177             lua_error("incorrect pattern (missing `]')");
00178         *ep = end + 1;
00179         if (c == 0)
00180             return 0;
00181         while (++p < end) {
00182             if (*p == ESC) {
00183                 if (((p + 1) < end) && matchclass(c, (byte)*++p))
00184                     return sig;
00185             } else if ((*(p + 1) == '-') && (p + 2 < end)) {
00186                 p += 2;
00187                 if ((byte)*(p - 2) <= c && c <= (byte)*p)
00188                     return sig;
00189             } else if ((byte)*p == c)
00190                 return sig;
00191         }
00192         return !sig;
00193     }
00194     default:
00195         *ep = p+1;
00196         return ((byte)*p == c);
00197     }
00198 }
00199 
00200 static const char *matchbalance(const char *s, int32 b, int32 e) {
00201     if (*s != b)
00202         return nullptr;
00203     else {
00204         int32 cont = 1;
00205         while (*(++s)) {
00206             if (*s == e) {
00207                 if (--cont == 0)
00208                     return s + 1;
00209             } else if (*s == b)
00210                 cont++;
00211         }
00212     }
00213     return nullptr;  // string ends out of balance
00214 }
00215 
00216 static const char *matchitem(const char *s, const char *p, Capture *cap, const char **ep) {
00217     if (*p == ESC) {
00218         p++;
00219         if (Common::isDigit((byte)*p)) {  // capture
00220             int32 l = check_cap(*p, cap);
00221             *ep = p + 1;
00222             if (strncmp(cap->capture[l].init, s, cap->capture[l].len) == 0)
00223                 return s + cap->capture[l].len;
00224             else
00225                 return nullptr;
00226         } else if (*p == 'b') {  // balanced string
00227             p++;
00228             if (*p == 0 || *(p + 1) == 0)
00229                 lua_error("unbalanced pattern");
00230             *ep = p + 2;
00231             return matchbalance(s, *p, *(p + 1));
00232         } else
00233             p--;  // and go through
00234     }
00235     return (luaI_singlematch((byte)*s, p, ep) ? s + 1 : nullptr);
00236 }
00237 
00238 static const char *match(const char *s, const char *p, Capture *cap) {
00239 init:
00240     // using goto's to optimize tail recursion
00241     switch (*p) {
00242     case '(':
00243         {  // start capture
00244             const char *res;
00245             if (cap->level >= MAX_CAPT)
00246                 lua_error("too many captures");
00247             cap->capture[cap->level].init = s;
00248             cap->capture[cap->level].len = -1;
00249             cap->level++;
00250             res = match(s, p + 1, cap);
00251             if (!res)  // match failed?
00252                 cap->level--;  // undo capture
00253             return res;
00254         }
00255     case ')':
00256         {  // end capture
00257             int32 l = capture_to_close(cap);
00258             const char *res;
00259             cap->capture[l].len = s - cap->capture[l].init;  // close capture
00260             res = match(s, p + 1, cap);
00261             if (!res)  // match failed?
00262                 cap->capture[l].len = -1;  // undo capture
00263             return res;
00264         }
00265     case '\0':
00266     case '$':  // (possibly) end of pattern
00267         if (*p == 0 || (*(p + 1) == 0 && *s == 0))
00268             return s;
00269         // fall through
00270     default:
00271         {  // it is a pattern item
00272             const char *ep;  // get what is next
00273             const char *s1 = matchitem(s, p, cap, &ep);
00274             switch (*ep) {
00275             case '*':
00276                 {  // repetition
00277                     const char *res;
00278                     if (s1) {
00279                         res = match(s1, p, cap);
00280                         return res;
00281                     }
00282                     p = ep + 1;
00283                     goto init;
00284                 }
00285             case '?':
00286                 {  // optional
00287                     const char *res;
00288                     if (s1) {
00289                         res = match(s1, ep + 1, cap);
00290                         return res;
00291                     }
00292                     p = ep + 1;
00293                     goto init;
00294                 }
00295             case '-':
00296                 {  // repetition
00297                     const char *res = match(s, ep + 1, cap);
00298                     if (res)
00299                         return res;
00300                     else if (s1) {
00301                         s = s1;
00302                         goto init;
00303                     } else
00304                         return nullptr;
00305                 }
00306             default:
00307                 if (s1) {
00308                     s = s1;
00309                     p = ep;
00310                     goto init;
00311                 } else
00312                     return nullptr;
00313             }
00314         }
00315     }
00316 }
00317 
00318 static void str_find() {
00319     const char *s = luaL_check_string(1);
00320     const char *p = luaL_check_string(2);
00321     int32 init = (uint32)luaL_opt_number(3, 1) - 1;
00322     luaL_arg_check(0 <= init && init <= (int32)strlen(s), 3, "out of range");
00323     if (lua_getparam(4) != LUA_NOOBJECT || !strpbrk(p, SPECIALS)) {  // no special characters?
00324             const char *s2 = strstr(s + init, p);
00325             if (s2) {
00326                 lua_pushnumber(s2 - s + 1);
00327                 lua_pushnumber(s2 - s + strlen(p));
00328             }
00329     } else {
00330         int32 anchor = (*p == '^') ? (p++, 1) : 0;
00331         const char *s1 = s + init;
00332         do {
00333             struct Capture cap;
00334             const char *res;
00335             cap.level = 0;
00336             res = match(s1, p, &cap);
00337             if (res) {
00338                 lua_pushnumber(s1 - s + 1);  // start
00339                 lua_pushnumber(res - s);   // end
00340                 push_captures(&cap);
00341                 return;
00342             }
00343         } while (*s1++ && !anchor);
00344     }
00345 }
00346 
00347 static void add_s(lua_Object newp, Capture *cap) {
00348     if (lua_isstring(newp)) {
00349         const char *news = lua_getstring(newp);
00350         while (*news) {
00351             if (*news != ESC || !Common::isDigit((byte)*++news))
00352                 luaL_addchar(*news++);
00353             else {
00354                 int l = check_cap(*news++, cap);
00355                 addnchar(cap->capture[l].init, cap->capture[l].len);
00356             }
00357         }
00358     } else if (lua_isfunction(newp)) {
00359         lua_Object res;
00360         int32 status;
00361         int32 oldbuff;
00362         lua_beginblock();
00363         push_captures(cap);
00364         // function may use buffer, so save it and create a new one
00365         oldbuff = luaL_newbuffer(0);
00366         status = lua_callfunction(newp);
00367         // restore old buffer
00368         luaL_oldbuffer(oldbuff);
00369         if (status) {
00370             lua_endblock();
00371             lua_error(nullptr);
00372         }
00373         res = lua_getresult(1);
00374         addstr(lua_isstring(res) ? lua_getstring(res) : "");
00375         lua_endblock();
00376     } else
00377         luaL_arg_check(0, 3, "string or function expected");
00378 }
00379 
00380 static void str_gsub() {
00381     const char *src = luaL_check_string(1);
00382     const char *p = luaL_check_string(2);
00383     lua_Object newp = lua_getparam(3);
00384     int32 max_s = (int32)luaL_opt_number(4, strlen(src) + 1);
00385     int32 anchor = (*p == '^') ? (p++, 1) : 0;
00386     int32 n = 0;
00387     luaL_resetbuffer();
00388     while (n < max_s) {
00389         struct Capture cap;
00390         const char *e;
00391         cap.level = 0;
00392         e = match(src, p, &cap);
00393         if (e) {
00394             n++;
00395             add_s(newp, &cap);
00396         }
00397         if (e && e > src) // non empty match?
00398             src = e;  // skip it
00399         else if (*src)
00400             luaL_addchar(*src++);
00401         else
00402             break;
00403         if (anchor)
00404             break;
00405     }
00406     addstr(src);
00407     closeandpush();
00408     lua_pushnumber(n);  // number of substitutions
00409 }
00410 
00411 static void luaI_addquoted(const char *s) {
00412     luaL_addchar('"');
00413     for (; *s; s++) {
00414         if (strchr("\"\\\n", *s))
00415             luaL_addchar('\\');
00416         luaL_addchar(*s);
00417     }
00418     luaL_addchar('"');
00419 }
00420 
00421 #define MAX_FORMAT 200
00422 
00423 static void str_format() {
00424     int32 arg = 1;
00425     const char *strfrmt = luaL_check_string(arg);
00426     luaL_resetbuffer();
00427     while (*strfrmt) {
00428         if (*strfrmt != '%')
00429             luaL_addchar(*strfrmt++);
00430         else if (*++strfrmt == '%')
00431             luaL_addchar(*strfrmt++);  // %%
00432         else { // format item
00433             char form[MAX_FORMAT];      // store the format ('%...')
00434             struct Capture cap;
00435             char *buff;
00436             const char *initf = strfrmt;
00437             form[0] = '%';
00438             cap.level = 0;
00439             strfrmt = match(strfrmt, "%d?%$?[-+ #]*(%d*)%.?(%d*)", &cap);
00440             if (cap.capture[0].len > 3 || cap.capture[1].len > 3)  // < 1000?
00441                 lua_error("invalid format (width or precision too long)");
00442             if (Common::isDigit((byte)initf[0]) && initf[1] == '$') {
00443                 arg = initf[0] - '0';
00444                 initf += 2;  // skip the 'n$'
00445             }
00446             arg++;
00447             strncpy(form+1, initf, strfrmt - initf + 1); // +1 to include convertion
00448             form[strfrmt-initf + 2] = 0;
00449             buff = luaL_openspace(1000);  // to store the formatted value
00450             switch (*strfrmt++) {
00451             case 'q':
00452                 luaI_addquoted(luaL_check_string(arg));
00453                 continue;
00454             case 's':
00455                 {
00456                     const char *s = luaL_check_string(arg);
00457                     buff = luaL_openspace(strlen(s));
00458                     sprintf(buff, form, s);
00459                     break;
00460                 }
00461             case 'c':
00462             case 'd':
00463             case 'o':
00464             case 'i':
00465             case 'u':
00466             case 'x':
00467             case 'X':
00468                 sprintf(buff, form, (int)luaL_check_number(arg));
00469                 break;
00470             case 'e':
00471             case 'E':
00472             case 'f':
00473             case 'g':
00474             case 'G':
00475                 sprintf(buff, form, luaL_check_number(arg));
00476                 break;
00477             default:  // also treat cases 'pnLlh'
00478                 lua_error("invalid option in `format'");
00479             }
00480             luaL_addsize(strlen(buff));
00481         }
00482     }
00483     closeandpush();  // push the result
00484 }
00485 
00486 static struct luaL_reg strlib[] = {
00487     {"strlen", str_len},
00488     {"strsub", str_sub},
00489     {"strlower", str_lower},
00490     {"strupper", str_upper},
00491     {"strrep", str_rep},
00492     {"format", str_format},
00493     {"strfind", str_find},
00494     {"gsub", str_gsub}
00495 };
00496 
00497 /*
00498 ** Open string library
00499 */
00500 void strlib_open() {
00501     luaL_openlib(strlib, (sizeof(strlib) / sizeof(strlib[0])));
00502 }
00503 
00504 } // end of namespace Grim


Generated on Sat Mar 23 2019 05:01:48 for ResidualVM by doxygen 1.7.1
curved edge   curved edge