2015-09-21 17:09:31 +00:00

277 lines
8.0 KiB
Markdown

strictness tutorial
===================
# <a name='TOC'>Table of Contents</a>
* [What is *strictness* ?](#what)
* [Tutorial](#tuto)
* [Adding *strictness* to your project](#adding)
* [The *strictness* module](#module)
* [Strict tables](#stricttables)
* [Non-strict tables](#unstricttables)
* [Checking strictness](#checking)
* [Strict functions](#strictf)
* [Non-strict functions](#unstrictf)
* [Combo functions](#combo)
* [License](#license)
# <a name='what'>What is *strictness* ?</a>
*strictness* is a Lua module for tracking accesses and assignements to indefined variables in Lua code. It is actually known that [undefined variables](http://lua-users.org/wiki/DetectingUndefinedVariables) 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](http://lua-users.org/wiki/StrictStructs), so that accessing undefined fields will always throw an error.
**[[⬆]](#TOC)**
# <a name='tuto'>Tutorial</a>
## <a name='adding'>Adding *strictness* to your project</a>
Place the file [strictness.lua](strictness.lua) in your Lua project and call it with [require](http://pgl.yoyo.org/luai/i/require). *strictness* does not write anything in the global (or the current) environnement. It rather returns a local module of functions.
```lua
local strictness = require "strictness"
````
**[[⬆]](#TOC)**
## <a name='module'>The *strictness* module</a>
### <a name='stricttables'>Strict tables</a>
*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:
```lua
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.
```lua
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:
```lua
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.
```lua
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:
```lua
local t = {}
strictness.strict(t)
strictness.strict(t) --> this will produce an error
````
````
...\test.lua:3: <table: 0x0032c110> was already made strict.
````
**[[⬆]](#TOC)**
### <a name='unstricttables'>Non-Strict (or normal) tables</a>
A strict table can be converted back to a normal one via `strictness.unstrict`:
```lua
local t = strictness.strict()
strictness.unstrict(t)
t.k = 5
print(t.k) --> 5
````
**[[⬆]](#TOC)**
### <a name='checking'>Checking strictness</a>
`strictness.is_strict` checks if a given table was patched via `strictness.strict`:
```lua
local strict_table = strictness.strict()
local normal_table = {}
print(strictness.is_strict(strict_table)) --> true
print(strictness.is_strict(normal_table)) --> false
````
**[[⬆]](#TOC)**
### <a name='strictf'>Strict functions</a>
`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:
```lua
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.
**[[⬆]](#TOC)**
### <a name='unstrictf'>Non-strict functions</a>
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.
```lua
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:
```lua
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
````
```lua
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
````
**[[⬆]](#TOC)**
### <a name='combo'>Combo functions</a>
*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:
```lua
local strict_f = strictness.strictf(f)
strict_f(...)
````
While `strictness.run_unstrict` is a short for:
```lua
local unstrict_f = strictness.unstrictf(f)
unstrict_f(...)
````
Here is an example for `strictness.run_strict`:
```lua
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``:
```lua
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
````
**[[⬆]](#TOC)**
# <a name='license'>LICENSE</a>
This work is under [MIT-LICENSE](http://www.opensource.org/licenses/mit-license.php)<br/>
*Copyright (c) 2013-2014 Roland Yonaba*.<br/>
See [LICENSE](http://github.com/Yonaba/strictness/blob/master/LICENSE).
**[[⬆]](#TOC)**