Skip to content

Latest commit

 

History

History
277 lines (197 loc) · 7.97 KB

tutorial.md

File metadata and controls

277 lines (197 loc) · 7.97 KB

strictness tutorial

strictness is a Lua module for tracking accesses and assignements to indefined variables in Lua code. It is actually known that undefined variables and global variables as well can be very problematic especially when working on large projects and maintaining code that spans across several files.

strictness aims to address this problem by providing a solution similar to strict structs, so that accessing undefined fields will always throw an error.

[⬆]

Place the file strictness.lua in your Lua project and call it with require. strictness does not write anything in the global (or the current) environnement. It rather returns a local module of functions.

local strictness = require "strictness"

[⬆]

strictness provides the function strictness.strict that patches a given table, so that we can no longer access to undefined keys in this table. Let us apply appy this on the global environnement:

strictness.strict(_G)
print(x) --> this line produces an error

The statement print(x)produces the following error:

...\test.lua:2: Attempt to access undeclared variable "x" in <table: 0x00321328>.
```

To avoid this, we now have to __declare explitely__ our globals. Assigning `nil` will do:

```lua
strictness.strict(_G)
x = nil
print(x) --> nil
x = 3
print(x) --> 3

A table can be made strict with allowed varnames.

strictness.strict(_G, 'x', 'y', 'z') -- "varnames x, y and z are allowed"
print(x, y, z) --> nil, nil, nil
x, y, z = 1, 2, 3
print(x, y, z) --> 1, 2, 3

Also, in case nothing is passed to strictness.strict, it will return a new table:

local t = strictness.strict()
print(t.k) --> produces an error
t.k = nil  --> declare a field "k"
print(t.k) --> nil

strictness.strict preserves the metatable of the passed-in table.

local t = setmetatable({}, {
    __call = function() return 'call' end,
    __tostring = function() return t.name end
})
t.name = 'table t'
strictness.strict(t)
print(t()) --> "call"
print(t) --> "table t"

In case a table was already made strict, passing it again to strictness.strict will raise an error:

local t = {}
strictness.strict(t)
strictness.strict(t) --> this will produce an error
...\test.lua:3: <table: 0x0032c110> was already made strict.

[⬆]

A strict table can be converted back to a normal one via strictness.unstrict:

local t = strictness.strict()
strictness.unstrict(t)
t.k = 5
print(t.k) --> 5

[⬆]

strictness.is_strict checks if a given table was patched via strictness.strict:

local strict_table = strictness.strict()
local normal_table = {}

print(strictness.is_strict(strict_table)) --> true
print(strictness.is_strict(normal_table)) --> false

[⬆]

strictness.strictf returns a wrapper function that runs the original function in strict mode. The returned function is not allowed to write or access undefined fields in its environment. Let us draw an example:

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 
...\test.lua:5: Attempt to assign value to an undeclared variable "var2" in <table: 0x0032c440>.

Notice that here, the strict function always run in strict mode whether its environment is strict or not.

[⬆]

Similarly, strictness.unstrictf creates a wrapper function that runs in non-strict mode in its environment. In other terms, the returned function is allowed to access and assign values in its environments, whether or not this environment is strict.

local env = strictness.strict()  -- a blank and strict environment for our functions

-- A function that assigns a value to a variable named "some_var"
local function normal_f(value)  
  some_var = value  
end

-- Converts the original function to a non-strict one
local unstrict_f = strictness.unstrictf(normal_f)

-- set environments for functions
setfenv(normal_f, env)
setfenv(unstrict_f, env)

-- Call the normal function, it should err because its env is strict
normal_f("hello") --> produces an error

-- Call the non-strict function, no error
unstrict_f("hello")
print(env.some_var) --> "hello

Here is an example with Lua 5.2:

local new_env = {print = print} --  a new env
do
  local _ENV = strictness.strict(new_env) -- sets a new strict env for the do..end scope
  local function normal_f(value) some_var = value end -- our normal function
  normal_f(5) --> produces an error, since normal_f cannot write in the strict _ENV
end
local new_env = {print = print} --  a new env
do
  local _ENV = strictness.strict(new_env) -- sets a new strict env for the do..end scope
  local function normal_f(value) some_var = value end -- our normal function
  local unstrict_f = strictness.unstrictf(normal_f) -- the non-strict version of our normal function 
  unstrict_f(5) -- no longer produces error
  print(some_var) --> 5
end

[⬆]

strictness also provides two combo functions, strictness.run_strict and strictness.run_unstrict. Those functions takes a function f plus an optional vararg ... and return the result of the call f(...) in strict and non-strict mode respectively. Syntactically speaking, strictnes.run_strict is the equivalent to this:

local strict_f = strictness.strictf(f)
strict_f(...)

While strictness.run_unstrict is a short for:

local unstrict_f = strictness.unstrictf(f)
unstrict_f(...)

Here is an example for strictness.run_strict:

local strictness = require 'strictness'

local env = {}  -- an environment

-- A function that assigns a value to a variable named "some_var"
local function normal_f(value)  some_var = value  end

setfenv(normal_f, env) -- defines an env for normal_f
strictness.run_strict(normal_f, 3) --> produces an error

And another example with `strictness.run_unstrict``:

local strictness = require 'strictness'

local env = strictness.strict()  -- a strict environment

-- A function that assigns a value to a variable named "some_var"
local function normal_f(value)  some_var = value  end

setfenv(normal_f, env) -- defines an env for normal_f
strictness.run_unstrict`(normal_f, 3) -- no error!
print(env.some_var, some_var) --> 3, nil

[⬆]

This work is under MIT-LICENSE
Copyright (c) 2013-2014 Roland Yonaba.
See LICENSE.

[⬆]