mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-04 19:21:42 +00:00

# What's changed? * Check interrupt handler inside the pattern match engine to eliminate potential for programs to hang during string library function execution. * Allow iteration over table properties to pass the old type solver. ### Native Code Generation * Use in-place memory operands for math library operations on x64. * Replace opaque bools with separate enum classes in IrDump to improve code maintainability. * Translate operations on inferred vectors to IR. * Enable support for debugging native-compiled functions in Roblox Studio. ### New Type Solver * Rework type inference for boolean and string literals to introduce bounded free types (bounded below by the singleton type, and above by the primitive type) and reworked primitive type constraint to decide which is the appropriate type for the literal. * Introduce `FunctionCheckConstraint` to handle bidirectional typechecking for function calls, pushing the expected parameter types from the function onto the arguments. * Introduce `union` and `intersect` type families to compute deferred simplified unions and intersections to be employed by the constraint generation logic in the new solver. * Implement support for expanding the domain of local types in `Unifier2`. * Rework type inference for iteration variables bound by for in loops to use local types. * Change constraint blocking logic to use a set to prevent accidental re-blocking. * Add logic to detect missing return statements in functions. ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1682 lines
46 KiB
C++
1682 lines
46 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
|
|
#include "lualib.h"
|
|
|
|
#include "lstring.h"
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauInterruptablePatternMatch, false)
|
|
|
|
// macro to `unsign' a character
|
|
#define uchar(c) ((unsigned char)(c))
|
|
|
|
static int str_len(lua_State* L)
|
|
{
|
|
size_t l;
|
|
luaL_checklstring(L, 1, &l);
|
|
lua_pushinteger(L, (int)l);
|
|
return 1;
|
|
}
|
|
|
|
static int posrelat(int pos, size_t len)
|
|
{
|
|
// relative string position: negative means back from end
|
|
if (pos < 0)
|
|
pos += (int)len + 1;
|
|
return (pos >= 0) ? pos : 0;
|
|
}
|
|
|
|
static int str_sub(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
int start = posrelat(luaL_checkinteger(L, 2), l);
|
|
int end = posrelat(luaL_optinteger(L, 3, -1), l);
|
|
if (start < 1)
|
|
start = 1;
|
|
if (end > (int)l)
|
|
end = (int)l;
|
|
if (start <= end)
|
|
lua_pushlstring(L, s + start - 1, end - start + 1);
|
|
else
|
|
lua_pushliteral(L, "");
|
|
return 1;
|
|
}
|
|
|
|
static int str_reverse(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
luaL_Strbuf b;
|
|
char* ptr = luaL_buffinitsize(L, &b, l);
|
|
while (l--)
|
|
*ptr++ = s[l];
|
|
luaL_pushresultsize(&b, ptr - b.p);
|
|
return 1;
|
|
}
|
|
|
|
static int str_lower(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
luaL_Strbuf b;
|
|
char* ptr = luaL_buffinitsize(L, &b, l);
|
|
for (size_t i = 0; i < l; i++)
|
|
*ptr++ = tolower(uchar(s[i]));
|
|
luaL_pushresultsize(&b, l);
|
|
return 1;
|
|
}
|
|
|
|
static int str_upper(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
luaL_Strbuf b;
|
|
char* ptr = luaL_buffinitsize(L, &b, l);
|
|
for (size_t i = 0; i < l; i++)
|
|
*ptr++ = toupper(uchar(s[i]));
|
|
luaL_pushresultsize(&b, l);
|
|
return 1;
|
|
}
|
|
|
|
static int str_rep(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
int n = luaL_checkinteger(L, 2);
|
|
|
|
if (n <= 0)
|
|
{
|
|
lua_pushliteral(L, "");
|
|
return 1;
|
|
}
|
|
|
|
if (l > MAXSSIZE / (size_t)n) // may overflow?
|
|
luaL_error(L, "resulting string too large");
|
|
|
|
luaL_Strbuf b;
|
|
char* ptr = luaL_buffinitsize(L, &b, l * n);
|
|
|
|
const char* start = ptr;
|
|
|
|
size_t left = l * n;
|
|
size_t step = l;
|
|
|
|
memcpy(ptr, s, l);
|
|
ptr += l;
|
|
left -= l;
|
|
|
|
// use the increasing 'pattern' inside our target buffer to fill the next part
|
|
while (step < left)
|
|
{
|
|
memcpy(ptr, start, step);
|
|
ptr += step;
|
|
left -= step;
|
|
step <<= 1;
|
|
}
|
|
|
|
// fill tail
|
|
memcpy(ptr, start, left);
|
|
ptr += left;
|
|
|
|
luaL_pushresultsize(&b, l * n);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int str_byte(lua_State* L)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, 1, &l);
|
|
int posi = posrelat(luaL_optinteger(L, 2, 1), l);
|
|
int pose = posrelat(luaL_optinteger(L, 3, posi), l);
|
|
int n, i;
|
|
if (posi <= 0)
|
|
posi = 1;
|
|
if ((size_t)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 < n; i++)
|
|
lua_pushinteger(L, uchar(s[posi + i - 1]));
|
|
return n;
|
|
}
|
|
|
|
static int str_char(lua_State* L)
|
|
{
|
|
int n = lua_gettop(L); // number of arguments
|
|
|
|
luaL_Strbuf b;
|
|
char* ptr = luaL_buffinitsize(L, &b, n);
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
{
|
|
int c = luaL_checkinteger(L, i);
|
|
luaL_argcheck(L, uchar(c) == c, i, "invalid value");
|
|
|
|
*ptr++ = uchar(c);
|
|
}
|
|
luaL_pushresultsize(&b, n);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** {======================================================
|
|
** PATTERN MATCHING
|
|
** =======================================================
|
|
*/
|
|
|
|
#define CAP_UNFINISHED (-1)
|
|
#define CAP_POSITION (-2)
|
|
|
|
typedef struct MatchState
|
|
{
|
|
int matchdepth; // control for recursive depth (to avoid C stack overflow)
|
|
const char* src_init; // init of source string
|
|
const char* src_end; // end ('\0') of source string
|
|
const char* p_end; // end ('\0') of pattern
|
|
lua_State* L;
|
|
int level; // total number of captures (finished or unfinished)
|
|
struct
|
|
{
|
|
const char* init;
|
|
ptrdiff_t len;
|
|
} capture[LUA_MAXCAPTURES];
|
|
} MatchState;
|
|
|
|
// recursive function
|
|
static const char* match(MatchState* ms, const char* s, const char* p);
|
|
|
|
#define L_ESC '%'
|
|
#define SPECIALS "^$*+?.([%-"
|
|
|
|
static int check_capture(MatchState* ms, int l)
|
|
{
|
|
l -= '1';
|
|
if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED)
|
|
luaL_error(ms->L, "invalid capture index %%%d", l + 1);
|
|
return l;
|
|
}
|
|
|
|
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;
|
|
luaL_error(ms->L, "invalid pattern capture");
|
|
}
|
|
|
|
static const char* classend(MatchState* ms, const char* p)
|
|
{
|
|
switch (*p++)
|
|
{
|
|
case L_ESC:
|
|
{
|
|
if (p == ms->p_end)
|
|
luaL_error(ms->L, "malformed pattern (ends with '%%')");
|
|
return p + 1;
|
|
}
|
|
case '[':
|
|
{
|
|
if (*p == '^')
|
|
p++;
|
|
do
|
|
{ // look for a `]'
|
|
if (p == ms->p_end)
|
|
luaL_error(ms->L, "malformed pattern (missing ']')");
|
|
if (*(p++) == L_ESC && p < ms->p_end)
|
|
p++; // skip escapes (e.g. `%]')
|
|
} while (*p != ']');
|
|
return p + 1;
|
|
}
|
|
default:
|
|
{
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int match_class(int c, int cl)
|
|
{
|
|
int res;
|
|
switch (tolower(cl))
|
|
{
|
|
case 'a':
|
|
res = isalpha(c);
|
|
break;
|
|
case 'c':
|
|
res = iscntrl(c);
|
|
break;
|
|
case 'd':
|
|
res = isdigit(c);
|
|
break;
|
|
case 'g':
|
|
res = isgraph(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(c);
|
|
break;
|
|
case 'z':
|
|
res = (c == 0);
|
|
break; // deprecated option
|
|
default:
|
|
return (cl == c);
|
|
}
|
|
return (islower(cl) ? res : !res);
|
|
}
|
|
|
|
static int matchbracketclass(int c, const char* p, const char* ec)
|
|
{
|
|
int sig = 1;
|
|
if (*(p + 1) == '^')
|
|
{
|
|
sig = 0;
|
|
p++; // skip the `^'
|
|
}
|
|
while (++p < ec)
|
|
{
|
|
if (*p == L_ESC)
|
|
{
|
|
p++;
|
|
if (match_class(c, uchar(*p)))
|
|
return sig;
|
|
}
|
|
else if ((*(p + 1) == '-') && (p + 2 < ec))
|
|
{
|
|
p += 2;
|
|
if (uchar(*(p - 2)) <= c && c <= uchar(*p))
|
|
return sig;
|
|
}
|
|
else if (uchar(*p) == c)
|
|
return sig;
|
|
}
|
|
return !sig;
|
|
}
|
|
|
|
static int singlematch(MatchState* ms, const char* s, const char* p, const char* ep)
|
|
{
|
|
if (s >= ms->src_end)
|
|
return 0;
|
|
else
|
|
{
|
|
int c = uchar(*s);
|
|
switch (*p)
|
|
{
|
|
case '.':
|
|
return 1; // matches any char
|
|
case L_ESC:
|
|
return match_class(c, uchar(*(p + 1)));
|
|
case '[':
|
|
return matchbracketclass(c, p, ep - 1);
|
|
default:
|
|
return (uchar(*p) == c);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char* matchbalance(MatchState* ms, const char* s, const char* p)
|
|
{
|
|
if (p >= ms->p_end - 1)
|
|
luaL_error(ms->L, "malformed pattern (missing arguments to '%%b')");
|
|
if (*s != *p)
|
|
return NULL;
|
|
else
|
|
{
|
|
int b = *p;
|
|
int e = *(p + 1);
|
|
int cont = 1;
|
|
while (++s < ms->src_end)
|
|
{
|
|
if (*s == e)
|
|
{
|
|
if (--cont == 0)
|
|
return s + 1;
|
|
}
|
|
else if (*s == b)
|
|
cont++;
|
|
}
|
|
}
|
|
return NULL; // string ends out of balance
|
|
}
|
|
|
|
static const char* max_expand(MatchState* ms, const char* s, const char* p, const char* ep)
|
|
{
|
|
ptrdiff_t i = 0; // counts maximum expand for item
|
|
while (singlematch(ms, s + i, p, ep))
|
|
i++;
|
|
// keeps trying to match with the maximum repetitions
|
|
while (i >= 0)
|
|
{
|
|
const char* res = match(ms, (s + i), ep + 1);
|
|
if (res)
|
|
return res;
|
|
i--; // else didn't match; reduce 1 repetition to try again
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char* min_expand(MatchState* ms, const char* s, const char* p, const char* ep)
|
|
{
|
|
for (;;)
|
|
{
|
|
const char* res = match(ms, s, ep + 1);
|
|
if (res != NULL)
|
|
return res;
|
|
else if (singlematch(ms, s, p, ep))
|
|
s++; // try with one more repetition
|
|
else
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static const char* start_capture(MatchState* ms, const char* s, const char* p, int what)
|
|
{
|
|
const char* 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;
|
|
}
|
|
|
|
static const char* end_capture(MatchState* ms, const char* s, const char* p)
|
|
{
|
|
int l = capture_to_close(ms);
|
|
const char* 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;
|
|
}
|
|
|
|
static const char* match_capture(MatchState* ms, const char* s, int l)
|
|
{
|
|
size_t len;
|
|
l = check_capture(ms, l);
|
|
len = ms->capture[l].len;
|
|
if ((size_t)(ms->src_end - s) >= len && memcmp(ms->capture[l].init, s, len) == 0)
|
|
return s + len;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static const char* match(MatchState* ms, const char* s, const char* p)
|
|
{
|
|
if (ms->matchdepth-- == 0)
|
|
luaL_error(ms->L, "pattern too complex");
|
|
|
|
if (DFFlag::LuauInterruptablePatternMatch)
|
|
{
|
|
lua_State* L = ms->L;
|
|
void (*interrupt)(lua_State*, int) = L->global->cb.interrupt;
|
|
|
|
if (LUAU_UNLIKELY(!!interrupt))
|
|
{
|
|
// this interrupt is not yieldable
|
|
L->nCcalls++;
|
|
interrupt(L, -1);
|
|
L->nCcalls--;
|
|
}
|
|
}
|
|
|
|
init: // using goto's to optimize tail recursion
|
|
if (p != ms->p_end)
|
|
{ // end of pattern?
|
|
switch (*p)
|
|
{
|
|
case '(':
|
|
{ // start capture
|
|
if (*(p + 1) == ')') // position capture?
|
|
s = start_capture(ms, s, p + 2, CAP_POSITION);
|
|
else
|
|
s = start_capture(ms, s, p + 1, CAP_UNFINISHED);
|
|
break;
|
|
}
|
|
case ')':
|
|
{ // end capture
|
|
s = end_capture(ms, s, p + 1);
|
|
break;
|
|
}
|
|
case '$':
|
|
{
|
|
if ((p + 1) != ms->p_end) // is the `$' the last char in pattern?
|
|
goto dflt; // no; go to default
|
|
s = (s == ms->src_end) ? s : NULL; // check end of string
|
|
break;
|
|
}
|
|
case L_ESC:
|
|
{ // escaped sequences not in the format class[*+?-]?
|
|
switch (*(p + 1))
|
|
{
|
|
case 'b':
|
|
{ // balanced string?
|
|
s = matchbalance(ms, s, p + 2);
|
|
if (s != NULL)
|
|
{
|
|
p += 4;
|
|
goto init; // return match(ms, s, p + 4);
|
|
} // else fail (s == NULL)
|
|
break;
|
|
}
|
|
case 'f':
|
|
{ // frontier?
|
|
const char* ep;
|
|
char previous;
|
|
p += 2;
|
|
if (*p != '[')
|
|
luaL_error(ms->L, "missing '[' after '%%f' in pattern");
|
|
ep = classend(ms, p); // points to what is next
|
|
previous = (s == ms->src_init) ? '\0' : *(s - 1);
|
|
if (!matchbracketclass(uchar(previous), p, ep - 1) && matchbracketclass(uchar(*s), p, ep - 1))
|
|
{
|
|
p = ep;
|
|
goto init; // return match(ms, s, ep);
|
|
}
|
|
s = NULL; // match failed
|
|
break;
|
|
}
|
|
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 = match_capture(ms, s, uchar(*(p + 1)));
|
|
if (s != NULL)
|
|
{
|
|
p += 2;
|
|
goto init; // return match(ms, s, p + 2)
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
goto dflt;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
dflt:
|
|
{ // pattern class plus optional suffix
|
|
const char* ep = classend(ms, p); // points to optional suffix
|
|
// does not match at least once?
|
|
if (!singlematch(ms, s, p, ep))
|
|
{
|
|
if (*ep == '*' || *ep == '?' || *ep == '-')
|
|
{ // accept empty?
|
|
p = ep + 1;
|
|
goto init; // return match(ms, s, ep + 1);
|
|
}
|
|
else // '+' or no suffix
|
|
s = NULL; // fail
|
|
}
|
|
else
|
|
{ // matched once
|
|
switch (*ep)
|
|
{ // handle optional suffix
|
|
case '?':
|
|
{ // optional
|
|
const char* res;
|
|
if ((res = match(ms, s + 1, ep + 1)) != NULL)
|
|
s = res;
|
|
else
|
|
{
|
|
p = ep + 1;
|
|
goto init; // else return match(ms, s, ep + 1);
|
|
}
|
|
break;
|
|
}
|
|
case '+': // 1 or more repetitions
|
|
s++; // 1 match already done
|
|
// go through
|
|
case '*': // 0 or more repetitions
|
|
s = max_expand(ms, s, p, ep);
|
|
break;
|
|
case '-': // 0 or more repetitions (minimum)
|
|
s = min_expand(ms, s, p, ep);
|
|
break;
|
|
default: // no suffix
|
|
s++;
|
|
p = ep;
|
|
goto init; // return match(ms, s + 1, ep);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ms->matchdepth++;
|
|
return s;
|
|
}
|
|
|
|
static const char* lmemfind(const char* s1, size_t l1, const char* s2, size_t l2)
|
|
{
|
|
if (l2 == 0)
|
|
return s1; // empty strings are everywhere
|
|
else if (l2 > l1)
|
|
return NULL; // avoids a negative `l1'
|
|
else
|
|
{
|
|
const char* 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 = (const char*)memchr(s1, *s2, l1)) != NULL)
|
|
{
|
|
init++; // 1st char is already checked
|
|
if (memcmp(init, s2 + 1, l2) == 0)
|
|
return init - 1;
|
|
else
|
|
{ // correct `l1' and `s1' to try again
|
|
l1 -= init - s1;
|
|
s1 = init;
|
|
}
|
|
}
|
|
return NULL; // not found
|
|
}
|
|
}
|
|
|
|
static void push_onecapture(MatchState* ms, int i, const char* s, const char* e)
|
|
{
|
|
if (i >= ms->level)
|
|
{
|
|
if (i == 0) // ms->level == 0, too
|
|
lua_pushlstring(ms->L, s, 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, (int)(ms->capture[i].init - ms->src_init) + 1);
|
|
else
|
|
lua_pushlstring(ms->L, ms->capture[i].init, l);
|
|
}
|
|
}
|
|
|
|
static int push_captures(MatchState* ms, const char* s, const char* e)
|
|
{
|
|
int i;
|
|
int nlevels = (ms->level == 0 && s) ? 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
|
|
}
|
|
|
|
// check whether pattern has no special characters
|
|
static int nospecials(const char* p, size_t l)
|
|
{
|
|
size_t upto = 0;
|
|
do
|
|
{
|
|
if (strpbrk(p + upto, SPECIALS))
|
|
return 0; // pattern has a special character
|
|
upto += strlen(p + upto) + 1; // may have more after \0
|
|
} while (upto <= l);
|
|
return 1; // no special chars found
|
|
}
|
|
|
|
static void prepstate(MatchState* ms, lua_State* L, const char* s, size_t ls, const char* p, size_t lp)
|
|
{
|
|
ms->L = L;
|
|
ms->matchdepth = LUAI_MAXCCALLS;
|
|
ms->src_init = s;
|
|
ms->src_end = s + ls;
|
|
ms->p_end = p + lp;
|
|
}
|
|
|
|
static void reprepstate(MatchState* ms)
|
|
{
|
|
ms->level = 0;
|
|
LUAU_ASSERT(ms->matchdepth == LUAI_MAXCCALLS);
|
|
}
|
|
|
|
static int str_find_aux(lua_State* L, int find)
|
|
{
|
|
size_t ls, lp;
|
|
const char* s = luaL_checklstring(L, 1, &ls);
|
|
const char* p = luaL_checklstring(L, 2, &lp);
|
|
int init = posrelat(luaL_optinteger(L, 3, 1), ls);
|
|
if (init < 1)
|
|
init = 1;
|
|
else if (init > (int)ls + 1)
|
|
{ // start after string's end?
|
|
lua_pushnil(L); // cannot find anything
|
|
return 1;
|
|
}
|
|
// explicit request or no special characters?
|
|
if (find && (lua_toboolean(L, 4) || nospecials(p, lp)))
|
|
{
|
|
// do a plain search
|
|
const char* s2 = lmemfind(s + init - 1, ls - init + 1, p, lp);
|
|
if (s2)
|
|
{
|
|
lua_pushinteger(L, (int)(s2 - s + 1));
|
|
lua_pushinteger(L, (int)(s2 - s + lp));
|
|
return 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MatchState ms;
|
|
const char* s1 = s + init - 1;
|
|
int anchor = (*p == '^');
|
|
if (anchor)
|
|
{
|
|
p++;
|
|
lp--; // skip anchor character
|
|
}
|
|
prepstate(&ms, L, s, ls, p, lp);
|
|
do
|
|
{
|
|
const char* res;
|
|
reprepstate(&ms);
|
|
if ((res = match(&ms, s1, p)) != NULL)
|
|
{
|
|
if (find)
|
|
{
|
|
lua_pushinteger(L, (int)(s1 - s + 1)); // start
|
|
lua_pushinteger(L, (int)(res - s)); // end
|
|
return push_captures(&ms, NULL, 0) + 2;
|
|
}
|
|
else
|
|
return push_captures(&ms, s1, res);
|
|
}
|
|
} while (s1++ < ms.src_end && !anchor);
|
|
}
|
|
lua_pushnil(L); // not found
|
|
return 1;
|
|
}
|
|
|
|
static int str_find(lua_State* L)
|
|
{
|
|
return str_find_aux(L, 1);
|
|
}
|
|
|
|
static int str_match(lua_State* L)
|
|
{
|
|
return str_find_aux(L, 0);
|
|
}
|
|
|
|
static int gmatch_aux(lua_State* L)
|
|
{
|
|
MatchState ms;
|
|
size_t ls, lp;
|
|
const char* s = lua_tolstring(L, lua_upvalueindex(1), &ls);
|
|
const char* p = lua_tolstring(L, lua_upvalueindex(2), &lp);
|
|
const char* src;
|
|
prepstate(&ms, L, s, ls, p, lp);
|
|
for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); src <= ms.src_end; src++)
|
|
{
|
|
const char* e;
|
|
reprepstate(&ms);
|
|
if ((e = match(&ms, src, p)) != NULL)
|
|
{
|
|
int newstart = (int)(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
|
|
}
|
|
|
|
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, NULL, 3);
|
|
return 1;
|
|
}
|
|
|
|
static void add_s(MatchState* ms, luaL_Strbuf* b, const char* s, const char* e)
|
|
{
|
|
size_t l, i;
|
|
const char* news = lua_tolstring(ms->L, 3, &l);
|
|
|
|
luaL_prepbuffsize(b, l);
|
|
|
|
for (i = 0; i < l; i++)
|
|
{
|
|
if (news[i] != L_ESC)
|
|
luaL_addchar(b, news[i]);
|
|
else
|
|
{
|
|
i++; // skip ESC
|
|
if (!isdigit(uchar(news[i])))
|
|
{
|
|
if (news[i] != L_ESC)
|
|
luaL_error(ms->L, "invalid use of '%c' in replacement string", L_ESC);
|
|
luaL_addchar(b, news[i]);
|
|
}
|
|
else if (news[i] == '0')
|
|
luaL_addlstring(b, s, e - s);
|
|
else
|
|
{
|
|
push_onecapture(ms, news[i] - '1', s, e);
|
|
luaL_addvalue(b); // add capture to accumulated result
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void add_value(MatchState* ms, luaL_Strbuf* b, const char* s, const char* e, int tr)
|
|
{
|
|
lua_State* L = ms->L;
|
|
switch (tr)
|
|
{
|
|
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;
|
|
}
|
|
default:
|
|
{ // LUA_TNUMBER or LUA_TSTRING
|
|
add_s(ms, b, s, e);
|
|
return;
|
|
}
|
|
}
|
|
if (!lua_toboolean(L, -1))
|
|
{ // nil or false?
|
|
lua_pop(L, 1);
|
|
lua_pushlstring(L, s, e - s); // keep original text
|
|
}
|
|
else if (!lua_isstring(L, -1))
|
|
luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1));
|
|
luaL_addvalue(b); // add result to accumulator
|
|
}
|
|
|
|
static int str_gsub(lua_State* L)
|
|
{
|
|
size_t srcl, lp;
|
|
const char* src = luaL_checklstring(L, 1, &srcl);
|
|
const char* p = luaL_checklstring(L, 2, &lp);
|
|
int tr = lua_type(L, 3);
|
|
int max_s = luaL_optinteger(L, 4, (int)srcl + 1);
|
|
int anchor = (*p == '^');
|
|
int n = 0;
|
|
MatchState ms;
|
|
luaL_Strbuf b;
|
|
luaL_argexpected(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, "string/function/table");
|
|
luaL_buffinit(L, &b);
|
|
if (anchor)
|
|
{
|
|
p++;
|
|
lp--; // skip anchor character
|
|
}
|
|
prepstate(&ms, L, src, srcl, p, lp);
|
|
while (n < max_s)
|
|
{
|
|
const char* e;
|
|
reprepstate(&ms);
|
|
e = match(&ms, src, p);
|
|
if (e)
|
|
{
|
|
n++;
|
|
add_value(&ms, &b, src, e, tr);
|
|
}
|
|
if (e && e > src) // non empty match?
|
|
src = e; // skip it
|
|
else if (src < ms.src_end)
|
|
luaL_addchar(&b, *src++);
|
|
else
|
|
break;
|
|
if (anchor)
|
|
break;
|
|
}
|
|
luaL_addlstring(&b, src, ms.src_end - src);
|
|
luaL_pushresult(&b);
|
|
lua_pushinteger(L, n); // number of substitutions
|
|
return 2;
|
|
}
|
|
|
|
// }======================================================
|
|
|
|
// valid flags in a format specification
|
|
#define FLAGS "-+ #0"
|
|
// maximum size of each formatted item (> len(format('%99.99f', -1e308)))
|
|
#define MAX_ITEM 512
|
|
// maximum size of each format specification (such as '%-099.99d')
|
|
#define MAX_FORMAT 32
|
|
|
|
static void addquoted(lua_State* L, luaL_Strbuf* b, int arg)
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, arg, &l);
|
|
|
|
luaL_prepbuffsize(b, l + 2);
|
|
|
|
luaL_addchar(b, '"');
|
|
while (l--)
|
|
{
|
|
switch (*s)
|
|
{
|
|
case '"':
|
|
case '\\':
|
|
case '\n':
|
|
{
|
|
luaL_addchar(b, '\\');
|
|
luaL_addchar(b, *s);
|
|
break;
|
|
}
|
|
case '\r':
|
|
{
|
|
luaL_addlstring(b, "\\r", 2);
|
|
break;
|
|
}
|
|
case '\0':
|
|
{
|
|
luaL_addlstring(b, "\\000", 4);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
luaL_addchar(b, *s);
|
|
break;
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
luaL_addchar(b, '"');
|
|
}
|
|
|
|
static const char* scanformat(lua_State* L, const char* strfrmt, char* form, size_t* size)
|
|
{
|
|
const char* p = strfrmt;
|
|
while (*p != '\0' && strchr(FLAGS, *p) != NULL)
|
|
p++; // skip flags
|
|
if ((size_t)(p - strfrmt) >= sizeof(FLAGS))
|
|
luaL_error(L, "invalid format (repeated flags)");
|
|
if (isdigit(uchar(*p)))
|
|
p++; // skip width
|
|
if (isdigit(uchar(*p)))
|
|
p++; // (2 digits at most)
|
|
if (*p == '.')
|
|
{
|
|
p++;
|
|
if (isdigit(uchar(*p)))
|
|
p++; // skip precision
|
|
if (isdigit(uchar(*p)))
|
|
p++; // (2 digits at most)
|
|
}
|
|
if (isdigit(uchar(*p)))
|
|
luaL_error(L, "invalid format (width or precision too long)");
|
|
*(form++) = '%';
|
|
*size = p - strfrmt + 1;
|
|
strncpy(form, strfrmt, *size);
|
|
form += *size;
|
|
*form = '\0';
|
|
return p;
|
|
}
|
|
|
|
static void addInt64Format(char form[MAX_FORMAT], char formatIndicator, size_t formatItemSize)
|
|
{
|
|
LUAU_ASSERT((formatItemSize + 3) <= MAX_FORMAT);
|
|
LUAU_ASSERT(form[0] == '%');
|
|
LUAU_ASSERT(form[formatItemSize] != 0);
|
|
LUAU_ASSERT(form[formatItemSize + 1] == 0);
|
|
form[formatItemSize + 0] = 'l';
|
|
form[formatItemSize + 1] = 'l';
|
|
form[formatItemSize + 2] = formatIndicator;
|
|
form[formatItemSize + 3] = 0;
|
|
}
|
|
|
|
static int str_format(lua_State* L)
|
|
{
|
|
int top = lua_gettop(L);
|
|
int arg = 1;
|
|
size_t sfl;
|
|
const char* strfrmt = luaL_checklstring(L, arg, &sfl);
|
|
const char* strfrmt_end = strfrmt + sfl;
|
|
luaL_Strbuf b;
|
|
luaL_buffinit(L, &b);
|
|
while (strfrmt < strfrmt_end)
|
|
{
|
|
if (*strfrmt != L_ESC)
|
|
luaL_addchar(&b, *strfrmt++);
|
|
else if (*++strfrmt == L_ESC)
|
|
luaL_addchar(&b, *strfrmt++); // %%
|
|
else if (*strfrmt == '*')
|
|
{
|
|
strfrmt++;
|
|
if (++arg > top)
|
|
luaL_error(L, "missing argument #%d", arg);
|
|
|
|
luaL_addvalueany(&b, arg);
|
|
}
|
|
else
|
|
{ // format item
|
|
char form[MAX_FORMAT]; // to store the format (`%...')
|
|
char buff[MAX_ITEM]; // to store the formatted item
|
|
if (++arg > top)
|
|
luaL_error(L, "missing argument #%d", arg);
|
|
size_t formatItemSize = 0;
|
|
strfrmt = scanformat(L, strfrmt, form, &formatItemSize);
|
|
char formatIndicator = *strfrmt++;
|
|
switch (formatIndicator)
|
|
{
|
|
case 'c':
|
|
{
|
|
snprintf(buff, sizeof(buff), form, (int)luaL_checknumber(L, arg));
|
|
break;
|
|
}
|
|
case 'd':
|
|
case 'i':
|
|
{
|
|
addInt64Format(form, formatIndicator, formatItemSize);
|
|
snprintf(buff, sizeof(buff), form, (long long)luaL_checknumber(L, arg));
|
|
break;
|
|
}
|
|
case 'o':
|
|
case 'u':
|
|
case 'x':
|
|
case 'X':
|
|
{
|
|
double argValue = luaL_checknumber(L, arg);
|
|
addInt64Format(form, formatIndicator, formatItemSize);
|
|
unsigned long long v = (argValue < 0) ? (unsigned long long)(long long)argValue : (unsigned long long)argValue;
|
|
snprintf(buff, sizeof(buff), form, v);
|
|
break;
|
|
}
|
|
case 'e':
|
|
case 'E':
|
|
case 'f':
|
|
case 'g':
|
|
case 'G':
|
|
{
|
|
snprintf(buff, sizeof(buff), form, (double)luaL_checknumber(L, arg));
|
|
break;
|
|
}
|
|
case 'q':
|
|
{
|
|
addquoted(L, &b, arg);
|
|
continue; // skip the 'luaL_addlstring' at the end
|
|
}
|
|
case 's':
|
|
{
|
|
size_t l;
|
|
const char* s = luaL_checklstring(L, arg, &l);
|
|
// no precision and string is too long to be formatted, or no format necessary to begin with
|
|
if (form[2] == '\0' || (!strchr(form, '.') && l >= 100))
|
|
{
|
|
luaL_addlstring(&b, s, l);
|
|
continue; // skip the `luaL_addlstring' at the end
|
|
}
|
|
else
|
|
{
|
|
snprintf(buff, sizeof(buff), form, s);
|
|
break;
|
|
}
|
|
}
|
|
case '*':
|
|
{
|
|
// %* is parsed above, so if we got here we must have %...*
|
|
luaL_error(L, "'%%*' does not take a form");
|
|
}
|
|
default:
|
|
{ // also treat cases `pnLlh'
|
|
luaL_error(L, "invalid option '%%%c' to 'format'", *(strfrmt - 1));
|
|
}
|
|
}
|
|
luaL_addlstring(&b, buff, strlen(buff));
|
|
}
|
|
}
|
|
luaL_pushresult(&b);
|
|
return 1;
|
|
}
|
|
|
|
static int str_split(lua_State* L)
|
|
{
|
|
size_t haystackLen;
|
|
const char* haystack = luaL_checklstring(L, 1, &haystackLen);
|
|
size_t needleLen;
|
|
const char* needle = luaL_optlstring(L, 2, ",", &needleLen);
|
|
|
|
const char* begin = haystack;
|
|
const char* end = haystack + haystackLen;
|
|
const char* spanStart = begin;
|
|
int numMatches = 0;
|
|
|
|
lua_createtable(L, 0, 0);
|
|
|
|
if (needleLen == 0)
|
|
begin++;
|
|
|
|
// Don't iterate the last needleLen - 1 bytes of the string - they are
|
|
// impossible to be splits and would let us memcmp past the end of the
|
|
// buffer.
|
|
for (const char* iter = begin; iter <= end - needleLen; iter++)
|
|
{
|
|
// Use of memcmp here instead of strncmp is so that we allow embedded
|
|
// nulls to be used in either of the haystack or the needle strings.
|
|
// Most Lua string APIs allow embedded nulls, and this should be no
|
|
// exception.
|
|
if (memcmp(iter, needle, needleLen) == 0)
|
|
{
|
|
lua_pushinteger(L, ++numMatches);
|
|
lua_pushlstring(L, spanStart, iter - spanStart);
|
|
lua_settable(L, -3);
|
|
|
|
spanStart = iter + needleLen;
|
|
if (needleLen > 0)
|
|
iter += needleLen - 1;
|
|
}
|
|
}
|
|
|
|
if (needleLen > 0)
|
|
{
|
|
lua_pushinteger(L, ++numMatches);
|
|
lua_pushlstring(L, spanStart, end - spanStart);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** {======================================================
|
|
** PACK/UNPACK
|
|
** =======================================================
|
|
*/
|
|
|
|
// value used for padding
|
|
#if !defined(LUAL_PACKPADBYTE)
|
|
#define LUAL_PACKPADBYTE 0x00
|
|
#endif
|
|
|
|
// maximum size for the binary representation of an integer
|
|
#define MAXINTSIZE 16
|
|
|
|
// number of bits in a character
|
|
#define NB CHAR_BIT
|
|
|
|
// mask for one character (NB 1's)
|
|
#define MC ((1 << NB) - 1)
|
|
|
|
// internal size of integers used for pack/unpack
|
|
#define SZINT (int)sizeof(long long)
|
|
|
|
// dummy union to get native endianness
|
|
static const union
|
|
{
|
|
int dummy;
|
|
char little; // true iff machine is little endian
|
|
} nativeendian = {1};
|
|
|
|
// assume we need to align for double & pointers
|
|
#define MAXALIGN 8
|
|
|
|
/*
|
|
** Union for serializing floats
|
|
*/
|
|
typedef union Ftypes
|
|
{
|
|
float f;
|
|
double d;
|
|
double n;
|
|
char buff[5 * sizeof(double)]; // enough for any float type
|
|
} Ftypes;
|
|
|
|
/*
|
|
** information to pack/unpack stuff
|
|
*/
|
|
typedef struct Header
|
|
{
|
|
lua_State* L;
|
|
int islittle;
|
|
int maxalign;
|
|
} Header;
|
|
|
|
/*
|
|
** options for pack/unpack
|
|
*/
|
|
typedef enum KOption
|
|
{
|
|
Kint, // signed integers
|
|
Kuint, // unsigned integers
|
|
Kfloat, // floating-point numbers
|
|
Kchar, // fixed-length strings
|
|
Kstring, // strings with prefixed length
|
|
Kzstr, // zero-terminated strings
|
|
Kpadding, // padding
|
|
Kpaddalign, // padding for alignment
|
|
Knop // no-op (configuration or spaces)
|
|
} KOption;
|
|
|
|
/*
|
|
** Read an integer numeral from string 'fmt' or return 'df' if
|
|
** there is no numeral
|
|
*/
|
|
static int digit(int c)
|
|
{
|
|
return '0' <= c && c <= '9';
|
|
}
|
|
|
|
static int getnum(Header* h, const char** fmt, int df)
|
|
{
|
|
if (!digit(**fmt)) // no number?
|
|
return df; // return default value
|
|
else
|
|
{
|
|
int a = 0;
|
|
do
|
|
{
|
|
a = a * 10 + (*((*fmt)++) - '0');
|
|
} while (digit(**fmt) && a <= (INT_MAX - 9) / 10);
|
|
if (a > MAXSSIZE || digit(**fmt))
|
|
luaL_error(h->L, "size specifier is too large");
|
|
return a;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Read an integer numeral and raises an error if it is larger
|
|
** than the maximum size for integers.
|
|
*/
|
|
static int getnumlimit(Header* h, const char** fmt, int df)
|
|
{
|
|
int sz = getnum(h, fmt, df);
|
|
if (sz > MAXINTSIZE || sz <= 0)
|
|
luaL_error(h->L, "integral size (%d) out of limits [1,%d]", sz, MAXINTSIZE);
|
|
return sz;
|
|
}
|
|
|
|
/*
|
|
** Initialize Header
|
|
*/
|
|
static void initheader(lua_State* L, Header* h)
|
|
{
|
|
h->L = L;
|
|
h->islittle = nativeendian.little;
|
|
h->maxalign = 1;
|
|
}
|
|
|
|
/*
|
|
** Read and classify next option. 'size' is filled with option's size.
|
|
*/
|
|
static KOption getoption(Header* h, const char** fmt, int* size)
|
|
{
|
|
int opt = *((*fmt)++);
|
|
*size = 0; // default
|
|
switch (opt)
|
|
{
|
|
case 'b':
|
|
*size = 1;
|
|
return Kint;
|
|
case 'B':
|
|
*size = 1;
|
|
return Kuint;
|
|
case 'h':
|
|
*size = 2;
|
|
return Kint;
|
|
case 'H':
|
|
*size = 2;
|
|
return Kuint;
|
|
case 'l':
|
|
*size = 8;
|
|
return Kint;
|
|
case 'L':
|
|
*size = 8;
|
|
return Kuint;
|
|
case 'j':
|
|
*size = 4;
|
|
return Kint;
|
|
case 'J':
|
|
*size = 4;
|
|
return Kuint;
|
|
case 'T':
|
|
*size = 4;
|
|
return Kuint;
|
|
case 'f':
|
|
*size = 4;
|
|
return Kfloat;
|
|
case 'd':
|
|
*size = 8;
|
|
return Kfloat;
|
|
case 'n':
|
|
*size = 8;
|
|
return Kfloat;
|
|
case 'i':
|
|
*size = getnumlimit(h, fmt, 4);
|
|
return Kint;
|
|
case 'I':
|
|
*size = getnumlimit(h, fmt, 4);
|
|
return Kuint;
|
|
case 's':
|
|
*size = getnumlimit(h, fmt, 4);
|
|
return Kstring;
|
|
case 'c':
|
|
*size = getnum(h, fmt, -1);
|
|
if (*size == -1)
|
|
luaL_error(h->L, "missing size for format option 'c'");
|
|
return Kchar;
|
|
case 'z':
|
|
return Kzstr;
|
|
case 'x':
|
|
*size = 1;
|
|
return Kpadding;
|
|
case 'X':
|
|
return Kpaddalign;
|
|
case ' ':
|
|
break;
|
|
case '<':
|
|
h->islittle = 1;
|
|
break;
|
|
case '>':
|
|
h->islittle = 0;
|
|
break;
|
|
case '=':
|
|
h->islittle = nativeendian.little;
|
|
break;
|
|
case '!':
|
|
h->maxalign = getnumlimit(h, fmt, MAXALIGN);
|
|
break;
|
|
default:
|
|
luaL_error(h->L, "invalid format option '%c'", opt);
|
|
}
|
|
return Knop;
|
|
}
|
|
|
|
/*
|
|
** Read, classify, and fill other details about the next option.
|
|
** 'psize' is filled with option's size, 'notoalign' with its
|
|
** alignment requirements.
|
|
** Local variable 'size' gets the size to be aligned. (Kpadal option
|
|
** always gets its full alignment, other options are limited by
|
|
** the maximum alignment ('maxalign'). Kchar option needs no alignment
|
|
** despite its size.
|
|
*/
|
|
static KOption getdetails(Header* h, size_t totalsize, const char** fmt, int* psize, int* ntoalign)
|
|
{
|
|
KOption opt = getoption(h, fmt, psize);
|
|
int align = *psize; // usually, alignment follows size
|
|
if (opt == Kpaddalign)
|
|
{ // 'X' gets alignment from following option
|
|
if (**fmt == '\0' || getoption(h, fmt, &align) == Kchar || align == 0)
|
|
luaL_argerror(h->L, 1, "invalid next option for option 'X'");
|
|
}
|
|
if (align <= 1 || opt == Kchar) // need no alignment?
|
|
*ntoalign = 0;
|
|
else
|
|
{
|
|
if (align > h->maxalign) // enforce maximum alignment
|
|
align = h->maxalign;
|
|
if ((align & (align - 1)) != 0) // is 'align' not a power of 2?
|
|
luaL_argerror(h->L, 1, "format asks for alignment not power of 2");
|
|
*ntoalign = (align - (int)(totalsize & (align - 1))) & (align - 1);
|
|
}
|
|
return opt;
|
|
}
|
|
|
|
/*
|
|
** Pack integer 'n' with 'size' bytes and 'islittle' endianness.
|
|
** The final 'if' handles the case when 'size' is larger than
|
|
** the size of a Lua integer, correcting the extra sign-extension
|
|
** bytes if necessary (by default they would be zeros).
|
|
*/
|
|
static void packint(luaL_Strbuf* b, unsigned long long n, int islittle, int size, int neg)
|
|
{
|
|
LUAU_ASSERT(size <= MAXINTSIZE);
|
|
char buff[MAXINTSIZE];
|
|
int i;
|
|
buff[islittle ? 0 : size - 1] = (char)(n & MC); // first byte
|
|
for (i = 1; i < size; i++)
|
|
{
|
|
n >>= NB;
|
|
buff[islittle ? i : size - 1 - i] = (char)(n & MC);
|
|
}
|
|
if (neg && size > SZINT)
|
|
{ // negative number need sign extension?
|
|
for (i = SZINT; i < size; i++) // correct extra bytes
|
|
buff[islittle ? i : size - 1 - i] = (char)MC;
|
|
}
|
|
luaL_addlstring(b, buff, size); // add result to buffer
|
|
}
|
|
|
|
/*
|
|
** Copy 'size' bytes from 'src' to 'dest', correcting endianness if
|
|
** given 'islittle' is different from native endianness.
|
|
*/
|
|
static void copywithendian(volatile char* dest, volatile const char* src, int size, int islittle)
|
|
{
|
|
if (islittle == nativeendian.little)
|
|
{
|
|
while (size-- != 0)
|
|
*(dest++) = *(src++);
|
|
}
|
|
else
|
|
{
|
|
dest += size - 1;
|
|
while (size-- != 0)
|
|
*(dest--) = *(src++);
|
|
}
|
|
}
|
|
|
|
static int str_pack(lua_State* L)
|
|
{
|
|
luaL_Strbuf b;
|
|
Header h;
|
|
const char* fmt = luaL_checkstring(L, 1); // format string
|
|
int arg = 1; // current argument to pack
|
|
size_t totalsize = 0; // accumulate total size of result
|
|
initheader(L, &h);
|
|
lua_pushnil(L); // mark to separate arguments from string buffer
|
|
luaL_buffinit(L, &b);
|
|
while (*fmt != '\0')
|
|
{
|
|
int size, ntoalign;
|
|
KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign);
|
|
totalsize += ntoalign + size;
|
|
while (ntoalign-- > 0)
|
|
luaL_addchar(&b, LUAL_PACKPADBYTE); // fill alignment
|
|
arg++;
|
|
switch (opt)
|
|
{
|
|
case Kint:
|
|
{ // signed integers
|
|
long long n = (long long)luaL_checknumber(L, arg);
|
|
if (size < SZINT)
|
|
{ // need overflow check?
|
|
long long lim = (long long)1 << ((size * NB) - 1);
|
|
luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow");
|
|
}
|
|
packint(&b, n, h.islittle, size, (n < 0));
|
|
break;
|
|
}
|
|
case Kuint:
|
|
{ // unsigned integers
|
|
long long n = (long long)luaL_checknumber(L, arg);
|
|
if (size < SZINT) // need overflow check?
|
|
luaL_argcheck(L, (unsigned long long)n < ((unsigned long long)1 << (size * NB)), arg, "unsigned overflow");
|
|
packint(&b, (unsigned long long)n, h.islittle, size, 0);
|
|
break;
|
|
}
|
|
case Kfloat:
|
|
{ // floating-point options
|
|
volatile Ftypes u;
|
|
char buff[MAXINTSIZE];
|
|
double n = luaL_checknumber(L, arg); // get argument
|
|
if (size == sizeof(u.f))
|
|
u.f = (float)n; // copy it into 'u'
|
|
else if (size == sizeof(u.d))
|
|
u.d = (double)n;
|
|
else
|
|
u.n = n;
|
|
// move 'u' to final result, correcting endianness if needed
|
|
copywithendian(buff, u.buff, size, h.islittle);
|
|
luaL_addlstring(&b, buff, size);
|
|
break;
|
|
}
|
|
case Kchar:
|
|
{ // fixed-size string
|
|
size_t len;
|
|
const char* s = luaL_checklstring(L, arg, &len);
|
|
luaL_argcheck(L, len <= (size_t)size, arg, "string longer than given size");
|
|
luaL_addlstring(&b, s, len); // add string
|
|
while (len++ < (size_t)size) // pad extra space
|
|
luaL_addchar(&b, LUAL_PACKPADBYTE);
|
|
break;
|
|
}
|
|
case Kstring:
|
|
{ // strings with length count
|
|
size_t len;
|
|
const char* s = luaL_checklstring(L, arg, &len);
|
|
luaL_argcheck(L, size >= (int)sizeof(size_t) || len < ((size_t)1 << (size * NB)), arg, "string length does not fit in given size");
|
|
packint(&b, len, h.islittle, size, 0); // pack length
|
|
luaL_addlstring(&b, s, len);
|
|
totalsize += len;
|
|
break;
|
|
}
|
|
case Kzstr:
|
|
{ // zero-terminated string
|
|
size_t len;
|
|
const char* s = luaL_checklstring(L, arg, &len);
|
|
luaL_argcheck(L, strlen(s) == len, arg, "string contains zeros");
|
|
luaL_addlstring(&b, s, len);
|
|
luaL_addchar(&b, '\0'); // add zero at the end
|
|
totalsize += len + 1;
|
|
break;
|
|
}
|
|
case Kpadding:
|
|
luaL_addchar(&b, LUAL_PACKPADBYTE); // FALLTHROUGH
|
|
case Kpaddalign:
|
|
case Knop:
|
|
arg--; // undo increment
|
|
break;
|
|
}
|
|
}
|
|
luaL_pushresult(&b);
|
|
return 1;
|
|
}
|
|
|
|
static int str_packsize(lua_State* L)
|
|
{
|
|
Header h;
|
|
const char* fmt = luaL_checkstring(L, 1); // format string
|
|
int totalsize = 0; // accumulate total size of result
|
|
initheader(L, &h);
|
|
while (*fmt != '\0')
|
|
{
|
|
int size, ntoalign;
|
|
KOption opt = getdetails(&h, totalsize, &fmt, &size, &ntoalign);
|
|
luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1, "variable-length format");
|
|
size += ntoalign; // total space used by option
|
|
luaL_argcheck(L, totalsize <= MAXSSIZE - size, 1, "format result too large");
|
|
totalsize += size;
|
|
}
|
|
lua_pushinteger(L, totalsize);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** Unpack an integer with 'size' bytes and 'islittle' endianness.
|
|
** If size is smaller than the size of a Lua integer and integer
|
|
** is signed, must do sign extension (propagating the sign to the
|
|
** higher bits); if size is larger than the size of a Lua integer,
|
|
** it must check the unread bytes to see whether they do not cause an
|
|
** overflow.
|
|
*/
|
|
static long long unpackint(lua_State* L, const char* str, int islittle, int size, int issigned)
|
|
{
|
|
unsigned long long res = 0;
|
|
int i;
|
|
int limit = (size <= SZINT) ? size : SZINT;
|
|
for (i = limit - 1; i >= 0; i--)
|
|
{
|
|
res <<= NB;
|
|
res |= (unsigned char)str[islittle ? i : size - 1 - i];
|
|
}
|
|
if (size < SZINT)
|
|
{ // real size smaller than int?
|
|
if (issigned)
|
|
{ // needs sign extension?
|
|
unsigned long long mask = (unsigned long long)1 << (size * NB - 1);
|
|
res = ((res ^ mask) - mask); // do sign extension
|
|
}
|
|
}
|
|
else if (size > SZINT)
|
|
{ // must check unread bytes
|
|
int mask = (!issigned || (long long)res >= 0) ? 0 : MC;
|
|
for (i = limit; i < size; i++)
|
|
{
|
|
if ((unsigned char)str[islittle ? i : size - 1 - i] != mask)
|
|
luaL_error(L, "%d-byte integer does not fit into Lua Integer", size);
|
|
}
|
|
}
|
|
return (long long)res;
|
|
}
|
|
|
|
static int str_unpack(lua_State* L)
|
|
{
|
|
Header h;
|
|
const char* fmt = luaL_checkstring(L, 1);
|
|
size_t ld;
|
|
const char* data = luaL_checklstring(L, 2, &ld);
|
|
int pos = posrelat(luaL_optinteger(L, 3, 1), ld) - 1;
|
|
if (pos < 0)
|
|
pos = 0;
|
|
int n = 0; // number of results
|
|
luaL_argcheck(L, size_t(pos) <= ld, 3, "initial position out of string");
|
|
initheader(L, &h);
|
|
while (*fmt != '\0')
|
|
{
|
|
int size, ntoalign;
|
|
KOption opt = getdetails(&h, pos, &fmt, &size, &ntoalign);
|
|
luaL_argcheck(L, (size_t)ntoalign + size <= ld - pos, 2, "data string too short");
|
|
pos += ntoalign; // skip alignment
|
|
// stack space for item + next position
|
|
luaL_checkstack(L, 2, "too many results");
|
|
n++;
|
|
switch (opt)
|
|
{
|
|
case Kint:
|
|
{
|
|
long long res = unpackint(L, data + pos, h.islittle, size, true);
|
|
lua_pushnumber(L, (double)res);
|
|
break;
|
|
}
|
|
case Kuint:
|
|
{
|
|
unsigned long long res = unpackint(L, data + pos, h.islittle, size, false);
|
|
lua_pushnumber(L, (double)res);
|
|
break;
|
|
}
|
|
case Kfloat:
|
|
{
|
|
volatile Ftypes u;
|
|
double num;
|
|
copywithendian(u.buff, data + pos, size, h.islittle);
|
|
if (size == sizeof(u.f))
|
|
num = (double)u.f;
|
|
else if (size == sizeof(u.d))
|
|
num = (double)u.d;
|
|
else
|
|
num = u.n;
|
|
lua_pushnumber(L, num);
|
|
break;
|
|
}
|
|
case Kchar:
|
|
{
|
|
lua_pushlstring(L, data + pos, size);
|
|
break;
|
|
}
|
|
case Kstring:
|
|
{
|
|
size_t len = (size_t)unpackint(L, data + pos, h.islittle, size, 0);
|
|
luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short");
|
|
lua_pushlstring(L, data + pos + size, len);
|
|
pos += (int)len; // skip string
|
|
break;
|
|
}
|
|
case Kzstr:
|
|
{
|
|
size_t len = strlen(data + pos);
|
|
luaL_argcheck(L, pos + len < ld, 2, "unfinished string for format 'z'");
|
|
lua_pushlstring(L, data + pos, len);
|
|
pos += (int)len + 1; // skip string plus final '\0'
|
|
break;
|
|
}
|
|
case Kpaddalign:
|
|
case Kpadding:
|
|
case Knop:
|
|
n--; // undo increment
|
|
break;
|
|
}
|
|
pos += size;
|
|
}
|
|
lua_pushinteger(L, pos + 1); // next position
|
|
return n + 1;
|
|
}
|
|
|
|
// }======================================================
|
|
|
|
static const luaL_Reg strlib[] = {
|
|
{"byte", str_byte},
|
|
{"char", str_char},
|
|
{"find", str_find},
|
|
{"format", str_format},
|
|
{"gmatch", gmatch},
|
|
{"gsub", str_gsub},
|
|
{"len", str_len},
|
|
{"lower", str_lower},
|
|
{"match", str_match},
|
|
{"rep", str_rep},
|
|
{"reverse", str_reverse},
|
|
{"sub", str_sub},
|
|
{"upper", str_upper},
|
|
{"split", str_split},
|
|
{"pack", str_pack},
|
|
{"packsize", str_packsize},
|
|
{"unpack", str_unpack},
|
|
{NULL, NULL},
|
|
};
|
|
|
|
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
|
|
*/
|
|
int luaopen_string(lua_State* L)
|
|
{
|
|
luaL_register(L, LUA_STRLIBNAME, strlib);
|
|
createmetatable(L);
|
|
|
|
return 1;
|
|
}
|