Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
[Lib/Lua] strictness, a strict mode for Lua — Gideros Forum

[Lib/Lua] strictness, a strict mode for Lua

Roland_YRoland_Y Member
edited June 2014 in Code snippets
Hi all,

Here is a little piece of code I'd like to share. This is my own crack at the undefined variables problem. This is defnitely not something new, but a new approach to work-around the problem.

With the Lua programming language, undeclared variables are not detected until runtime, as Lua will not complain when loading code. This is releated to the convention that Lua uses : global by default. In other words, when a variable is not recognized as local, it will be interpreted as a global one, and will involve a lookup in the global environment, which is _G (for Lua 5.1). Note that this behaviour has been addressed in Lua 5.2, which strictly speaking has no globals, because of its lexical scoping.

strictness is a module to track access and assignment to undefined variables in your code. It enforces to declare globals and modules variables before assigning them values. As such, it helps having a better control on the scope of variables across the code.

strictness is mostly meant to work with Lua 5.1, but it is compatible with Lua 5.2.

Here is an example of use, plus some code:

The module is required added into your project this way:
local strictness = require 'strictness'

Then you can use the functions provided with the module to patch tables (or modules). A patched table become "strict", meaning that trying to access to some undefined field inside this table will raise an error.
Let us consider this little example. We create a table with two fields, named "x" and "y". We want to get the value at field "x", but instead, we index field "X" (small typo).
local player = {x = 10, y = 10}
print(player.X) --> nil, as "player.X" (undefined) is not the same as "player.x"
Obviously, the value returned is nil and this will remain silent until this value is involved in some computation where it would probably err.
However, in case the table player was made strict, an error will occur right at the line where we wanted to index the undefined field.
local player = {x = 10, y = 10}
strictness.strict(player)
print(player.X) --> error: Attempt to access undeclared variable "X" in <table: 0x0032c8e8>.
Added to that, strictness now offers the possibility to run functions in strict mode. In this mode, a function is not allowed to access/assign values to undefined variables in its environment table.
local env = {}  -- a blank environment for our functions
 
-- A function that writes a varname and assigns it a value 
local function normal_f(varname, value) env[varname] = value end
 
-- Convert the original function to a strict one
local strict_f = strictness.strictf(normal_f)
 
-- set environments for functions
setfenv(normal_f, env)
setfenv(strict_f, env)
 
-- Call the normal function, no error
normal_f("var1", "hello")
print(env.var1) --> "hello"
 
strict_f("var2", "hello") --> produces an error, as "strict_f" cannot write in its environment.
Latest version : 0.2.0
Source: github
Documentation:online module docs
Tutorial:Getting started

Comments

  • ar2rsawseenar2rsawseen Maintainer
    Great, this is one of the points that strictly type language fans are always annoyed off in weakly typed languages.
    Somehow I never get into situations like the typos so don't really see a problem (probably because I almost never use autocompletes)

    but the idea of using such strictness in development or debugging mode is great
  • HubertRonaldHubertRonald Member
    edited June 2014
    @Roland_Y this is Interesting, for me this is something new.

    Hi @ar2rsawseen
    If I for example make the follow in Gideros:
    MyClass = Core.class()
    function MyClass:init()
     local x = 3 --lowercase
     X --uppercase
     
    end
    "X" (uppercase) is a undeclared variable, so is "X" global or local variable?
    ie when I use the scene manager, "X" will be removed in the next scene, of course if "X" is a local variable.

    I read in this link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
    Undeclared variables are always global, in JavaScript

    Any information thanks

    --------------Edit--------------
    If I put only "X" I got this:
    "main.lua:4: '=' expected near 'end'

    I believe that such errors have been expected and prevented from occurring when compile the code, but if I use the next code:
    MyClass = Core.class()
    function MyClass:init()
     local x = 3 --lowercase
     X = x+1 --uppercase
     
    end
    There isn't way to tell that you have put "X" instead of "x"
  • @ar2rsawseen

    Good point. My example might be a little off, but those kind of issues are much more likely to happen with large projects, especially when you have to deal with multiple files. Typos are very likely to happen, no matter how experienced one is. I do know something about that :)

    @HubertRonald: In your first example, the code itself is not correct. Since "X" is not a valid Lua statement, the code won't even compile.
    In the second example, though, you define a local variable and assign it a value, which is 3. Then right after, you will create a global variable, named "X", which will be avilable from anywhere else in the code, as soon as the function MyClass.init() is called once.
    This new global "X" will be assigned the value 4 (which is the value of local "x" plus 1).
    So, maybe you were expecting to increment the local variable "x", finding this odd might be quite nifty.

    Using *strictness*, this is unlikely to happen, since when calling MyClass.init() for the first time, it will detect that "X" is an undeclared global, and an error will be raised, so that you can fix the typo...
  • Thanks for your replay @Roland_Y

    I tried the following code:
    local function globalVariable()
    	for k,v in pairs(_G) do
    		print(k,v)
    	end
    end
     
    globalVariable()
     
    MyClass = Core.class()
    function MyClass:init()
     local x = 3 --lowercase
     X = x+1 --uppercase
     
    end
     
    MyClass = nil
    print(""); globalVariable()
    and "X" is deleted when MyClass = nil then I think that can't be global "X" if I use classes Gideros or maybe I'm missing something. In any case when I change scene with scene.manager of Gideros, that variable "X" will disappear.

    Thanks again for share and comment
  • @HubertRonald

    No, it doesn't work that way.
    First of all, you printed all the contents of the global environment _G.
    Then, you defined a global object, named MyClass (instance of Gideros' Core.class), and appended a method named init() to it.
    Then right after, you deleted that object. Therefore, you cannot find any global "X" because the init method was never run.

    Run it, even once, you will definitely spot a new global "X". :)

  • HubertRonaldHubertRonald Member
    edited June 2014
    Hi @Roland_Y I don't know what I was thinking (maybe in my lunch :D), you're right

    I tried the following code:
    local function globalVariable()
    	for k,v in pairs(_G) do
    		print(k,v)
    	end
    end
     
    globalVariable()
     
    MyClass = Core.class()
    function MyClass:init()
     local x = 3 -- lowercase
     X = x+1 -- uppercase (for this case: It's a typing mistake intentional "X" instead of "x")
     
    end
     
    NClass = MyClass.new()
    NClass = nil
    print(""); globalVariable()
    And YES... "X = 4" is a global variable.

    Amazing what I have learned today :D
    Thanks @Roland_Y keep it up!
  • HubertRonaldHubertRonald Member
    edited June 2014
    I forgot to conclusion:

    Undeclared variables are always global, in Lua too
    This is similar to JavaScript
  • john26john26 Maintainer
    Very impressive and useful. It might be worth checking other approaches to this also eg strict.lua

    http://www.luafaq.org/#T1.6

  • @HubertRonald

    Yes, that is true, but just for Lua 5.1, though. Actually, when Lua wants to access/assign a variable, and cannot find a local (or an upvalue) within the scope of the current code context, it ends up looking up in the global environment _G: that is what makes it a global.
    In Lua 5.2 though, this is not the same. Actually, Lua 5.2 (strictly speaking has no global) has a lexical scoping scheme. _G is still there, but actually, an undefined variable raises a lookup in a different table, _ENV, which can be _G, or can be redefined in a new code segment. That feature is quite powerful :)

    @john26 : Thanks for the kind words. Actually, this library was inspired by strict.lua. Although, I wanted something better because strict.lua only patches the global table _G and set it a custom metatable. In case your current code has a metatable appended to _G, strict.lua would override it. Also, I wanted to be able to spot access to undefined fields in any random table, and strict.lua would not allow that. Last, strict.lua only catches undefined global only in the main program, via the debug library. I tried to build something that would provide some of the missing features (IMHO) in strict.lua through this.


Sign In or Register to comment.