Added a warning system to Lua

The warning system is just a way for Lua to emit warnings, messages
to the programmer that do not interfere with the running program.
This commit is contained in:
Roberto Ierusalimschy 2018-12-28 15:42:34 -02:00
parent ba7da13ec5
commit 437a5b07d4
10 changed files with 173 additions and 15 deletions

18
lapi.c
View File

@ -1267,6 +1267,24 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) {
}
void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
lua_lock(L);
G(L)->ud_warn = ud;
G(L)->warnf = f;
lua_unlock(L);
}
void lua_warning (lua_State *L, const char *msg) {
lua_WarnFunction wf = G(L)->warnf;
lua_lock(L);
if (wf != NULL)
wf(&G(L)->ud_warn, msg);
lua_unlock(L);
}
LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) {
Udata *u;
lua_lock(L);

View File

@ -986,9 +986,35 @@ static int panic (lua_State *L) {
}
/*
** checks whether 'message' ends with end-of-line
** (and therefore is the last part of a warning)
*/
static int islast (const char *message) {
size_t len = strlen(message);
return (len > 0 && message[len - 1] == '\n');
}
/*
** Emit a warning. If '*pud' is NULL, previous message was to be
** continued by the current one.
*/
static void warnf (void **pud, const char *message) {
if (*pud == NULL) /* previous message was not the last? */
lua_writestringerror("%s", message);
else /* start a new warning */
lua_writestringerror("Lua warning: %s", message);
*pud = (islast(message)) ? pud : NULL;
}
LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) lua_atpanic(L, &panic);
if (L) {
lua_atpanic(L, &panic);
lua_setwarnf(L, warnf, L);
}
return L;
}

View File

@ -43,6 +43,13 @@ static int luaB_print (lua_State *L) {
}
static int luaB_warn (lua_State *L) {
const char *msg = luaL_checkstring(L, 1);
lua_warning(L, msg);
return 0;
}
#define SPACECHARS " \f\n\r\t\v"
static const char *b_str2int (const char *s, int base, lua_Integer *pn) {
@ -482,6 +489,7 @@ static const luaL_Reg base_funcs[] = {
{"pairs", luaB_pairs},
{"pcall", luaB_pcall},
{"print", luaB_print},
{"warn", luaB_warn},
{"rawequal", luaB_rawequal},
{"rawlen", luaB_rawlen},
{"rawget", luaB_rawget},

View File

@ -365,6 +365,8 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
L->next = NULL;
g->frealloc = f;
g->ud = ud;
g->warnf = NULL;
g->ud_warn = NULL;
g->mainthread = L;
g->seed = luai_makeseed(L);
g->gcrunning = 0; /* no GC while building state */

View File

@ -231,6 +231,8 @@ typedef struct global_State {
TString *tmname[TM_N]; /* array with tag-method names */
struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */
lua_WarnFunction warnf; /* warning function */
void *ud_warn; /* auxiliary data to 'warnf' */
} global_State;

View File

@ -63,10 +63,36 @@ static void pushobject (lua_State *L, const TValue *o) {
}
static void badexit (void) {
/* avoid assertion failures when exiting */
l_memcontrol.numblocks = l_memcontrol.total = 0;
exit(EXIT_FAILURE);
}
static int tpanic (lua_State *L) {
fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(L, -1));
return (exit(EXIT_FAILURE), 0); /* do not return to Lua */
return (badexit(), 0); /* do not return to Lua */
}
static int islast (const char *message) {
size_t len = strlen(message);
return (len > 0 && message[len - 1] == '\n');
}
static void warnf (void **pud, const char *msg) {
if (*pud == NULL) /* continuation line? */
printf("%s", msg); /* print it */
else if (msg[0] == '*') /* expected warning? */
printf("Expected Lua warning: %s", msg + 1); /* print without the star */
else { /* a real warning; should not happen during tests */
fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg);
badexit();
}
*pud = islast(msg) ? pud : NULL;
}
@ -1405,6 +1431,10 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
const char *msg = getstring;
printf("%s\n", msg);
}
else if EQ("warning") {
const char *msg = getstring;
lua_warning(L1, msg);
}
else if EQ("pushbool") {
lua_pushboolean(L1, getnum);
}
@ -1743,6 +1773,7 @@ static void checkfinalmem (void) {
int luaB_opentests (lua_State *L) {
void *ud;
lua_atpanic(L, &tpanic);
lua_setwarnf(L, &warnf, L);
atexit(checkfinalmem);
lua_assert(lua_getallocf(L, &ud) == debug_realloc);
lua_assert(ud == cast_voidp(&l_memcontrol));

14
lua.h
View File

@ -126,6 +126,13 @@ typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
/*
** Type for warning functions
*/
typedef void (*lua_WarnFunction) (void **pud, const char *msg);
/*
** generic extra include file
@ -299,6 +306,13 @@ LUA_API int (lua_isyieldable) (lua_State *L);
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
/*
** Warning-related functions
*/
LUA_API void (lua_setwarnf) (lua_State *L, lua_WarnFunction f, void *ud);
LUA_API void (lua_warning) (lua_State *L, const char *msg);
/*
** garbage-collection function and options
*/

View File

@ -1795,7 +1795,7 @@ Functions with any detectable difference
(different behavior, different definition) are always different.
Functions created at different times but with no detectable differences
may be classified as equal or not
(depending on internal cashing details).
(depending on internal caching details).
You can change the way that Lua compares tables and userdata
by using the @idx{__eq} metamethod @see{metatable}.
@ -4033,6 +4033,16 @@ for the @Q{newindex} event @see{metatable}.
}
@APIEntry{int lua_setiuservalue (lua_State *L, int index, int n);|
@apii{1,0,-}
Pops a value from the stack and sets it as
the new @id{n}-th user value associated to the
full userdata at the given index.
Returns 0 if the userdata does not have that value.
}
@APIEntry{void lua_setmetatable (lua_State *L, int index);|
@apii{1,0,-}
@ -4066,13 +4076,13 @@ If @id{index} @N{is 0}, then all stack elements are removed.
}
@APIEntry{int lua_setiuservalue (lua_State *L, int index, int n);|
@apii{1,0,-}
@APIEntry{void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud);|
@apii{0,0,-}
Pops a value from the stack and sets it as
the new @id{n}-th user value associated to the
full userdata at the given index.
Returns 0 if the userdata does not have that value.
Sets the @x{warning function} to be used by Lua to emit warnings
@see{lua_WarnFunction}.
The @id{ud} parameter initializes the slot @id{pud} passed to
the warning function.
}
@ -4334,6 +4344,30 @@ Returns the version number of this core.
}
@APIEntry{
typedef void (*lua_WarnFunction) (void **pud, const char *msg);|
The type of @x{warning function}s, called by Lua to emit warnings.
The first parameter is the address of a writable slot,
constant for a given Lua state and
initialized by @Lid{lua_setwarnf}.
The second parameter is the warning message.
This function should assume that
a message not ending with an end-of-line will be
continued by the message in the next call.
}
@APIEntry{
void lua_warning (lua_State *L, const char *msg);|
@apii{0,0,-}
Emits a warning with the given message.
A message not ending with an end-of-line should be
continued in another call to this function.
}
@APIEntry{
typedef int (*lua_Writer) (lua_State *L,
const void* p,
@ -4345,7 +4379,7 @@ Every time it produces another piece of chunk,
@Lid{lua_dump} calls the writer,
passing along the buffer to be written (@id{p}),
its size (@id{sz}),
and the @id{data} parameter supplied to @Lid{lua_dump}.
and the @id{ud} parameter supplied to @Lid{lua_dump}.
The writer returns an error code:
@N{0 means} no errors;
@ -6261,6 +6295,12 @@ The current value of this variable is @St{Lua 5.4}.
}
@LibEntry{warn (message)|
Emits a warning with the given message.
}
@LibEntry{xpcall (f, msgh [, arg1, @Cdots])|
This function is similar to @Lid{pcall},

View File

@ -5,8 +5,8 @@
local version = "Lua 5.4"
if _VERSION ~= version then
io.stderr:write("\nThis test suite is for ", version, ", not for ", _VERSION,
"\nExiting tests\n")
warn(string.format(
"This test suite is for %s, not for %s\nExiting tests\n", version, _VERSION))
return
end
@ -190,11 +190,10 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
print("\ntests not performed:")
warn("*tests not performed:\n ")
for i=1,#msgs do
print(msgs[i])
warn(msgs[i]); warn("\n ")
end
print()
end
-- no test module should define 'debug'
@ -220,6 +219,10 @@ local _G, showmem, print, format, clock, time, difftime, assert, open =
local fname = T and "time-debug.txt" or "time.txt"
local lasttime
warn("*This is "); warn("an expected"); warn(" warning\n")
warn("*This is"); warn(" another one\n")
if not usertests then
-- open file with time of last performed test
local f = io.open(fname)

View File

@ -111,6 +111,20 @@ do -- testing 'rotate'
tcheck(t, {10, 20, 30, 40})
end
-- testing warnings
T.testC([[
warning "*This "
warning "warning "
warning "should be in a"
warning " single line
"
warning "*This should be "
warning "another warning
"
]])
-- testing message handlers
do
local f = T.makeCfunc[[