Adds #90 - Added strictness Lua module

master
Yonaba 2015-09-21 17:09:31 +00:00
parent be42334fb7
commit fefec5d721
7 changed files with 1247 additions and 0 deletions

View File

@ -0,0 +1,20 @@
CHANGELOG
=========
###0.2.0 (06/11/14)
* `strictness` no longer create globals. It returns a local table of library functions.
* `strictness` can now create (or convert) strict/unstrict tables (or environnements).
* Strict/unstrict rules are now applied per table (or environnement).
* Strict mode enforces variable declarations and complain on undefined fields access/assignment.
* Tables (or environnements) already existing metatables are preserved, including `__index` and `__newindex` fields.
* Added `strictness.strict` to convert a normal table (or environnement) to a strict one.
* Added `strictness.unstrict` to convert a strict table (or environnement) to a normal one.
* Added `strictness.is_strict` to check if a table (or environnement) is strict.
* Added `strictness.strictf` to wrap a normal function into a non strict function.
* Added `strictness.unstrictf` to wrap a normal function into a strict function.
* Added `strictness.run_strict` to run a normal function in strict mode.
* Added `strictness.run_unstrict` to run a normal function in non strict mode.
* Made compliant with Lua 5.2 new _ENV lexical scoping (although strictly speaking there are no globals in Lua 5.2).
###0.1.0 (10/05/13)
* Initial release

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2014 Roland Y.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,63 @@
strictness
===========
[![Build Status](https://travis-ci.org/Yonaba/strictness.png)](https://travis-ci.org/Yonaba/strictness)
[![Coverage Status](https://coveralls.io/repos/Yonaba/strictness/badge.png?branch=master)](https://coveralls.io/r/Yonaba/strictness?branch=master)
[![License](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE)
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](http://www.lua.org/pil/1.2.html). 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 `_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](http://www.luafaq.org/#T8.2.1).
*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](http://www.lua.org/versions.html#5.1), but it is compatible with Lua [5.2](http://www.lua.org/versions.html#5.2).
##Installation
####Git
git clone git://github.com/Yonaba/strictness
####Download
* See [releases](https://github.com/Yonaba/strictness/releases)
####LuaRocks
luarocks install strictness
####MoonRocks
moonrocks install strictness
or
luarocks install strictness --server=http://rocks.moonscript.org strictness
## Documentation
See [tutorial.md](doc/tutorial.md).
##Tests
This project has specification tests. To run these tests, execute the following command from the project root folder:
lua spec/tests.lua
##Similar projects
Feel free to check those alternate implementations, from with *strictness* takes some inspiration:
* [strict.lua](http://rtfc.googlecode.com/svn-history/r2/trunk/lua-5.1/etc/strict.lua) which is included in the official Lua 5.1 distribution,
* [pl.strict](https://github.com/stevedonovan/Penlight/blob/master/lua/pl/strict.lua) which is part of [Penlight](https://github.com/stevedonovan/Penlight),
##License
This work is under [MIT-LICENSE](http://www.opensource.org/licenses/mit-license.php)<br/>
*Copyright (c) 2013-2014 Roland Yonaba*.
See [LICENSE](LICENSE).
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/Yonaba/strictness/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

View File

@ -0,0 +1,306 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>strictness documentation</title>
<link rel="stylesheet" href="ldoc.css" type="text/css" />
</head>
<body>
<div id="container">
<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->
<div id="main">
<!-- Menu -->
<div id="navigation">
<br/>
<h1>strictness</h1>
<h2>Contents</h2>
<ul>
<li><a href="#Functions">Functions</a></li>
</ul>
<h2>Modules</h2>
<ul class="$(kind=='Topics' and '' or 'nowrap'">
<li><strong>strictness</strong></li>
</ul>
</div>
<div id="content">
<h1>Module <code>strictness</code></h1>
<p><em>strictness, a "strict" mode for Lua</em>.</p>
<p> Source on <a href="http://github.com/Yonaba/strictness">Github</a></p>
<h3>Info:</h3>
<ul>
<li><strong>Copyright</strong>: 2013-2014</li>
<li><strong>License</strong>: MIT</li>
<li><strong>Author</strong>: Roland Yonaba</li>
</ul>
<h2><a href="#Functions">Functions</a></h2>
<table class="function_list">
<tr>
<td class="name" nowrap><a href="#strict">strict ([t[, ...]])</a></td>
<td class="summary">Makes a given table strict.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#is_strict">is_strict (t)</a></td>
<td class="summary">Checks if a given table is strict.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#unstrict">unstrict (t)</a></td>
<td class="summary">Makes a given table non-strict.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#strictf">strictf (f)</a></td>
<td class="summary">Creates a strict function.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#unstrictf">unstrictf (f)</a></td>
<td class="summary">Creates a non-strict function.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#run_strict">run_strict (f[, ...])</a></td>
<td class="summary">Returns the result of a function call in strict mode.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#run_unstrict">run_unstrict (f[, ...])</a></td>
<td class="summary">Returns the result of a function call in non-strict mode.</td>
</tr>
</table>
<br/>
<br/>
<h2><a name="Functions"></a>Functions</h2>
<dl class="function">
<dt>
<a name = "strict"></a>
<strong>strict ([t[, ...]])</strong>
</dt>
<dd>
Makes a given table strict. It mutates the passed-in table (or creates a
new table) and returns it. The returned table is strict, indexing or
assigning undefined fields will raise an error.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">t</span>
a table
</li>
<li><span class="parameter">...</span>
a vararg list of allowed fields in the table.
</li>
</ul>
<h3>Returns:</h3>
<ol>
the passed-in table <code>t</code> or a new table, patched to be strict.
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example">
<span class="keyword">local</span> t = strictness.strict()
<span class="keyword">local</span> t2 = strictness.strict({})
<span class="keyword">local</span> t3 = strictness.strict({}, <span class="string">'field1'</span>, <span class="string">'field2'</span>)</pre>
</ul>
</dd>
<dt>
<a name = "is_strict"></a>
<strong>is_strict (t)</strong>
</dt>
<dd>
Checks if a given table is strict.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">t</span>
a table
</li>
</ul>
<h3>Returns:</h3>
<ol>
<code>true</code> if the table is strict, <code>false</code> otherwise.
</ol>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> is_strict = strictness.is_strict(a_table)</pre>
</ul>
</dd>
<dt>
<a name = "unstrict"></a>
<strong>unstrict (t)</strong>
</dt>
<dd>
Makes a given table non-strict. It mutates the passed-in table and
returns it. The returned table is non-strict.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">t</span>
a table
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> unstrict_table = strictness.unstrict(trict_table)</pre>
</ul>
</dd>
<dt>
<a name = "strictf"></a>
<strong>strictf (f)</strong>
</dt>
<dd>
Creates a strict function. Wraps the given function and returns the wrapper.
The new function will always run in strict mode in its environment, whether
or not this environment is strict.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">f</span>
a function, or a callable value.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">
<span class="keyword">local</span> strict_f = strictness.strictf(a_function)
<span class="keyword">local</span> result = strict_f(...)</pre>
</ul>
</dd>
<dt>
<a name = "unstrictf"></a>
<strong>unstrictf (f)</strong>
</dt>
<dd>
Creates a non-strict function. Wraps the given function and returns the wrapper.
The new function will always run in non-strict mode in its environment, whether
or not this environment is strict.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">f</span>
a function, or a callable value.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example">
<span class="keyword">local</span> unstrict_f = strictness.unstrictf(a_function)
<span class="keyword">local</span> result = unstrict_f(...)</pre>
</ul>
</dd>
<dt>
<a name = "run_strict"></a>
<strong>run_strict (f[, ...])</strong>
</dt>
<dd>
Returns the result of a function call in strict mode.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">f</span>
a function, or a callable value.
</li>
<li><span class="parameter">...</span>
a vararg list of arguments to function <code>f</code>.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> result = strictness.run_strict(a_function, arg1, arg2)</pre>
</ul>
</dd>
<dt>
<a name = "run_unstrict"></a>
<strong>run_unstrict (f[, ...])</strong>
</dt>
<dd>
Returns the result of a function call in non-strict mode.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">f</span>
a function, or a callable value.
</li>
<li><span class="parameter">...</span>
a vararg list of arguments to function <code>f</code>.
</li>
</ul>
<h3>Usage:</h3>
<ul>
<pre class="example"><span class="keyword">local</span> result = strictness.run_unstrict(a_function, arg1, arg2)</pre>
</ul>
</dd>
</dl>
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.2</a></i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@ -0,0 +1,302 @@
/* BEGIN RESET
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.8.2r1
*/
html {
color: #000;
background: #FFF;
}
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset,img {
border: 0;
}
address,caption,cite,code,dfn,em,strong,th,var,optgroup {
font-style: inherit;
font-weight: inherit;
}
del,ins {
text-decoration: none;
}
li {
list-style: disc;
margin-left: 20px;
}
caption,th {
text-align: left;
}
h1,h2,h3,h4,h5,h6 {
font-size: 100%;
font-weight: bold;
}
q:before,q:after {
content: '';
}
abbr,acronym {
border: 0;
font-variant: normal;
}
sup {
vertical-align: baseline;
}
sub {
vertical-align: baseline;
}
legend {
color: #000;
}
input,button,textarea,select,optgroup,option {
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-weight: inherit;
}
input,button,textarea,select {*font-size:100%;
}
/* END RESET */
body {
margin-left: 1em;
margin-right: 1em;
font-family: arial, helvetica, geneva, sans-serif;
background-color: #ffffff; margin: 0px;
}
code, tt { font-family: monospace; }
span.parameter { font-family:monospace; }
span.parameter:after { content:":"; }
span.types:before { content:"("; }
span.types:after { content:")"; }
.type { font-weight: bold; font-style:italic }
body, p, td, th { font-size: .95em; line-height: 1.2em;}
p, ul { margin: 10px 0 0 0px;}
strong { font-weight: bold;}
em { font-style: italic;}
h1 {
font-size: 1.5em;
margin: 0 0 20px 0;
}
h2, h3, h4 { margin: 15px 0 10px 0; }
h2 { font-size: 1.25em; }
h3 { font-size: 1.15em; }
h4 { font-size: 1.06em; }
a:link { font-weight: bold; color: #004080; text-decoration: none; }
a:visited { font-weight: bold; color: #006699; text-decoration: none; }
a:link:hover { text-decoration: underline; }
hr {
color:#cccccc;
background: #00007f;
height: 1px;
}
blockquote { margin-left: 3em; }
ul { list-style-type: disc; }
p.name {
font-family: "Andale Mono", monospace;
padding-top: 1em;
}
pre.example {
background-color: rgb(245, 245, 245);
border: 1px solid silver;
padding: 10px;
margin: 10px 0 10px 0;
font-family: "Andale Mono", monospace;
font-size: .85em;
}
pre {
background-color: rgb(245, 245, 245);
border: 1px solid silver;
padding: 10px;
margin: 10px 0 10px 0;
overflow: auto;
font-family: "Andale Mono", monospace;
}
table.index { border: 1px #00007f; }
table.index td { text-align: left; vertical-align: top; }
#container {
margin-left: 1em;
margin-right: 1em;
background-color: #f0f0f0;
}
#product {
text-align: center;
border-bottom: 1px solid #cccccc;
background-color: #ffffff;
}
#product big {
font-size: 2em;
}
#main {
background-color: #f0f0f0;
border-left: 2px solid #cccccc;
}
#navigation {
float: left;
width: 18em;
vertical-align: top;
background-color: #f0f0f0;
overflow: visible;
}
#navigation h2 {
background-color:#e7e7e7;
font-size:1.1em;
color:#000000;
text-align: left;
padding:0.2em;
border-top:1px solid #dddddd;
border-bottom:1px solid #dddddd;
}
#navigation ul
{
font-size:1em;
list-style-type: none;
margin: 1px 1px 10px 1px;
}
#navigation li {
text-indent: -1em;
display: block;
margin: 3px 0px 0px 22px;
}
#navigation li li a {
margin: 0px 3px 0px -1em;
}
#content {
margin-left: 18em;
padding: 1em;
width: 700px;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
}
#about {
clear: both;
padding: 5px;
border-top: 2px solid #cccccc;
background-color: #ffffff;
}
@media print {
body {
font: 12pt "Times New Roman", "TimeNR", Times, serif;
}
a { font-weight: bold; color: #004080; text-decoration: underline; }
#main {
background-color: #ffffff;
border-left: 0px;
}
#container {
margin-left: 2%;
margin-right: 2%;
background-color: #ffffff;
}
#content {
padding: 1em;
background-color: #ffffff;
}
#navigation {
display: none;
}
pre.example {
font-family: "Andale Mono", monospace;
font-size: 10pt;
page-break-inside: avoid;
}
}
table.module_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.module_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.module_list td.name { background-color: #f0f0f0; min-width: 200px; }
table.module_list td.summary { width: 100%; }
table.function_list {
border-width: 1px;
border-style: solid;
border-color: #cccccc;
border-collapse: collapse;
}
table.function_list td {
border-width: 1px;
padding: 3px;
border-style: solid;
border-color: #cccccc;
}
table.function_list td.name { background-color: #f0f0f0; min-width: 200px; }
table.function_list td.summary { width: 100%; }
ul.nowrap {
overflow:auto;
white-space:nowrap;
}
dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
dl.table h3, dl.function h3 {font-size: .95em;}
/* stop sublists from having initial vertical space */
ul ul { margin-top: 0px; }
ol ul { margin-top: 0px; }
ol ol { margin-top: 0px; }
ul ol { margin-top: 0px; }
/* styles for prettification of source */
pre .comment { color: #558817; }
pre .constant { color: #a8660d; }
pre .escape { color: #844631; }
pre .keyword { color: #2239a8; font-weight: bold; }
pre .library { color: #0e7c6b; }
pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
pre .string { color: #a8660d; }
pre .number { color: #f8660d; }
pre .operator { color: #2239a8; font-weight: bold; }
pre .preprocessor, pre .prepro { color: #a33243; }
pre .global { color: #800080; }
pre .prompt { color: #558817; }
pre .url { color: #272fc2; text-decoration: underline; }

View File

@ -0,0 +1,277 @@
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)**

259
files/lua/strictness.lua Normal file
View File

@ -0,0 +1,259 @@
#!/usr/bin/env lua
------------------
-- *strictness, a "strict" mode for Lua*.
-- Source on [Github](http://github.com/Yonaba/strictness)
-- @author Roland Yonaba
-- @copyright 2013-2014
-- @license MIT
local _LUA52 = _VERSION:match('Lua 5.2')
local setmetatable, getmetatable = setmetatable, getmetatable
local pairs, ipairs = pairs, ipairs
local rawget, rawget = rawget, rawget
local unpack = _LUA52 and table.unpack or unpack
local tostring, select, error = tostring, select, error
local getfenv = getfenv
local _MODULEVERSION = '0.2.0'
----------------------------- Private definitions -----------------------------
if _LUA52 then
-- Provide a replacement for getfenv in Lua 5.2, using the debug library
-- Taken from: http://lua-users.org/lists/lua-l/2010-06/msg00313.html
-- Slightly modified to handle f being nil and return _ENV if f is global.
getfenv = function(f)
f = (type(f) == 'function' and f or debug.getinfo((f or 0) + 1, 'f').func)
local name, val
local up = 0
repeat
up = up + 1
name, val = debug.getupvalue(f, up)
until name == '_ENV' or name == nil
return val~=nil and val or _ENV
end
end
-- Lua reserved keywords
local is_reserved_keyword = {
['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true,
['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true,
['function'] = true, ['if'] = true, ['in'] = true, ['local'] = true,
['nil'] = true, ['not'] = true, ['or'] = true, ['repeat'] = true,
['return'] = true, ['then'] = true, ['true'] = true, ['until'] = true,
['while'] = true,
}; if _LUA52 then is_reserved_keyword['goto'] = true end
-- Throws an error if cond
local function complain_if(cond, msg, level)
return cond and error(msg, level or 3)
end
-- Checks if iden match an valid Lua identifier syntax
local function is_identifier(iden)
return tostring(iden):match('^[%a_]+[%w_]*$') and
not is_reserved_keyword[iden]
end
-- Checks if all elements of vararg are valid Lua identifiers
local function validate_identifiers(...)
local arg, varnames= {...}, {}
for i, iden in ipairs(arg) do
complain_if(not is_identifier(iden),
('varname #%d "<%s>" is not a valid Lua identifier.')
:format(i, tostring(iden)),4)
varnames[iden] = true
end
return varnames
end
-- add true keys in register all keys in t
local function add_allowed_keys(t,register)
for key in pairs(t) do
if is_identifier(key) then register[key] = true end
end
return register
end
-- Checks if the given arg is callable
local function callable(f)
return type(f) == 'function' or (getmetatable(f) and getmetatable(f).__call)
end
------------------------------- Module functions ------------------------------
--- Makes a given table strict. It mutates the passed-in table (or creates a
-- new table) and returns it. The returned table is strict, indexing or
-- assigning undefined fields will raise an error.
-- @function strictness.strict
-- @param[opt] t a table
-- @param[opt] ... a vararg list of allowed fields in the table.
-- @return the passed-in table `t` or a new table, patched to be strict.
-- @usage
-- local t = strictness.strict()
-- local t2 = strictness.strict({})
-- local t3 = strictness.strict({}, 'field1', 'field2')
local function make_table_strict(t, ...)
t = t or {}
local has_mt = getmetatable(t)
complain_if(type(t) ~= 'table',
('Argument #1 should be a table, not %s.'):format(type(t)),3)
local mt = getmetatable(t) or {}
complain_if(mt.__strict,
('<%s> was already made strict.'):format(tostring(t)),3)
local varnames = v
mt.__allowed = add_allowed_keys(t, validate_identifiers(...))
mt.__predefined_index = mt.__index
mt.__predefined_newindex = mt.__newindex
mt.__index = function(tbl, key)
if not mt.__allowed[key] then
if mt.__predefined_index then
local expected_result = mt.__predefined_index(tbl, key)
if expected_result then return expected_result end
end
complain_if(true,
('Attempt to access undeclared variable "%s" in <%s>.')
:format(key, tostring(tbl)),3)
end
return rawget(tbl, key)
end
mt.__newindex = function(tbl, key, val)
if mt.__predefined_newindex then
mt.__predefined_newindex(tbl, key, val)
if rawget(tbl, key) ~= nil then return end
end
if not mt.__allowed[key] then
if val == nil then
mt.__allowed[key] = true
return
end
complain_if(not mt.__allowed[key],
('Attempt to assign value to an undeclared variable "%s" in <%s>.')
:format(key,tostring(tbl)),3)
mt.__allowed[key] = true
end
rawset(tbl, key, val)
end
mt.__strict = true
mt.__has_mt = has_mt
return setmetatable(t, mt)
end
--- Checks if a given table is strict.
-- @function strictness.is_strict
-- @param t a table
-- @return `true` if the table is strict, `false` otherwise.
-- @usage
-- local is_strict = strictness.is_strict(a_table)
local function is_table_strict(t)
complain_if(type(t) ~= 'table',
('Argument #1 should be a table, not %s.'):format(type(t)),3)
return not not (getmetatable(t) and getmetatable(t).__strict)
end
--- Makes a given table non-strict. It mutates the passed-in table and
-- returns it. The returned table is non-strict.
-- @function strictness.unstrict
-- @param t a table
-- @usage
-- local unstrict_table = strictness.unstrict(trict_table)
local function make_table_unstrict(t)
complain_if(type(t) ~= 'table',
('Argument #1 should be a table, not %s.'):format(type(t)),3)
if is_table_strict(t) then
local mt = getmetatable(t)
if not mt.__has_mt then
setmetatable(t, nil)
else
mt.__index, mt.__newindex = mt.__predefined_index, mt.__predefined_newindex
mt.__strict, mt.__allowed, mt.__has_mt = nil, nil, nil
mt.__predefined_index, mt.__predefined_newindex = nil, nil
end
end
return t
end
--- Creates a strict function. Wraps the given function and returns the wrapper.
-- The new function will always run in strict mode in its environment, whether
-- or not this environment is strict.
-- @function strictness.strictf
-- @param f a function, or a callable value.
-- @usage
-- local strict_f = strictness.strictf(a_function)
-- local result = strict_f(...)
local function make_function_strict(f)
complain_if(not callable(f),
('Argument #1 should be a callable, not %s.'):format(type(f)),3)
return function(...)
local ENV = getfenv(f)
local was_strict = is_table_strict(ENV)
if not was_strict then make_table_strict(ENV) end
local results = {f(...)}
if not was_strict then make_table_unstrict(ENV) end
return unpack(results)
end
end
--- Creates a non-strict function. Wraps the given function and returns the wrapper.
-- The new function will always run in non-strict mode in its environment, whether
-- or not this environment is strict.
-- @function strictness.unstrictf
-- @param f a function, or a callable value.
-- @usage
-- local unstrict_f = strictness.unstrictf(a_function)
-- local result = unstrict_f(...)
local function make_function_unstrict(f)
complain_if(not callable(f),
('Argument #1 should be a callable, not %s.'):format(type(f)),3)
return function(...)
local ENV = getfenv(f)
local was_strict = is_table_strict(ENV)
make_table_unstrict(ENV)
local results = {f(...)}
if was_strict then make_table_strict(ENV) end
return unpack(results)
end
end
--- Returns the result of a function call in strict mode.
-- @function strictness.run_strict
-- @param f a function, or a callable value.
-- @param[opt] ... a vararg list of arguments to function `f`.
-- @usage
-- local result = strictness.run_strict(a_function, arg1, arg2)
local function run_strict(f,...)
complain_if(not callable(f),
('Argument #1 should be a callable, not %s.'):format(type(f)),3)
return make_function_strict(f)(...)
end
--- Returns the result of a function call in non-strict mode.
-- @function strictness.run_unstrict
-- @param f a function, or a callable value.
-- @param[opt] ... a vararg list of arguments to function `f`.
-- @usage
-- local result = strictness.run_unstrict(a_function, arg1, arg2)
local function run_unstrict(f,...)
complain_if(not callable(f),
('Argument #1 should be a callable, not %s.'):format(type(f)),3)
return make_function_unstrict(f)(...)
end
return {
strict = make_table_strict,
unstrict = make_table_unstrict,
is_strict = is_table_strict,
strictf = make_function_strict,
unstrictf = make_function_unstrict,
run_strict = run_strict,
run_unstrict = run_unstrict,
_VERSION = 'strictness v'.._MODULEVERSION,
_URL = 'http://github.com/Yonaba/strictness',
_LICENSE = 'MIT <http://raw.githubusercontent.com/Yonaba/strictness/master/LICENSE>',
_DESCRIPTION = 'Tracking accesses and assignments to undefined variables in Lua code'
}