Skip to content

Commit

Permalink
enable "strict mode" in wesnoth lua
Browse files Browse the repository at this point in the history
Enables an ilua feature called "strict mode" in all of our lua
environments. This change causes lua to report an error if a global
variable is used before it is assigned. The benefits of this are:
- Improved maintainability of lua-based add-ons, since you get
better error reporting in case of a typo.
- Improved behavior of the lua interpreter console, since mistyped
or gibberish lines resolve to an error rather than "nil", which
the console treats as a legal return value.

It is possible to disable this or work around it in individual
scripts. For more info see release notes.
  • Loading branch information
cbeck88 committed Nov 22, 2014
1 parent fd68986 commit a65d168
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 35 deletions.
16 changes: 16 additions & 0 deletions RELEASE_NOTES
Expand Up @@ -192,8 +192,24 @@ Note that:

[section="Lua interpreter console"]
As a debugging aide, a lua interpreter console has been added to the game. At time of writing it is accessible from a button the gamestate inspector, also it may be brought up using hotkey '`' by default.

The lua interpreter console supports command-line history, backed up by the GNU readline library. This is an optional dependency, but if you don't have it you won't be able to use the feature.
[/section]

[section="Compatability breaking changes"]
[list]
[*] By default, all lua in wesnoth is now run using "strict globals". This means that you must assign a value to a global variable before using it, or it is a runtime error. Even assigning "nil" is acceptable. While this is strictly speaking a compatability-breaking change, it is quite uncommon that you would require the default behavior for your script to work, and this change greatly improves the maintainability of lua in add-ons, by giving an error message in case of a programmer typo, rather than pretending that everything is fine. This change also improves the behavior of the lua interpreter console.

The implementation is based on a module called "ilua" (interactive lua) available here: http://lua-users.org/files/wiki_insecure/users/steved/ilua.lua

If you must, you can disable the feature in one of the following ways:
[list]
[*] [code] setmetatable(_G, nil) [/code] to disable, and [code] ilua.set_strict() [/code] to re-enable.
[*] [code] result, err = pcall ( possible_global_var ~= nil ) [/code]
[*] [code] result = rawget(_G, "possible_global_var") [/code]
[/list]
[/list]

[section="Example section 2"]
Example contents 2.
[/section]
Expand Down
75 changes: 44 additions & 31 deletions data/lua/ilua.lua
Expand Up @@ -4,36 +4,22 @@
-- will try to print out tables recursively, subject to the pretty_print_limit value.
-- Steve Donovan, 2007
-- Adapted by iceiceice for wesnoth, 2014
-- Retrived from: http://lua-users.org/files/wiki_insecure/users/steved/ilua.lua

local pretty_print_limit = 20
local max_depth = 7
local table_clever = true
local prompt = '> '
local verbose = false
local strict = true
-- suppress strict warnings
_ = true

-- imported global functions
local sub = string.sub
local match = string.match
local find = string.find
local push = table.insert
local pop = table.remove
local append = table.insert
local concat = table.concat
local pack = table.pack
local floor = math.floor

local savef
local collisions = {}
local G_LIB = {}
local declared = {}
local line_handler_fn, global_handler_fn
local print_handlers = {}

ilua = {}
local num_prec
local num_all

local jstack = {}

Expand Down Expand Up @@ -89,13 +75,8 @@ local function join(tbl,delim,limit,depth)
return sub(res,2)
end


function val2str(val)
local function val2str(val)
local tp = type(val)
if print_handlers[tp] then
local s = print_handlers[tp](val)
return s or '?'
end
if tp == 'function' then
return tostring(val)
elseif tp == 'table' then
Expand All @@ -107,23 +88,55 @@ function val2str(val)
elseif tp == 'string' then
return "'"..val.."'"
elseif tp == 'number' then
-- we try only to apply floating-point precision for numbers deemed to be floating-point,
-- unless the 3rd arg to precision() is true.
if num_prec and (num_all or floor(val) ~= val) then
return num_prec:format(val)
else
return tostring(val)
end
-- removed numeric precision features, but we might actually want these... might put them back
return tostring(val)
else
return tostring(val)
end
end

function _pretty_print(...)
arg = table.pack(...)
local function _pretty_print(...)
local arg = pack(...)
for i,val in ipairs(arg) do
print(val2str(val))
end
_G['_'] = arg[1]
end

--
-- strict.lua
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere.
--
local function set_strict()
local mt = getmetatable(_G)
if mt == nil then
mt = {}
setmetatable(_G, mt)
end

local function what ()
local d = debug.getinfo(3, "S")
return d and d.what or "C"
end

mt.__newindex = function (t, n, v)
declared[n] = true
rawset(t, n, v)
end

mt.__index = function (t, n)
if not declared[n] and what() ~= "C" then
error("variable '"..n.."' must be assigned before being used", 2)
end
return rawget(t, n)
end
end

return {
val2str = val2str,
_pretty_print = _pretty_print,
set_strict = set_strict
}
17 changes: 13 additions & 4 deletions src/scripting/lua_kernel_base.cpp
Expand Up @@ -205,8 +205,8 @@ lua_kernel_base::lua_kernel_base(CVideo * video)
while(lua_next(L, -2) != 0) {
lua_pop(L, 1);
char const* function = lua_tostring(L, -1);
if(strcmp(function, "traceback") == 0) continue;
lua_pushnil(L);
if(strcmp(function, "traceback") == 0 || strcmp(function, "getinfo") == 0) continue; //traceback is needed for our error handler
lua_pushnil(L); //getinfo is needed for ilua strict mode
lua_setfield(L, -3, function);
}
lua_pop(L, 1);
Expand Down Expand Up @@ -350,7 +350,16 @@ lua_kernel_base::lua_kernel_base(CVideo * video)
lua_pushstring(L, "lua/ilua.lua");
int result = lua_fileops::intf_require(L);
if (result == 1) {
lua_setglobal(L, "ilua");
//run "ilua.set_strict()"
lua_pushstring(L, "set_strict");
lua_gettable(L, -2);
if (!protected_call(0,0, boost::bind(&lua_kernel_base::log_error, this, _1, _2))) {
cmd_log_ << "Failed to activate strict mode.\n";
} else {
cmd_log_ << "Activated strict mode.\n";
}

lua_setglobal(L, "ilua"); //save ilua table as a global
} else {
cmd_log_ << "Error: failed to load ilua.\n";
}
Expand Down Expand Up @@ -478,7 +487,7 @@ void lua_kernel_base::run(const char * prog) {

// Tests if a program resolves to an expression, and pretty prints it if it is, otherwise it runs it normally. Throws exceptions.
void lua_kernel_base::interactive_run(char const * prog) {
std::string experiment = "_pretty_print(";
std::string experiment = "ilua._pretty_print(";
experiment += prog;
experiment += ")";

Expand Down

0 comments on commit a65d168

Please sign in to comment.