├── doc
└── tree.png
├── benchmark
├── doc
│ ├── tree1.png
│ └── tree7.png
├── torch7capi.lua
├── test.c
├── torch9ffi.lua
├── torch9luaffi.lua
└── README.md
├── dump.lua
├── CMakeLists.txt
├── doc.lua
├── rocks
├── argcheck-0.5.0-0.rockspec
├── argcheck-1.0.0-0.rockspec
└── argcheck-scm-1.rockspec
├── utils.lua
├── env.lua
├── COPYRIGHT.txt
├── init.lua
├── usage.lua
├── test
└── test.lua
├── graph.lua
└── README.md
/doc/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atcold/argcheck/master/doc/tree.png
--------------------------------------------------------------------------------
/benchmark/doc/tree1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atcold/argcheck/master/benchmark/doc/tree1.png
--------------------------------------------------------------------------------
/benchmark/doc/tree7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atcold/argcheck/master/benchmark/doc/tree7.png
--------------------------------------------------------------------------------
/dump.lua:
--------------------------------------------------------------------------------
1 | local doc = require 'argcheck.doc'
2 |
3 | local ffi = require 'ffi'
4 |
5 | doc.__noop = ffi.new('int*')
6 | ffi.gc(doc.__noop,
7 | function()
8 | print(doc.stop())
9 | end)
10 |
11 | doc.record()
12 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR)
2 | CMAKE_POLICY(VERSION 2.6)
3 |
4 | SET(src "")
5 | SET(luasrc
6 | ${CMAKE_CURRENT_SOURCE_DIR}/init.lua
7 | ${CMAKE_CURRENT_SOURCE_DIR}/graph.lua
8 | ${CMAKE_CURRENT_SOURCE_DIR}/env.lua
9 | ${CMAKE_CURRENT_SOURCE_DIR}/utils.lua
10 | ${CMAKE_CURRENT_SOURCE_DIR}/doc.lua
11 | ${CMAKE_CURRENT_SOURCE_DIR}/dump.lua
12 | ${CMAKE_CURRENT_SOURCE_DIR}/usage.lua
13 | )
14 |
15 | INSTALL(FILES ${luasrc} DESTINATION ${LUADIR}/argcheck)
16 |
17 |
--------------------------------------------------------------------------------
/doc.lua:
--------------------------------------------------------------------------------
1 | local doc = {}
2 |
3 | function doc.record()
4 | doc.__record = {}
5 | end
6 |
7 | function doc.stop()
8 | local md = table.concat(doc.__record)
9 | doc.__record = nil
10 | return md
11 | end
12 |
13 | function doc.doc(str)
14 | if doc.__record then
15 | table.insert(doc.__record, str)
16 | end
17 | end
18 |
19 | setmetatable(doc, {__call=
20 | function(self, ...)
21 | return self.doc(...)
22 | end})
23 |
24 | return doc
25 |
--------------------------------------------------------------------------------
/benchmark/torch7capi.lua:
--------------------------------------------------------------------------------
1 | require 'torch'
2 |
3 | local SZ = tonumber(arg[1])
4 | local N = tonumber(arg[2])
5 | local scale = tonumber(arg[3]) or 1
6 |
7 | torch.manualSeed(1111)
8 |
9 | local x = torch.rand(SZ,SZ)
10 | local y = torch.rand(SZ,SZ)
11 |
12 | print('x', x:norm())
13 | print('y', x:norm())
14 | print('running')
15 |
16 | local clk = os.clock()
17 | if scale == 1 then
18 | for i=1,N do
19 | torch.add(y, x, 5)
20 | torch.add(y, x, y)
21 | end
22 | else
23 | for i=1,N do
24 | torch.add(y, x, 5)
25 | torch.add(y, x, scale, y)
26 | end
27 | end
28 | print('time (s)', os.clock()-clk)
29 |
30 | print('x', x:norm())
31 | print('y', y:norm())
32 |
--------------------------------------------------------------------------------
/rocks/argcheck-0.5.0-0.rockspec:
--------------------------------------------------------------------------------
1 | package = "argcheck"
2 | version = "0.5.0-0"
3 |
4 | source = {
5 | url = "git://github.com/torch/argcheck.git",
6 | tag = "0.5.0-0"
7 | }
8 |
9 | description = {
10 | summary = "Advanced function argument checker",
11 | detailed = [[
12 | Argcheck allows you to insure arguments given to a function are correct.
13 | Checks are compiled beforehand, which guarantees little overheads.
14 | ]],
15 | homepage = "https://github.com/torch/argcheck",
16 | license = "BSD"
17 | }
18 |
19 | dependencies = {
20 | "lua >= 5.1",
21 | }
22 |
23 | build = {
24 | type = "builtin",
25 | modules = {
26 | ["argcheck.init"] = "init.lua",
27 | ["argcheck.argtypes"] = "argtypes.lua"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/rocks/argcheck-1.0.0-0.rockspec:
--------------------------------------------------------------------------------
1 | package = "argcheck"
2 | version = "1.0.0-0"
3 |
4 | source = {
5 | url = "git://github.com/torch/argcheck.git",
6 | tag = "1.0.0-0"
7 | }
8 |
9 | description = {
10 | summary = "Advanced function argument checker",
11 | detailed = [[
12 | Argcheck generates specific code for checking arguments of a function. This
13 | allows complex argument checking (possibly with optional values), as well
14 | as function overloading with almost no overhead.
15 | ]],
16 | homepage = "https://github.com/torch/argcheck",
17 | license = "BSD"
18 | }
19 |
20 | dependencies = {
21 | "lua >= 5.1",
22 | }
23 |
24 | build = {
25 | type = "builtin",
26 | modules = {
27 | ["argcheck.init"] = "init.lua",
28 | ["argcheck.env"] = "env.lua",
29 | ["argcheck.utils"] = "utils.lua",
30 | ["argcheck.doc"] = "doc.lua",
31 | ["argcheck.dump"] = "dump.lua"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/rocks/argcheck-scm-1.rockspec:
--------------------------------------------------------------------------------
1 | package = "argcheck"
2 | version = "scm-1"
3 |
4 | source = {
5 | url = "git://github.com/torch/argcheck.git"
6 | }
7 |
8 | description = {
9 | summary = "Advanced function argument checker",
10 | detailed = [[
11 | Argcheck generates specific code for checking arguments of a function. This
12 | allows complex argument checking (possibly with optional values), with almost
13 | no overhead.
14 | ]],
15 | homepage = "https://github.com/torch/argcheck",
16 | license = "BSD"
17 | }
18 |
19 | dependencies = {
20 | "lua >= 5.1",
21 | "sundown >= 1.0"
22 | }
23 |
24 | build = {
25 | type = "builtin",
26 | modules = {
27 | ["argcheck.init"] = "init.lua",
28 | ["argcheck.graph"] = "graph.lua",
29 | ["argcheck.env"] = "env.lua",
30 | ["argcheck.utils"] = "utils.lua",
31 | ["argcheck.doc"] = "doc.lua",
32 | ["argcheck.dump"] = "dump.lua",
33 | ["argcheck.usage"] = "usage.lua"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/utils.lua:
--------------------------------------------------------------------------------
1 | local utils = {}
2 |
3 | function utils.setupvalue(func, name, newvalue, quiet)
4 | local uidx = 0
5 | repeat
6 | uidx = uidx + 1
7 | local uname, value = debug.getupvalue(func, uidx)
8 | if uname == name then
9 | debug.setupvalue(func, uidx, newvalue)
10 | return value -- previous one
11 | end
12 | until uname == nil
13 | if not quiet then
14 | error(string.format('unknown upvalue <%s>', name))
15 | end
16 | end
17 |
18 | function utils.getupvalue(func, name, quiet)
19 | local uidx = 0
20 | repeat
21 | uidx = uidx + 1
22 | local uname, value = debug.getupvalue(func, uidx)
23 | if uname == name then
24 | return value
25 | end
26 | until uname == nil
27 | if not quiet then
28 | error(string.format('unknown upvalue <%s>', name))
29 | end
30 | end
31 |
32 | function utils.duptable(tbl)
33 | local dup = {}
34 | for k,v in pairs(tbl) do
35 | dup[k] = v
36 | end
37 | return dup
38 | end
39 |
40 | return utils
41 |
--------------------------------------------------------------------------------
/benchmark/test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | THDoubleTensor *x = THDoubleTensor_new();
8 | THDoubleTensor *y = THDoubleTensor_new();
9 | THGenerator *_gen = THGenerator_new();
10 |
11 | if(argc != 3 && argc != 4) {
12 | printf("usage: SZ N [SCALE]!\n");
13 | exit(-1);
14 | }
15 | long SZ = atoi(argv[1]);
16 | long N = atoi(argv[2]);
17 | double scale = ((argc == 4) ? atof(argv[3]) : 1.0);
18 | printf("SZ=%ld\n", SZ);
19 | printf("N =%ld\n", N);
20 | printf("scale =%lf\n", scale);
21 |
22 | THLongStorage *size = THLongStorage_newWithSize2(SZ, SZ);
23 |
24 | THRandom_manualSeed(_gen, 1111);
25 |
26 | THDoubleTensor_rand(x, _gen, size);
27 | THDoubleTensor_rand(y, _gen, size);
28 |
29 | printf("x\t%lf\n", THDoubleTensor_normall(x, 2));
30 | printf("y\t%lf\n", THDoubleTensor_normall(x, 2));
31 | clock_t clk = clock();
32 | if(scale == 1.0) {
33 | long i;
34 | for(i = 1; i < N; i++) {
35 | THDoubleTensor_add(y, x, 5);
36 | THDoubleTensor_cadd(y, x, 1, y);
37 | }
38 | } else {
39 | long i;
40 | for(i = 1; i < N; i++) {
41 | THDoubleTensor_add(y, x, 5);
42 | THDoubleTensor_cadd(y, x, scale, y);
43 | }
44 | }
45 | printf("time (s) %lf\n", ((double)(clock()-clk))/((double)CLOCKS_PER_SEC));
46 | printf("x\t%lf\n", THDoubleTensor_normall(x, 2));
47 | printf("y\t%lf\n", THDoubleTensor_normall(y, 2));
48 |
49 | THLongStorage_free(size);
50 | THDoubleTensor_free(x);
51 | THDoubleTensor_free(y);
52 | return 0;
53 | }
54 |
--------------------------------------------------------------------------------
/env.lua:
--------------------------------------------------------------------------------
1 | local env = {}
2 |
3 | -- user configurable function
4 | function env.istype(obj, typename)
5 | local mt = getmetatable(obj)
6 | if type(mt) == 'table' then
7 | local objtype = rawget(mt, '__typename')
8 | if objtype then
9 | return objtype == typename
10 | end
11 | end
12 | return type(obj) == typename
13 | end
14 |
15 | function env.type(obj)
16 | local mt = getmetatable(obj)
17 | if type(mt) == 'table' then
18 | local objtype = rawget(mt, '__typename')
19 | if objtype then
20 | return objtype
21 | end
22 | end
23 | return type(obj)
24 | end
25 |
26 | -- torch specific
27 | if pcall(require, 'torch') then
28 | function env.istype(obj, typename)
29 | local thname = torch.typename(obj)
30 | if thname then
31 | -- __typename (see below) might be absent
32 | local match = thname:match(typename)
33 | if match and (match ~= typename or match == thname) then
34 | return true
35 | end
36 | local mt = torch.getmetatable(thname)
37 | while mt do
38 | if mt.__typename then
39 | match = mt.__typename:match(typename)
40 | if match and (match ~= typename or match == mt.__typename) then
41 | return true
42 | end
43 | end
44 | mt = getmetatable(mt)
45 | end
46 | return false
47 | end
48 | return type(obj) == typename
49 | end
50 | function env.type(obj)
51 | return torch.type(obj)
52 | end
53 | end
54 |
55 | return env
56 |
--------------------------------------------------------------------------------
/COPYRIGHT.txt:
--------------------------------------------------------------------------------
1 | argcheck -- http://github.com/andresy/argcheck
2 |
3 | Copyright (c) 2013-2014 Idiap Research Institute (Ronan Collobert)
4 |
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | 1. Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 |
13 | 2. Redistributions in binary form must reproduce the above copyright
14 | notice, this list of conditions and the following disclaimer in the
15 | documentation and/or other materials provided with the distribution.
16 |
17 | 3. Neither the names of NEC Laboratories American and Idiap Research
18 | Institute nor the names of its contributors may be used to endorse or
19 | promote products derived from this software without specific prior
20 | written permission.
21 |
22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/benchmark/torch9ffi.lua:
--------------------------------------------------------------------------------
1 | local argcheck = require 'argcheck'
2 | local ffi = require 'ffi'
3 | local class = require 'class'
4 |
5 | local SZ = tonumber(arg[1])
6 | local N = tonumber(arg[2])
7 | local scale = tonumber(arg[3]) or 1
8 | local dbg = arg[4] == '1'
9 | local named = arg[5] == '1'
10 |
11 | if named then
12 | print('warning: using named arguments!')
13 | end
14 |
15 | ffi.cdef[[
16 |
17 | typedef struct THLongStorage THLongStorage;
18 | THLongStorage* THLongStorage_newWithSize2(long, long);
19 | void THLongStorage_free(THLongStorage *storage);
20 |
21 | typedef struct THGenerator THGenerator;
22 | THGenerator* THGenerator_new();
23 | void THRandom_manualSeed(THGenerator *_generator, unsigned long the_seed_);
24 |
25 | typedef struct THDoubleTensor THDoubleTensor;
26 | THDoubleTensor *THDoubleTensor_new(void);
27 | void THDoubleTensor_free(THDoubleTensor *self);
28 | void THDoubleTensor_rand(THDoubleTensor *r_, THGenerator *_generator, THLongStorage *size);
29 | void THDoubleTensor_add(THDoubleTensor *r_, THDoubleTensor *t, double value);
30 | void THDoubleTensor_cadd(THDoubleTensor *r_, THDoubleTensor *t, double value, THDoubleTensor *src);
31 | double THDoubleTensor_normall(THDoubleTensor *t, double value);
32 |
33 | ]]
34 |
35 | local status, C = pcall(ffi.load, 'TH')
36 | if not status then
37 | error('please specify path to libTH in your (DY)LD_LIBRARY_PATH')
38 | end
39 |
40 | local DoubleTensor = class('torch.DoubleTensor', ffi.typeof('THDoubleTensor&'))
41 |
42 | function DoubleTensor.new()
43 | local self = C.THDoubleTensor_new()
44 | self = ffi.cast('THDoubleTensor&', self)
45 | ffi.gc(self, C.THDoubleTensor_free)
46 | return self
47 | end
48 |
49 | function DoubleTensor:norm(l)
50 | l = l or 2
51 | return tonumber(C.THDoubleTensor_normall(self, l))
52 | end
53 |
54 | ffi.metatype('THDoubleTensor', getmetatable(DoubleTensor))
55 |
56 | local _gen = C.THGenerator_new()
57 | C.THRandom_manualSeed(_gen, 1111)
58 |
59 | local function rand(a, b)
60 | local size = C.THLongStorage_newWithSize2(a, b)
61 | local self = DoubleTensor()
62 | C.THDoubleTensor_rand(self, _gen, size)
63 | C.THLongStorage_free(size)
64 | return self
65 | end
66 |
67 | local add
68 | local dotgraph
69 |
70 | for _, RealTensor in ipairs{--'torch.ByteTensor', 'torch.ShortTensor', 'torch.FloatTensor',
71 | --'torch.LongTensor', 'torch.IntTensor', 'torch.CharTensor',
72 | 'torch.DoubleTensor'} do
73 |
74 | add = argcheck{
75 | chain = add,
76 | {name="res", type=RealTensor, opt=true},
77 | {name="src", type=RealTensor},
78 | {name="value", type="number"},
79 | call =
80 | function(res, src, value)
81 | res = res or DoubleTensor()
82 | C.THDoubleTensor_add(res, src, value)
83 | return res
84 | end
85 | }
86 |
87 | add, dotgraph = argcheck{
88 | debug = dbg,
89 | overload = add,
90 | {name="res", type=RealTensor, opt=true},
91 | {name="src1", type=RealTensor},
92 | {name="value", type="number", default=1},
93 | {name="src2", type=RealTensor},
94 | call =
95 | function(res, src1, value, src2)
96 | res = res or torch.DoubleTensor()
97 | C.THDoubleTensor_cadd(res, src1, value, src2)
98 | return res
99 | end
100 | }
101 |
102 | end
103 |
104 | if dotgraph then
105 | local f = io.open('argtree.dot', 'w')
106 | f:write(dotgraph)
107 | f:close()
108 | end
109 |
110 | local x = rand(SZ, SZ)
111 | local y = rand(SZ, SZ)
112 |
113 | print('x', x:norm())
114 | print('y', x:norm())
115 | print('running')
116 |
117 | if named then
118 | local clk = os.clock()
119 | if scale == 1 then
120 | for i=1,N do
121 | add{res=y, src=x, value=5}
122 | add{res=y, src1=x, src2=y}
123 | end
124 | else
125 | for i=1,N do
126 | add{res=y, src=x, value=5}
127 | add{res=y, src1=x, value=scale, src2=y}
128 | end
129 | end
130 | print('time (s)', os.clock()-clk)
131 | else
132 | local clk = os.clock()
133 | if scale == 1 then
134 | for i=1,N do
135 | add(y, x, 5)
136 | add(y, x, y)
137 | end
138 | else
139 | for i=1,N do
140 | add(y, x, 5)
141 | add(y, x, scale, y)
142 | end
143 | end
144 | print('time (s)', os.clock()-clk)
145 | end
146 |
147 | print('x', x:norm())
148 | print('y', y:norm())
149 |
--------------------------------------------------------------------------------
/benchmark/torch9luaffi.lua:
--------------------------------------------------------------------------------
1 | local argcheck = require 'argcheck'
2 | local ffi = require 'ffi'
3 |
4 | local env = require 'argcheck.env'
5 |
6 | local SZ = tonumber(arg[1])
7 | local N = tonumber(arg[2])
8 | local scale = tonumber(arg[3]) or 1
9 | local dbg = arg[4] == '1'
10 | local named = arg[5] == '1'
11 |
12 | if named then
13 | print('warning: using named arguments!')
14 | end
15 |
16 | function env.istype(obj, typename)
17 | if type(obj) == 'userdata' then
18 | if typename == 'torch.DoubleTensor' then
19 | return true
20 | else
21 | return false
22 | end
23 | end
24 | return type(obj) == typename
25 | end
26 |
27 | ffi.cdef[[
28 |
29 | typedef struct THLongStorage THLongStorage;
30 | THLongStorage* THLongStorage_newWithSize2(long, long);
31 | void THLongStorage_free(THLongStorage *storage);
32 |
33 | typedef struct THGenerator THGenerator;
34 | THGenerator* THGenerator_new();
35 | void THRandom_manualSeed(THGenerator *_generator, unsigned long the_seed_);
36 |
37 | typedef struct THDoubleTensor THDoubleTensor;
38 | THDoubleTensor *THDoubleTensor_new(void);
39 | void THDoubleTensor_free(THDoubleTensor *self);
40 | void THDoubleTensor_rand(THDoubleTensor *r_, THGenerator *_generator, THLongStorage *size);
41 | void THDoubleTensor_add(THDoubleTensor *r_, THDoubleTensor *t, double value);
42 | void THDoubleTensor_cadd(THDoubleTensor *r_, THDoubleTensor *t, double value, THDoubleTensor *src);
43 | double THDoubleTensor_normall(THDoubleTensor *t, double value);
44 |
45 | ]]
46 |
47 | local status, C = pcall(ffi.load, ffi.os == 'OSX' and 'libTH.dylib' or 'libTH.so')
48 | if not status then
49 | error('please specify path to libTH in your (DY)LD_LIBRARY_PATH')
50 | end
51 |
52 | local DoubleTensor = {}
53 |
54 | function DoubleTensor_new()
55 | local self = C.THDoubleTensor_new()
56 | ffi.gc(self, C.THDoubleTensor_free)
57 | return self
58 | end
59 |
60 | function DoubleTensor:norm(l)
61 | l = l or 2
62 | return tonumber(C.THDoubleTensor_normall(self, l))
63 | end
64 |
65 | DoubleTensor_mt = {__index=DoubleTensor, __new=DoubleTensor_new}
66 | DoubleTensor = ffi.metatype('THDoubleTensor', DoubleTensor_mt)
67 |
68 | local _gen = C.THGenerator_new()
69 | C.THRandom_manualSeed(_gen, 1111)
70 |
71 | local function rand(a, b)
72 | local size = C.THLongStorage_newWithSize2(a, b)
73 | local self = DoubleTensor()
74 | C.THDoubleTensor_rand(self, _gen, size)
75 | C.THLongStorage_free(size)
76 | return self
77 | end
78 |
79 | local add
80 | local dotgraph
81 |
82 | for _, RealTensor in ipairs{'torch.ByteTensor', 'torch.ShortTensor', 'torch.FloatTensor',
83 | 'torch.LongTensor', 'torch.IntTensor', 'torch.CharTensor',
84 | 'torch.DoubleTensor'} do
85 |
86 | add = argcheck{
87 | {name="res", type=RealTensor, opt=true},
88 | {name="src", type=RealTensor},
89 | {name="value", type="number"},
90 | call =
91 | function(res, src, value)
92 | res = res or DoubleTensor()
93 | C.THDoubleTensor_add(res, src, value)
94 | return res
95 | end
96 | }
97 |
98 | add, dotgraph = argcheck{
99 | debug=dbg,
100 | overload = add,
101 | {name="res", type=RealTensor, opt=true},
102 | {name="src1", type=RealTensor},
103 | {name="value", type="number", default=1},
104 | {name="src2", type=RealTensor},
105 | call =
106 | function(res, src1, value, src2)
107 | res = res or torch.DoubleTensor()
108 | C.THDoubleTensor_cadd(res, src1, value, src2)
109 | return res
110 | end
111 | }
112 |
113 | end
114 |
115 | if dotgraph then
116 | local f = io.open('argtree.dot', 'w')
117 | f:write(dotgraph)
118 | f:close()
119 | end
120 |
121 | local x = rand(SZ, SZ)
122 | local y = rand(SZ, SZ)
123 |
124 | print('x', x:norm())
125 | print('y', x:norm())
126 | print('running')
127 |
128 | if named then
129 | local clk = os.clock()
130 | if scale == 1 then
131 | for i=1,N do
132 | add{res=y, src=x, value=5}
133 | add{res=y, src1=x, src2=y}
134 | end
135 | else
136 | for i=1,N do
137 | add{res=y, src=x, value=5}
138 | add{res=y, src1=x, value=scale, src2=y}
139 | end
140 | end
141 | print('time (s)', os.clock()-clk)
142 | else
143 | local clk = os.clock()
144 | if scale == 1 then
145 | for i=1,N do
146 | add(y, x, 5)
147 | add(y, x, y)
148 | end
149 | else
150 | for i=1,N do
151 | add(y, x, 5)
152 | add(y, x, scale, y)
153 | end
154 | end
155 | print('time (s)', os.clock()-clk)
156 | end
157 |
158 | print('x', x:norm())
159 | print('y', y:norm())
160 |
--------------------------------------------------------------------------------
/init.lua:
--------------------------------------------------------------------------------
1 | local env = require 'argcheck.env'
2 | local utils = require 'argcheck.utils'
3 | local doc = require 'argcheck.doc'
4 | local usage = require 'argcheck.usage'
5 | local ACN = require 'argcheck.graph'
6 |
7 | local setupvalue = utils.setupvalue
8 | local getupvalue = utils.getupvalue
9 | local loadstring = loadstring or load
10 |
11 | local function generaterules(rules)
12 | local graph
13 | if rules.chain or rules.overload then
14 | local status
15 | status, graph = pcall(getupvalue, rules.chain or rules.overload, 'graph')
16 | if not status then
17 | error('trying to overload a non-argcheck function')
18 | end
19 | else
20 | graph = ACN.new('@')
21 | end
22 | local upvalues = {istype=env.istype, graph=graph}
23 |
24 | local optperrule = {}
25 | for ridx, rule in ipairs(rules) do
26 | if rule.default ~= nil or rule.defaulta or rule.defaultf then
27 | optperrule[ridx] = 3 -- here, nil or not here
28 | elseif rule.opt then
29 | optperrule[ridx] = 3 -- here, nil or not here
30 | else
31 | optperrule[ridx] = 1 -- here
32 | end
33 | end
34 |
35 | local optperrulestride = {}
36 | local nvariant = 1
37 | for ridx=#rules,1,-1 do
38 | optperrulestride[ridx] = nvariant
39 | nvariant = nvariant * optperrule[ridx]
40 | end
41 |
42 | -- note: we keep the original rules (id) for all path variants
43 | -- hence, the mask.
44 | for variant=nvariant,1,-1 do
45 | local r = variant
46 | local rulemask = {} -- 1/2/3 means present [ordered]/not present [ordered]/ nil [named or ordered]
47 | for ridx=1,#rules do
48 | table.insert(rulemask, math.floor((r-1)/optperrulestride[ridx]) + 1)
49 | r = (r-1) % optperrulestride[ridx] + 1
50 | end
51 | rulemask = table.concat(rulemask)
52 |
53 | if not rules.noordered then
54 | graph:addpath(rules, rulemask, 'O')
55 | end
56 |
57 | if not rules.nonamed then
58 | if rules[1] and rules[1].name == 'self' then
59 | graph:addpath(rules, rulemask, 'M')
60 | else
61 | graph:addpath(rules, rulemask, 'N')
62 | end
63 | end
64 | end
65 |
66 | local code = graph:generate(upvalues)
67 |
68 | return code, upvalues
69 | end
70 |
71 | local function argcheck(rules)
72 |
73 | -- basic checks
74 | assert(not (rules.noordered and rules.nonamed), 'rules must be at least ordered or named')
75 | assert(rules.help == nil or type(rules.help) == 'string', 'rules help must be a string or nil')
76 | assert(rules.doc == nil or type(rules.doc) == 'string', 'rules doc must be a string or nil')
77 | assert(rules.chain == nil or type(rules.chain) == 'function', 'rules chain must be a function or nil')
78 | assert(rules.overload == nil or type(rules.overload) == 'function', 'rules overload must be a function or nil')
79 | assert(not (rules.chain and rules.overload), 'rules must have either overload [or chain (deprecated)]')
80 | assert(not (rules.doc and rules.help), 'choose between doc or help, not both')
81 | for _, rule in ipairs(rules) do
82 | assert(rule.name, 'rule must have a name field')
83 | assert(rule.type == nil or type(rule.type) == 'string', 'rule type must be a string or nil')
84 | assert(rule.help == nil or type(rule.help) == 'string', 'rule help must be a string or nil')
85 | assert(rule.doc == nil or type(rule.doc) == 'string', 'rule doc must be a string or nil')
86 | assert(rule.check == nil or type(rule.check) == 'function', 'rule check must be a function or nil')
87 | assert(rule.defaulta == nil or type(rule.defaulta) == 'string', 'rule defaulta must be a string or nil')
88 | assert(rule.defaultf == nil or type(rule.defaultf) == 'function', 'rule defaultf must be a function or nil')
89 | end
90 | if rules[1] and rules[1].name == 'self' then
91 | local rule = rules[1]
92 | assert(
93 | not rule.opt
94 | and not rule.default
95 | and not rule.defaulta
96 | and not rule.defaultf,
97 | 'self cannot be optional, nor having a default value!')
98 | end
99 |
100 | -- dump doc if any
101 | if rules.doc or rules.help then
102 | doc(usage.render(usage.usage(true, rules, true)))
103 | end
104 |
105 | local code, upvalues = generaterules(rules)
106 | if rules.debug then
107 | print(code)
108 | end
109 | local func, err = loadstring(code, 'argcheck')
110 | if not func then
111 | error(string.format('could not generate argument checker: %s', err))
112 | end
113 | func = func()
114 |
115 | for upvaluename, upvalue in pairs(upvalues) do
116 | setupvalue(func, upvaluename, upvalue)
117 | end
118 |
119 | if rules.debug then
120 | return func, upvalues.graph:print()
121 | else
122 | return func
123 | end
124 | end
125 |
126 | env.argcheck = argcheck
127 |
128 | return argcheck
129 |
--------------------------------------------------------------------------------
/usage.lua:
--------------------------------------------------------------------------------
1 | local env = require 'argcheck.env'
2 | local sdascii
3 | pcall(function()
4 | sdascii = require 'sundown.ascii'
5 | end)
6 |
7 | local usage = {}
8 |
9 | local function generateargp(rules)
10 | local txt = {}
11 | for idx, rule in ipairs(rules) do
12 | local isopt = rule.opt or rule.default ~= nil or rules.defauta or rule.defaultf
13 | table.insert(txt,
14 | (isopt and '[' or '')
15 | .. ((idx == 1) and '' or ', ')
16 | .. rule.name
17 | .. (isopt and ']' or ''))
18 | end
19 | return table.concat(txt)
20 | end
21 |
22 | local function generateargt(rules)
23 | local txt = {}
24 | table.insert(txt, '```')
25 | table.insert(txt, string.format(
26 | '%s%s',
27 | rules.noordered and '' or '(',
28 | rules.nonamed and '' or '{'))
29 |
30 | local size = 0
31 | for _,rule in ipairs(rules) do
32 | size = math.max(size, #rule.name)
33 | end
34 | local arg = {}
35 | local hlp = {}
36 | for _,rule in ipairs(rules) do
37 | table.insert(arg,
38 | ((rule.opt or rule.default ~= nil or rule.defaulta or rule.defaultf) and '[' or ' ')
39 | .. rule.name .. string.rep(' ', size-#rule.name)
40 | .. (rule.type and (' = ' .. rule.type) or '')
41 | .. ((rule.opt or rule.default ~= nil or rule.defaulta or rule.defaultf) and ']' or '')
42 | )
43 | local default = ''
44 | if rule.defaulta then
45 | default = string.format(' [defaulta=%s]', rule.defaulta)
46 | elseif rule.defaultf then
47 | default = string.format(' [has default]')
48 | elseif type(rule.default) ~= 'nil' then
49 | if type(rule.default) == 'string' then
50 | default = string.format(' [default=%s]', rule.default)
51 | elseif type(rule.default) == 'number' then
52 | default = string.format(' [default=%s]', rule.default)
53 | elseif type(rule.default) == 'boolean' then
54 | default = string.format(' [default=%s]', rule.default and 'true' or 'false')
55 | else
56 | default = ' [has default value]'
57 | end
58 | end
59 | table.insert(hlp, (rule.help or '') .. (rule.doc or '') .. default)
60 | end
61 |
62 | local size = 0
63 | for i=1,#arg do
64 | size = math.max(size, #arg[i])
65 | end
66 |
67 | for i=1,#arg do
68 | table.insert(txt, string.format(" %s %s -- %s", arg[i], string.rep(' ', size-#arg[i]), hlp[i]))
69 | end
70 | table.insert(txt, string.format(
71 | '%s%s',
72 | rules.nonamed and '' or '}',
73 | rules.noordered and '' or ')'))
74 | table.insert(txt, '```')
75 |
76 | txt = table.concat(txt, '\n')
77 |
78 | return txt
79 | end
80 |
81 | function usage.render(doc)
82 | -- We render any markdown in the input into ANSI color codes using sundown, but only if stdout and stderr are terminals
83 | if sdascii and pcall(require, 'torch') and torch.isatty(io.stderr) and torch.isatty(io.stdout) then
84 | doc = sdascii.render(doc)
85 | end
86 |
87 | return doc
88 | end
89 |
90 | function usage.usage(truth, rules, ...)
91 | if truth then
92 | local norender = select(1, ...)
93 | local doc = rules.help or rules.doc
94 |
95 | if doc then
96 | doc = doc:gsub('@ARGP',
97 | function()
98 | return generateargp(rules)
99 | end)
100 |
101 | doc = doc:gsub('@ARGT',
102 | function()
103 | return generateargt(rules)
104 | end)
105 | end
106 |
107 | if not doc then
108 | doc = '\n*Arguments:*\n' .. generateargt(rules)
109 | end
110 |
111 | return doc
112 | else
113 | local self = rules
114 | local args = {}
115 | for i=1,select('#', ...) do
116 | table.insert(args, string.format("**%s**", env.type(select(i, ...))))
117 | end
118 | local argtblidx
119 | if self:hasruletype('N') then
120 | if select("#", ...) == 1 and env.istype(select(1, ...), "table") then
121 | argtblidx = 1
122 | end
123 | elseif self:hasruletype('M') then
124 | if select("#", ...) == 2 and env.istype(select(2, ...), "table") then
125 | argtblidx = 2
126 | end
127 | end
128 | if argtblidx then
129 | local argtbl = {}
130 | local tbl = select(argtblidx, ...)
131 | local n = 0
132 | for k,v in pairs(tbl) do
133 | n = n + 1
134 | if n > 20 then
135 | table.insert(argtbl, '...')
136 | break
137 | end
138 | if type(k) == 'string' then
139 | table.insert(argtbl, string.format("**%s=%s**", k, env.type(v)))
140 | else
141 | table.insert(argtbl, string.format("**[%s]**=?", env.type(k)))
142 | end
143 | end
144 | args[argtblidx] = string.format("**table**={ %s }", table.concat(argtbl, ', '))
145 | end
146 | local doc = string.format("*Got:* %s", table.concat(args, ', '))
147 | return doc
148 | end
149 | end
150 |
151 | return usage
152 |
--------------------------------------------------------------------------------
/benchmark/README.md:
--------------------------------------------------------------------------------
1 | argcheck benchmark
2 | ==================
3 |
4 | We show here an example of `argcheck` in a real-life case: wrapping a call
5 | to the numerical library [TH](https://github.com/torch/TH), used in
6 | [torch7](https://github.com/torch/torch7).
7 |
8 | The code does a simple loop over this particular function call. In torch7,
9 | this looks like:
10 | ```lua
11 | for i=1,N do
12 | torch.add(y, x, 5)
13 | torch.add(y, x, scale, y)
14 | end
15 | ```
16 |
17 | The add function of torch7 is non-trivial, because it has to handle cases
18 | where one wants to add a tensor to a tensor or a value to a tensor. There
19 | is also an optional scale argument. The function is also overloaded for 7
20 | different types of tensors (double, float, int...), which makes things even
21 | more uneasy. We define the double overloading last, to study the worst case
22 | performance.
23 |
24 | In the following, we compare:
25 | - `torch7` (here ran with luajit). Torch7 uses the regular lua/C API.
26 | - `torch9`, a FFI interface for [`luajit`](http://luajit.org), to the TH library achieved with `argcheck`.
27 | - `torch9lua`, running [`lua`](http://www.lua.org) with [`libffi`](https://github.com/jmckaskill/luaffi) and `argcheck`.
28 | - `C`, plain C calls to `TH` library. Contrary to other versions, _it does not include the overhead of multiple tensor types_.
29 |
30 | What we call `torch9` here is only a thin interface to `TH` with FFI,
31 | limited to the purpose of this benchmark. The only thing it has to do with
32 | the upcoming `torch9` is the way we use `argcheck` with FFI.
33 |
34 | We avoid garbage-collection side-effects by not allocating objects.
35 |
36 | ## Call to argcheck
37 |
38 | We create a function `add()`, which is overloaded to handle various possible argument situations.
39 |
40 | ```lua
41 | add = argcheck{
42 | overload = add,
43 | {name="res", type="torch.DoubleTensor", opt=true},
44 | {name="src", type="torch.DoubleTensor"},
45 | {name="value", type="number"},
46 | call =
47 | function(res, src, value)
48 | res = res or DoubleTensor()
49 | C.THDoubleTensor_add(res, src, value)
50 | return res
51 | end
52 | }
53 |
54 | add = argcheck{
55 | overload = add,
56 | {name="res", type="torch.DoubleTensor", opt=true},
57 | {name="src1", type="torch.DoubleTensor"},
58 | {name="value", type="number", default=1},
59 | {name="src2", type="torch.DoubleTensor"},
60 | call =
61 | function(res, src1, value, src2)
62 | res = res or torch.DoubleTensor()
63 | C.THDoubleTensor_cadd(res, src1, value, src2)
64 | return res
65 | end
66 | }
67 | ```
68 |
69 | As you can see, there are many variations to handle. The generated code is
70 | 201 lines of `lua` code, only for the case of DoubleTensor. With all the 7
71 | types of tensor, it is 5250 lines of code! This code handles both ordered
72 | arguments (as in `torch7`) and named arguments calls. Arguments calls is
73 | just for syntactic sugar, but is slower (it implies creating argument
74 | tables, and looping over them, which is not JIT in the current
75 | `luajit` 2.1).
76 |
77 | The tree generated in the case of DoubleTensor is alone, is the following:
78 | 
79 | When it includes all the 7 types of tensors:
80 | 
81 |
82 | ## Running it
83 |
84 | We now compare our different setups with matrix sizes of size 2, 10, 100,
85 | and 300 over 100,000,000, 10,000,000, 1,000,000 and 100,000 iterations
86 | respectively. Running time is given is seconds. Experiments were performed
87 | on a MacBook Pro 2.6GHz Quad-core i7, using one core. Overhead per call is
88 | reported, in nano-seconds, computed with the first two columns (w.r.t C
89 | performance).
90 |
91 | | | 2 | 10 | 100 | 300 | overhead |
92 | |:--------------------------------|---------:|---------:|---------:|---------:|-----------:|
93 | | C | 3.82s | 1.16s | 8.74s | 10.34s | 0ns |
94 | | torch7 (luajit+C API) (jit=on) | 73.45s | 8.22s | 9.47s | 10.47s | 701ns |
95 | | torch7 (luajit+C API) (jit=off) | 72.22s | 8.21s | 9.49s | 10.59s | 694ns |
96 | | torch9 (luajit+ffi) (jit=on) | 3.80s | 1.14s | 8.82s | 10.30s | -1ns |
97 | | torch9 (luajit+ffi) (jit=off) | 167.62s | 17.35s | 10.75s | 10.83s | 1619ns |
98 | | torch9 (lua+luaffi) | 256.20s | 26.93s | 11.30s | 10.66s | 2550ns |
99 |
100 | ### Comments
101 |
102 | Not suprisingly, the old lua/C API has quite some overheads when calling
103 | short duration C code.
104 |
105 | `luajit` does an impressive job in calling C functions through FFI. It
106 | stays on par with C performance, even when C operations are limited (small
107 | matrix size). `argcheck` is viable even in interpreted mode with luajit,
108 | with only a x2 overhead compared to the lua/C API.
109 |
110 | Lua interpreter (with luaffi library) has clearly more
111 | overheads. `argcheck` might be still very usable (here 2.5ms per call, in a
112 | pretty complicated setup), depending on your use-case.
113 |
114 | ## Named arguments
115 |
116 | As mention earlier, named argument calls are expected to be slower. Here is
117 | a comparison against ordered arguments calls, using the same benchmark. In
118 | our case, the overhead is about 1ms per call with luajit (note that with
119 | jit off, the performance is similar, meaning luajit relies mainly on the
120 | interpreter in that case). Our test case is pretty complicated, your
121 | mileage might vary...
122 |
123 | | | 2 | 10 | 100 | 300 | overhead |
124 | |:-----------------------------------------|---------:|---------:|---------:|---------:|-----------:|
125 | | torch9 (luajit+ffi) (jit=on) (ordered) | 3.80s | 1.14s | 8.82s | 10.30s | -1ns |
126 | | torch9 (luajit+ffi) (jit=off) (ordered) | 167.62s | 17.35s | 10.75s | 10.83s | 1628ns |
127 | | torch9 (lua+luaffi) (ordered) | 256.20s | 26.93s | 11.30s | 10.66s | 2550ns |
128 | | torch9 (luajit+ffi) (jit=on) (named) | 110.24s | 11.81s | 9.85s | 10.29s | 1064ns |
129 | | torch9 (luajit+ffi) (jit=off) (named) | 205.99s | 21.92s | 11.08s | 10.72s | 2049ns |
130 | | torch9 (lua+luaffi) (named) | 486.19s | 49.48s | 13.87s | 10.66s | 4828ns |
131 |
--------------------------------------------------------------------------------
/test/test.lua:
--------------------------------------------------------------------------------
1 | local argcheck = require 'argcheck'
2 | local env = require 'argcheck.env'
3 |
4 | function addfive(x)
5 | return string.format('%f + 5 = %f', x, x+5)
6 | end
7 |
8 | check = argcheck{
9 | {name="x", type="number"}
10 | }
11 |
12 | function addfive(...)
13 | local x = check(...)
14 | return string.format('%f + 5 = %f', x, x+5)
15 | end
16 |
17 | assert(addfive(5) == '5.000000 + 5 = 10.000000')
18 | assert(not pcall(addfive))
19 |
20 | check = argcheck{
21 | {name="x", type="number", default=0}
22 | }
23 |
24 | assert(addfive() == '0.000000 + 5 = 5.000000')
25 |
26 |
27 | check = argcheck{
28 | help=[[
29 | This function is going to do a simple addition.
30 | Give a number, it adds 5. Amazing.
31 | ]],
32 | {name="x", type="number", default=0, help="the age of the captain"},
33 | {name="msg", type="string", help="a message"}
34 | }
35 |
36 | function addfive(...)
37 | local x, msg = check(...)
38 | return string.format('%f + 5 = %f [msg=%s]', x, x+5, msg)
39 | end
40 |
41 | assert(addfive(4, 'hello world') == '4.000000 + 5 = 9.000000 [msg=hello world]')
42 | assert(addfive('hello world') == '0.000000 + 5 = 5.000000 [msg=hello world]')
43 |
44 | check = argcheck{
45 | {name="x", type="number"},
46 | {name="y", type="number", defaulta="x"}
47 | }
48 |
49 | function mul(...)
50 | local x, y = check(...)
51 | return string.format('%f x %f = %f', x, y, x*y)
52 | end
53 |
54 | assert(mul(3,4) == '3.000000 x 4.000000 = 12.000000')
55 | assert(mul(3) == '3.000000 x 3.000000 = 9.000000')
56 |
57 | idx = 0
58 | check = argcheck{
59 | {name="x", type="number"},
60 | {name="y", type="number", defaultf=function() idx = idx + 1 return idx end}
61 | }
62 |
63 | function mul(...)
64 | local x, y = check(...)
65 | return string.format('%f x %f = %f', x, y, x*y)
66 | end
67 |
68 | assert(mul(3) == '3.000000 x 1.000000 = 3.000000')
69 | assert(mul(3) == '3.000000 x 2.000000 = 6.000000')
70 | assert(mul(3) == '3.000000 x 3.000000 = 9.000000')
71 |
72 | check = argcheck{
73 | {name="x", type="number", default=0, help="the age of the captain"},
74 | {name="msg", type="string", help="a message", opt=true}
75 | }
76 |
77 | function addfive(...)
78 | local x, msg = check(...)
79 | return string.format('%f + 5 = %f [msg=%s]', x, x+5, msg)
80 | end
81 |
82 | assert(addfive('hello world') == '0.000000 + 5 = 5.000000 [msg=hello world]')
83 | assert(addfive() == '0.000000 + 5 = 5.000000 [msg=nil]')
84 |
85 | check = argcheck{
86 | {name="x", type="number", help="a number between one and ten",
87 | check=function(x)
88 | return x >= 1 and x <= 10
89 | end}
90 | }
91 |
92 | function addfive(...)
93 | local x = check(...)
94 | return string.format('%f + 5 = %f', x, x+5)
95 | end
96 |
97 | assert(addfive(3) == '3.000000 + 5 = 8.000000')
98 | assert( not pcall(addfive, 11))
99 |
100 | check = argcheck{
101 | {name="x", type="number", default=0, help="the age of the captain"},
102 | {name="msg", type="string", help="a message", opt=true}
103 | }
104 |
105 | function addfive(...)
106 | local x, msg = check(...)
107 | return string.format('%f + 5 = %f [msg=%s]', x, x+5, msg)
108 | end
109 |
110 | assert(addfive(1, "hello world") == '1.000000 + 5 = 6.000000 [msg=hello world]')
111 | assert(addfive{x=1, msg="hello world"} == '1.000000 + 5 = 6.000000 [msg=hello world]')
112 |
113 | check = argcheck{
114 | pack=true,
115 | {name="x", type="number", default=0, help="the age of the captain"},
116 | {name="msg", type="string", help="a message"}
117 | }
118 |
119 | function addfive(...)
120 | local args = check(...) -- now arguments are stored in this table
121 | return(string.format('%f + 5 = %f [msg=%s]', args.x, args.x+5, args.msg))
122 | end
123 |
124 | assert(addfive(5, 'hello world') == '5.000000 + 5 = 10.000000 [msg=hello world]')
125 |
126 | check = argcheck{
127 | nonamed=true,
128 | {name="x", type="number", default=0, help="the age of the captain"},
129 | {name="msg", type="string", help="a message"}
130 | }
131 |
132 | function addfive(...)
133 | local x, msg = check(...)
134 | return string.format('%f + 5 = %f [msg=%s]', x, x+5, msg)
135 | end
136 |
137 | assert(addfive('blah') == '0.000000 + 5 = 5.000000 [msg=blah]')
138 | assert(not pcall(addfive, {msg='blah'}))
139 |
140 | check = argcheck{
141 | quiet=true,
142 | {name="x", type="number", default=0, help="the age of the captain"},
143 | {name="msg", type="string", help="a message"}
144 | }
145 |
146 | assert(check(5, 'hello world'))
147 | assert(not check(5))
148 |
149 | addfive = argcheck{
150 | {name="x", type="number"},
151 | call =
152 | function(x)
153 | return string.format('%f + 5 = %f', x, x+5)
154 | end
155 | }
156 |
157 | assert(addfive(5) == '5.000000 + 5 = 10.000000')
158 | assert(not pcall(addfive))
159 |
160 | checknum = argcheck{
161 | quiet=true,
162 | {name="x", type="number"}
163 | }
164 |
165 | checkstr = argcheck{
166 | quiet=true,
167 | {name="str", type="string"}
168 | }
169 |
170 | function addfive(...)
171 |
172 | -- first case
173 | local status, x = checknum(...)
174 | if status then
175 | return string.format('%f + 5 = %f', x, x+5)
176 | end
177 |
178 | -- second case
179 | local status, str = checkstr(...)
180 | if status then
181 | return string.format('%s .. 5 = %s', str, str .. '5')
182 | end
183 |
184 | -- note that in case of failure with quiet, the error is returned after the status
185 | error('invalid arguments')
186 | end
187 |
188 | assert(addfive(123) == '123.000000 + 5 = 128.000000')
189 | assert(addfive('hi') == 'hi .. 5 = hi5')
190 |
191 | addfive = argcheck{
192 | {name="x", type="number"},
193 | call =
194 | function(x) -- called in case of success
195 | return string.format('%f + 5 = %f', x, x+5)
196 | end
197 | }
198 |
199 | addfive = argcheck{
200 | {name="str", type="string"},
201 | overload = addfive, -- overload previous one
202 | call =
203 | function(str) -- called in case of success
204 | return string.format('%s .. 5 = %s', str, str .. '5')
205 | end
206 | }
207 |
208 | assert(addfive(5) == '5.000000 + 5 = 10.000000')
209 | assert(addfive('hi') == 'hi .. 5 = hi5')
210 |
211 | addfive = argcheck{
212 | {name="x", type="number"},
213 | call =
214 | function(x) -- called in case of success
215 | return string.format('%f + 7 = %f', x, x+7)
216 | end
217 | }
218 |
219 | assert(not pcall(argcheck,
220 | {
221 | {name="x", type="number"},
222 | {name="msg", type="string", default="i know what i am doing"},
223 | overload = addfive,
224 | call =
225 | function(x, msg) -- called in case of success
226 | return string.format('%f + 5 = %f [msg = %s]', x, x+5, msg)
227 | end
228 | })
229 | )
230 |
231 | addfive = argcheck{
232 | {name="x", type="number"},
233 | {name="msg", type="string", default="i know what i am doing"},
234 | overload = addfive,
235 | force = true,
236 | call =
237 | function(x, msg) -- called in case of success
238 | return string.format('%f + 5 = %f [msg = %s]', x, x+5, msg)
239 | end
240 | }
241 |
242 | assert(addfive(5, 'hello') == '5.000000 + 5 = 10.000000 [msg = hello]')
243 | assert(addfive(5) == '5.000000 + 5 = 10.000000 [msg = i know what i am doing]')
244 |
245 | local foobar
246 | if pcall(require, 'torch') then
247 | local ctors = {}
248 | torch.class('foobar', ctors)
249 | foobar = ctors.foobar()
250 | else
251 | foobar = {}
252 | setmetatable(foobar, {__typename="foobar"})
253 | end
254 | foobar.checksum = 1234567
255 |
256 | foobar.addnothing = argcheck{
257 | {name="self", type="foobar"},
258 | debug=true,
259 | call =
260 | function(self)
261 | return self.checksum
262 | end
263 | }
264 |
265 | assert(foobar:addnothing() == 1234567)
266 |
267 | foobar.addfive = argcheck{
268 | {name="self", type="foobar"},
269 | {name="x", type="number"},
270 | {name="msg", type="string", default="i know what i am doing"},
271 | call =
272 | function(self, x, msg) -- called in case of success
273 | return string.format('%f + 5 = %f [msg = %s] [self.checksum=%s]', x, x+5, msg, self.checksum)
274 | end
275 | }
276 |
277 | assert(foobar:addfive(5, 'paf') == '5.000000 + 5 = 10.000000 [msg = paf] [self.checksum=1234567]')
278 | assert(foobar:addfive{x=5, msg='paf'} == '5.000000 + 5 = 10.000000 [msg = paf] [self.checksum=1234567]')
279 |
280 | assert(foobar:addfive(5) == '5.000000 + 5 = 10.000000 [msg = i know what i am doing] [self.checksum=1234567]')
281 | assert(foobar:addfive{x=5} == '5.000000 + 5 = 10.000000 [msg = i know what i am doing] [self.checksum=1234567]')
282 |
283 | foobar.addfive = argcheck{
284 | {name="self", type="foobar"},
285 | {name="x", type="number", default=5},
286 | {name="msg", type="string", default="wassup"},
287 | call =
288 | function(self, x, msg) -- called in case of success
289 | return string.format('%f + 5 = %f [msg = %s] [self.checksum=%s]', x, x+5, msg, self.checksum)
290 | end
291 | }
292 |
293 | assert(foobar:addfive() == '5.000000 + 5 = 10.000000 [msg = wassup] [self.checksum=1234567]')
294 | assert(foobar:addfive('paf') == '5.000000 + 5 = 10.000000 [msg = paf] [self.checksum=1234567]')
295 | assert(foobar:addfive(nil, 'paf') == '5.000000 + 5 = 10.000000 [msg = paf] [self.checksum=1234567]')
296 | assert(foobar:addfive(6, 'paf') == '6.000000 + 5 = 11.000000 [msg = paf] [self.checksum=1234567]')
297 | assert(foobar:addfive(6) == '6.000000 + 5 = 11.000000 [msg = wassup] [self.checksum=1234567]')
298 | assert(foobar:addfive(6, nil) == '6.000000 + 5 = 11.000000 [msg = wassup] [self.checksum=1234567]')
299 |
300 | assert(foobar:addfive{} == '5.000000 + 5 = 10.000000 [msg = wassup] [self.checksum=1234567]')
301 | assert(foobar:addfive{msg='paf'} == '5.000000 + 5 = 10.000000 [msg = paf] [self.checksum=1234567]')
302 | assert(foobar:addfive{x=6, msg='paf'} == '6.000000 + 5 = 11.000000 [msg = paf] [self.checksum=1234567]')
303 | assert(foobar:addfive{x=6} == '6.000000 + 5 = 11.000000 [msg = wassup] [self.checksum=1234567]')
304 |
305 | addstuff = argcheck{
306 | {name="x", type="number"},
307 | {name="y", type="number", default=7},
308 | {name="msg", type="string", opt=true},
309 | call =
310 | function(x, y, msg)
311 | return string.format('%f + %f = %f [msg=%s]', x, y, x+y, msg or 'NULL')
312 | end
313 | }
314 |
315 | assert(addstuff(3) == '3.000000 + 7.000000 = 10.000000 [msg=NULL]')
316 | assert(addstuff{x=3} == '3.000000 + 7.000000 = 10.000000 [msg=NULL]')
317 | assert(addstuff(3, 'paf') == '3.000000 + 7.000000 = 10.000000 [msg=paf]')
318 | assert(addstuff{x=3, msg='paf'} == '3.000000 + 7.000000 = 10.000000 [msg=paf]')
319 |
320 | assert(addstuff(3, 4) == '3.000000 + 4.000000 = 7.000000 [msg=NULL]')
321 | assert(addstuff{x=3, y=4} == '3.000000 + 4.000000 = 7.000000 [msg=NULL]')
322 | assert(addstuff(3, 4, 'paf') == '3.000000 + 4.000000 = 7.000000 [msg=paf]')
323 | assert(addstuff{x=3, y=4, msg='paf'} == '3.000000 + 4.000000 = 7.000000 [msg=paf]')
324 |
325 | assert(env.type('string') == 'string')
326 | assert(env.type(foobar) == 'foobar')
327 |
328 | if pcall(require, 'torch') then
329 | local t = torch.LongTensor()
330 | assert(env.type(t) == 'torch.LongTensor')
331 | assert(env.istype(t, 'torch.LongTensor') == true)
332 | assert(env.istype(t, 'torch.*Tensor') == true)
333 | assert(env.istype(t, '.*Long') == true)
334 | assert(env.istype(t, 'torch.IntTensor') == false)
335 | assert(env.istype(t, 'torch.Long') == false)
336 |
337 | -- test argcheck function serialization
338 | local f = argcheck{
339 | {name='arg', type='string'},
340 | call = function(arg)
341 | print(arg)
342 | end
343 | }
344 | local m = torch.MemoryFile()
345 | m:writeObject(f)
346 | end
347 |
348 | print('PASSED')
349 |
--------------------------------------------------------------------------------
/graph.lua:
--------------------------------------------------------------------------------
1 | local usage = require 'argcheck.usage'
2 | local utils = require 'argcheck.utils'
3 |
4 | local function argname2idx(rules, name)
5 | for idx, rule in ipairs(rules) do
6 | if rule.name == name then
7 | return idx
8 | end
9 | end
10 | error(string.format('invalid defaulta name <%s>', name))
11 | end
12 |
13 | local function table2id(tbl)
14 | -- DEBUG: gros hack de misere
15 | return tostring(tbl):match('0x([^%s]+)')
16 | end
17 |
18 | local function func2id(func)
19 | -- DEBUG: gros hack de misere
20 | return tostring(func):match('0x([^%s]+)')
21 | end
22 |
23 | local function rules2maskedrules(rules, rulesmask, rulestype, iscall)
24 | local maskedrules = {}
25 | for ridx=1,#rulesmask do
26 | local rule = utils.duptable(rules[ridx])
27 | rule.__ridx = ridx
28 | if not iscall then -- do not mess up the name for a call
29 | if rulestype == 'O' then
30 | rule.name = nil
31 | elseif rulestype == 'M' and ridx == 1 then -- self?
32 | rule.name = nil
33 | end
34 | end
35 |
36 | local rulemask = rulesmask:sub(ridx,ridx)
37 | if rulemask == '1' then
38 | table.insert(maskedrules, rule)
39 | elseif rulemask == '2' then
40 | elseif rulemask == '3' and rulestype == 'O' then
41 | rule.type = 'nil'
42 | rule.check = nil
43 | table.insert(maskedrules, rule)
44 | end
45 | end
46 | return maskedrules
47 | end
48 |
49 | local function rules2defaultrules(rules, rulesmask, rulestype)
50 | local defaultrules = {}
51 | for ridx=1,#rulesmask do
52 | local rule = utils.duptable(rules[ridx])
53 | rule.__ridx = ridx
54 | if rulestype == 'O' then
55 | rule.name = nil
56 | elseif rulestype == 'M' and ridx == 1 then -- self?
57 | rule.name = nil
58 | end
59 |
60 | local rulemask = rulesmask:sub(ridx,ridx)
61 | if rulemask == '1' then
62 | elseif rulemask == '2' then
63 | table.insert(defaultrules, rule)
64 | elseif rulemask == '3' then
65 | if rule.default or rule.defaulta or rule.defaultf then
66 | table.insert(defaultrules, rule)
67 | end
68 | end
69 | end
70 | return defaultrules
71 | end
72 |
73 | local ACN = {}
74 |
75 | function ACN.new(typename, name, check, rules, rulesmask, rulestype)
76 | assert(typename)
77 | local self = {}
78 | setmetatable(self, {__index=ACN})
79 | self.type = typename
80 | self.name = name
81 | self.check = check
82 | self.rules = rules
83 | self.rulesmask = rulesmask
84 | self.rulestype = rulestype
85 | self.next = {}
86 | return self
87 | end
88 |
89 | function ACN:add(node)
90 | table.insert(self.next, node)
91 | end
92 |
93 | function ACN:match(rules)
94 | local head = self
95 | for idx=1,#rules do
96 | local rule = rules[idx]
97 | local matched = false
98 | for _,child in ipairs(head.next) do
99 | if child.type == rule.type
100 | and child.check == rule.check
101 | and child.name == rule.name then
102 | head = child
103 | matched = true
104 | break
105 | end
106 | end
107 | if not matched then
108 | return head, idx-1
109 | end
110 | end
111 | return head, #rules
112 | end
113 |
114 | function ACN:hasruletype(ruletype)
115 | local hasruletype
116 | self:apply(function(self)
117 | if self.rulestype == ruletype then
118 | hasruletype = true
119 | end
120 | end)
121 | return hasruletype
122 | end
123 |
124 | function ACN:addpath(rules, rulesmask, rulestype) -- 'O', 'N', 'M'
125 | -- DEBUG: on peut aussi imaginer avoir d'abord mis
126 | -- les no-named, et ensuite les named!!
127 |
128 | assert(rules)
129 | assert(rulesmask)
130 | assert(rulestype)
131 |
132 | local maskedrules = rules2maskedrules(rules, rulesmask, rulestype, false)
133 |
134 | if rulestype == 'N' then
135 | table.insert(maskedrules, 1, {type='table'})
136 | end
137 |
138 | if rulestype == 'M' then
139 | table.insert(maskedrules, 2, {type='table'})
140 | end
141 |
142 | local head, idx = self:match(maskedrules)
143 |
144 | if idx == #maskedrules then
145 | -- check we are not overwriting something here
146 | if not rules.force and head.rules and rules ~= head.rules then
147 | error('argcheck rules led to ambiguous situations')
148 | end
149 | head.rules = rules
150 | head.rulesmask = rulesmask
151 | head.rulestype = rulestype
152 | end
153 | for idx=idx+1,#maskedrules do
154 | local rule = maskedrules[idx]
155 | local node = ACN.new(rule.type,
156 | rule.name,
157 | rule.check,
158 | idx == #maskedrules and rules or nil,
159 | idx == #maskedrules and rulesmask or nil,
160 | idx == #maskedrules and rulestype or nil)
161 | head:add(node)
162 | head = node
163 | end
164 |
165 | -- special trick: mark self
166 | if rulestype == 'M' then
167 | local head, idx = self:match({maskedrules[1]}) -- find self
168 | assert(idx == 1, 'internal bug, please report')
169 | head.isself = true
170 | end
171 |
172 | end
173 |
174 | function ACN:id()
175 | return table2id(self)
176 | end
177 |
178 | function ACN:print(txt)
179 | local isroot = not txt
180 | txt = txt or {'digraph ACN {'}
181 | table.insert(txt, 'edge [penwidth=.3 arrowsize=0.8];')
182 | table.insert(txt, string.format('id%s [label="%s%s%s%s" penwidth=.1 fontsize=10 style=filled fillcolor="%s"];',
183 | self:id(),
184 | self.type,
185 | self.isself and '*' or '',
186 | self.check and ' ' or '',
187 | self.name and string.format(' (%s)', self.name) or '',
188 | self.rules and '#aaaaaa' or '#eeeeee'))
189 |
190 | for _,child in ipairs(self.next) do
191 | child:print(txt) -- make sure its id is defined
192 | table.insert(txt, string.format('id%s -> id%s;',
193 | self:id(),
194 | child:id()))
195 | end
196 |
197 | if isroot then
198 | table.insert(txt, '}')
199 | txt = table.concat(txt, '\n')
200 | return txt
201 | end
202 | end
203 |
204 | function ACN:generate_ordered_or_named(code, upvalues, rulestype, depth)
205 | depth = depth or 0
206 |
207 | -- no need to go deeper if no rules found later
208 | if not self:hasruletype(rulestype) then
209 | return
210 | end
211 |
212 | if depth > 0 then
213 | local argname =
214 | (rulestype == 'N' or rulestype == 'M')
215 | and string.format('args.%s', self.name)
216 | or string.format('select(%d, ...)', depth)
217 |
218 | if self.check then
219 | upvalues[string.format('check%s', func2id(self.check))] = self.check
220 | end
221 | if self.type == 'nil' and (rulestype == 'N' or rulestype == 'M') then
222 | table.insert(code, string.format('%sif istype(%s, "%s")%s then',
223 | string.rep(' ', depth),
224 | argname,
225 | self.type,
226 | self.check and string.format(' and check%s(%s)', func2id(self.check), argname) or ''))
227 | else
228 | table.insert(code, string.format('%sif narg > 0 and istype(%s, "%s")%s then',
229 | string.rep(' ', depth),
230 | argname,
231 | self.type,
232 | self.check and string.format(' and check%s(%s)', func2id(self.check), argname) or ''))
233 | table.insert(code, string.format('%s narg = narg - 1', string.rep(' ', depth)))
234 | end
235 | end
236 |
237 | if self.rules and self.rulestype == rulestype then
238 | local rules = self.rules
239 | local rulesmask = self.rulesmask
240 | local id = table2id(rules)
241 | table.insert(code, string.format(' %sif narg == 0 then', string.rep(' ', depth)))
242 |
243 | -- 'M' case (method: first arg is self)
244 | if rulestype == 'M' then
245 | rules = utils.duptable(self.rules)
246 | table.remove(rules, 1) -- remove self
247 | rulesmask = rulesmask:sub(2)
248 | end
249 |
250 | -- func args
251 | local argcode = {}
252 | for ridx, rule in ipairs(rules) do
253 | if rules.pack then
254 | table.insert(argcode, string.format('%s=arg%d', rule.name, ridx))
255 | else
256 | table.insert(argcode, string.format('arg%d', ridx))
257 | end
258 | end
259 |
260 | -- passed arguments
261 | local maskedrules = rules2maskedrules(rules, rulesmask, rulestype, true)
262 | for argidx, rule in ipairs(maskedrules) do
263 |
264 | local argname =
265 | (rulestype == 'N' or rulestype == 'M')
266 | and string.format('args.%s', rule.name)
267 | or string.format('select(%d, ...)', argidx)
268 |
269 | table.insert(code, string.format(' %slocal arg%d = %s',
270 | string.rep(' ', depth),
271 | rule.__ridx,
272 | argname))
273 | end
274 |
275 | -- default arguments
276 | local defaultrules = rules2defaultrules(rules, rulesmask)
277 | local defacode = {}
278 | for _, rule in ipairs(defaultrules) do
279 | local defidx = rulestype == 'M' and rule.__ridx+1 or rule.__ridx
280 | if rule.default ~= nil then
281 | table.insert(code, string.format(' %slocal arg%d = arg%s_%dd', string.rep(' ', depth), rule.__ridx, id, defidx))
282 | upvalues[string.format('arg%s_%dd', id, defidx)] = rule.default
283 | elseif rule.defaultf then
284 | table.insert(code, string.format(' %slocal arg%d = arg%s_%df()', string.rep(' ', depth), rule.__ridx, id, defidx))
285 | upvalues[string.format('arg%s_%df', id, defidx)] = rule.defaultf
286 | elseif rule.opt then
287 | table.insert(code, string.format(' %slocal arg%d', string.rep(' ', depth), rule.__ridx))
288 | elseif rule.defaulta then
289 | table.insert(defacode, string.format(' %slocal arg%d = arg%d', string.rep(' ', depth), rule.__ridx, argname2idx(rules, rule.defaulta)))
290 | end
291 | end
292 |
293 | if #defacode > 0 then
294 | table.insert(code, table.concat(defacode, '\n'))
295 | end
296 |
297 | if rules.pack then
298 | argcode = table.concat(argcode, ', ')
299 | if rulestype == 'M' then
300 | argcode = string.format('self, {%s}', argcode)
301 | else
302 | argcode = string.format('{%s}', argcode)
303 | end
304 | else
305 | if rulestype == 'M' then
306 | table.insert(argcode, 1, 'self')
307 | end
308 | argcode = table.concat(argcode, ', ')
309 | end
310 |
311 | if rules.call and not rules.quiet then
312 | argcode = string.format('call%s(%s)', id, argcode)
313 | upvalues[string.format('call%s', id)] = rules.call
314 | end
315 | if rules.quiet and not rules.call then
316 | argcode = string.format('true%s%s', #argcode > 0 and ', ' or '', argcode)
317 | end
318 | if rules.quiet and rules.call then
319 | argcode = string.format('call%s%s%s', id, #argcode > 0 and ', ' or '', argcode)
320 | upvalues[string.format('call%s', id)] = rules.call
321 | end
322 |
323 | table.insert(code, string.format(' %sreturn %s', string.rep(' ', depth), argcode))
324 | table.insert(code, string.format(' %send', string.rep(' ', depth)))
325 | end
326 |
327 | for _,child in ipairs(self.next) do
328 | child:generate_ordered_or_named(code, upvalues, rulestype, depth+1)
329 | end
330 |
331 | if depth > 0 then
332 | if self.type ~= 'nil' or (rulestype ~= 'N' and rulestype ~= 'M') then
333 | table.insert(code, string.format('%s narg = narg + 1', string.rep(' ', depth)))
334 | end
335 | table.insert(code, string.format('%send', string.rep(' ', depth)))
336 | end
337 |
338 | end
339 |
340 | function ACN:apply(func)
341 | func(self)
342 | for _,child in ipairs(self.next) do
343 | child:apply(func)
344 | end
345 | end
346 |
347 | function ACN:usage(...)
348 | local txt = {}
349 | local history = {}
350 | self:apply(
351 | function(self)
352 | if self.rules and not history[self.rules] then
353 | history[self.rules] = true
354 | table.insert(txt, usage.usage(true, self.rules))
355 | end
356 | end
357 | )
358 | return string.format(
359 | "%s\n%s\n",
360 | table.concat(txt, '\n\nor\n\n'),
361 | usage.usage(false, self, ...)
362 | )
363 | end
364 |
365 | function ACN:generate(upvalues)
366 | assert(upvalues, 'upvalues table missing')
367 | local code = {}
368 | table.insert(code, 'return function(...)')
369 | table.insert(code, ' local usage = require "argcheck.usage"')
370 | table.insert(code, ' local narg = select("#", ...)')
371 | self:generate_ordered_or_named(code, upvalues, 'O')
372 |
373 | if self:hasruletype('N') then -- is there any named?
374 | local selfnamed = self:match({{type='table'}})
375 | assert(selfnamed ~= self, 'internal bug, please report')
376 | table.insert(code, ' if select("#", ...) == 1 and istype(select(1, ...), "table") then')
377 | table.insert(code, ' local args = select(1, ...)')
378 | table.insert(code, ' local narg = 0')
379 | table.insert(code, ' for k,v in pairs(args) do')
380 | table.insert(code, ' narg = narg + 1')
381 | table.insert(code, ' end')
382 | selfnamed:generate_ordered_or_named(code, upvalues, 'N')
383 | table.insert(code, ' end')
384 | end
385 |
386 | for _,head in ipairs(self.next) do
387 | if head.isself then -- named self method
388 | local selfnamed = head:match({{type='table'}})
389 | assert(selfnamed ~= head, 'internal bug, please report')
390 | if head.check then
391 | upvalues[string.format('check%s', func2id(head.check))] = head.check
392 | end
393 | table.insert(code,
394 | string.format(' if select("#", ...) == 2 and istype(select(2, ...), "table") and istype(select(1, ...), "%s")%s then',
395 | head.type,
396 | head.check and string.format(' and check%s(select(1, ...))', func2id(head.check)) or '')
397 | )
398 | table.insert(code, ' local self = select(1, ...)')
399 | table.insert(code, ' local args = select(2, ...)')
400 | table.insert(code, ' local narg = 0')
401 | table.insert(code, ' for k,v in pairs(args) do')
402 | table.insert(code, ' narg = narg + 1')
403 | table.insert(code, ' end')
404 | selfnamed:generate_ordered_or_named(code, upvalues, 'M')
405 | table.insert(code, ' end')
406 | end
407 | end
408 |
409 | for upvaluename, upvalue in pairs(upvalues) do
410 | table.insert(code, 1, string.format('local %s', upvaluename))
411 | end
412 |
413 | table.insert(code, ' assert(istype)') -- keep istype as an upvalue
414 | table.insert(code, ' assert(graph)') -- keep graph as an upvalue
415 |
416 | local quiet = true
417 | self:apply(
418 | function(self)
419 | if self.rules and not self.rules.quiet then
420 | quiet = false
421 | end
422 | end
423 | )
424 | if quiet then
425 | table.insert(code, ' return false, usage.render(graph:usage(...))')
426 | else
427 | table.insert(code, ' error(string.format("%s\\ninvalid arguments!", usage.render(graph:usage(...))))')
428 | end
429 | table.insert(code, 'end')
430 | return table.concat(code, '\n')
431 | end
432 |
433 | return ACN
434 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | argcheck
2 | ========
3 |
4 | A powerful function argument checker and function overloading system for
5 | Lua or LuaJIT.
6 |
7 | Argcheck generates specific code for checking arguments of a function. This
8 | allows complex argument checking (possibly with optional values), with
9 | little overhead (with [LuaJIT](http://luajit.org)). Argcheck computes a
10 | tree of all possible variants of arguments, allowing efficient overloading
11 | and default argument management.
12 |
13 | Installation
14 | ------------
15 |
16 | The easiest is to use [luarocks](http://www.luarocks.org).
17 |
18 | If you use Torch, simply do
19 | ```sh
20 | luarocks install argcheck
21 | ```
22 | else
23 | ```sh
24 | luarocks build https://raw.github.com/torch/argcheck/master/rocks/argcheck-scm-1.rockspec
25 | ```
26 |
27 | You can also copy the `argcheck` directory where `luajit` (or `lua`) will
28 | find it.
29 |
30 | Changelog
31 | ---------
32 |
33 | - Version 2.0 (git)
34 | - Rewrote completely the code generation.
35 | - Now creates a tree of possible argument paths (much more efficient).
36 | - Thanks to the tree approach, many bugs have been fixed.
37 | - Argcheck will produce an error if there are ambiguous argument rules.
38 | - The feature `chain` is still deprecated (but available). Use `overload` instead.
39 | - True overloading is happening. Contrary to `chain`, `overload` functions must be overwritten.
40 | - Same functionalities than in 1.0, plus
41 | - Handles named method calls
42 | - Can generate a dot graphviz file of the argument paths for debugging purposes.
43 |
44 | - Version 1.0
45 | - Simplified the way of calling argcheck.
46 | - Same functionalities than before, except named method call were not handled anymore.
47 | - Added the `call` option to call a function if the arguments match given rules.
48 | - Added the `chain` option to chain several argcheck function calls (a cheap version of overloading).
49 | - Simplified the way of adding a type.
50 |
51 | - Version 0.5
52 | - Handling of default arguments (including defaulting to another argument), optional arguments (which might be `nil`), named arguments, named only option.
53 | - Complicated way to handle method and functions.
54 | - Calling function is mandatory.
55 |
56 |
57 | Documentation
58 | ------------
59 |
60 | To use `argcheck`, you have to first `require` it:
61 | ```lua
62 | local argcheck = require 'argcheck'
63 | ```
64 | In the following, we assume this has been done in your script.
65 | Note that `argcheck` does not import anything globally, to avoid cluttering
66 | the global namespace. The value returned by the require is a function: for
67 | most usages, it will be the only thing you need.
68 |
69 | _Note that in the following examples we do not use local variables for
70 | check functions or example functions. This is bad practive, but helpful if
71 | you want to cut-and-paste the code in your interactive lua to see how
72 | this is running._
73 |
74 | The `argcheck()` function creates a fast pre-compiled function for checking
75 | arguments, according to rules provided by the user. Assume you have a
76 | function which requires a unique number argument:
77 | ```lua
78 | function addfive(x)
79 | print(string.format('%f + 5 = %f', x, x+5))
80 | end
81 | ```
82 | You can make sure everything goes fine by doing creating the rule:
83 | ```lua
84 | check = argcheck{
85 | {name="x", type="number"}
86 | }
87 |
88 | function addfive(...)
89 | local x = check(...)
90 | print(string.format('%f + 5 = %f', x, x+5))
91 | end
92 | ```
93 | If a user try to pass a wrong argument, too many arguments, or no arguments
94 | at all, `argcheck` will complain:
95 | ```lua
96 | arguments:
97 | {
98 | x = number --
99 | }
100 |
101 | stdin:2: invalid arguments
102 | ```
103 |
104 | A rule must at least take a `name` field. The `type` field is optional
105 | (even though it is highly recommended!). If `type` is not provided, `argcheck` will make
106 | sure the given argument is not `nil`. If you want also to accept `nil` arguments, see the
107 | [`opt` option](#argcheck.opt).
108 |
109 | ### Default arguments
110 | Arguments can have defaults:
111 | ```lua
112 | check = argcheck{
113 | {name="x", type="number", default=0}
114 | }
115 | ```
116 | In which case, if the argument is missing, `argcheck` will pass the default
117 | one to your function:
118 | ```lua
119 | > addfive()
120 | 0.000000 + 5 = 5.000000
121 | ```
122 |
123 | ### Help (or doc)
124 | Argcheck encourages you to add help to your function. You can document each argument:
125 | ```lua
126 | check = argcheck{
127 | {name="x", type="number", default=0, help="the age of the captain"}
128 | }
129 | ```
130 | Or even document the function:
131 | ```lua
132 | check = argcheck{
133 | help=[[
134 | This function is going to do a simple addition.
135 | Give a number, it adds 5. Amazing.
136 | ]],
137 | {name="x", type="number", default=0, help="the age of the captain"}
138 | }
139 | ```
140 | Then, if the user makes a mistake in the arguments, the error message
141 | becomes more clear:
142 | ```lua
143 | > addfive('')
144 | stdin:2: invalid arguments
145 |
146 | This function is going to do a simple addition.
147 | Give a number, it adds 5. Amazing.
148 |
149 | arguments:
150 | {
151 | [x = number] -- the age of the captain [default=0]
152 | }
153 | ```
154 |
155 | Note that is (equivalently) possible to use the key `doc=` instead of `help=`.
156 |
157 | ### Multiple arguments
158 |
159 | Until now, our function had only one argument. Obviously, argcheck can
160 | handle as many as you wish:
161 | ```lua
162 | check = argcheck{
163 | help=[[
164 | This function is going to do a simple addition.
165 | Give a number, it adds 5. Amazing.
166 | ]],
167 | {name="x", type="number", default=0, help="the age of the captain"},
168 | {name="msg", type="string", help="a message"}
169 | }
170 |
171 | function addfive(...)
172 | local x, msg = check(...)
173 | print(string.format('%f + 5 = %f', x, x+5))
174 | print(msg)
175 | end
176 | ```
177 | Argcheck handles well various cases, including those where some arguments
178 | with defaults values might be missing:
179 | ```lua
180 | > addfive(4, 'hello world')
181 | 4.000000 + 5 = 9.000000
182 | hello world
183 | >
184 | > addfive('hello world')
185 | 0.000000 + 5 = 5.000000
186 | hello world
187 | >
188 | > addfive(4)
189 |
190 | stdin:2: invalid arguments
191 |
192 | This function is going to do a simple addition.
193 | Give a number, it adds 5. Amazing.
194 |
195 | arguments:
196 | {
197 | [x = number] -- the age of the captain [default=0]
198 | msg = string -- a message
199 | }
200 | ```
201 |
202 | ### Default argument defaulting to another argument
203 |
204 | Arguments can have a default value coming from another argument, with the
205 | `defaulta` option. In the following
206 | ```lua
207 |
208 | check = argcheck{
209 | {name="x", type="number"},
210 | {name="y", type="number", defaulta="x"}
211 | }
212 |
213 | function mul(...)
214 | local x, y = check(...)
215 | print(string.format('%f x %f = %f', x, y, x*y))
216 | end
217 | ```
218 | argument `y` will take the value of `x` if it is not passed during the function call:
219 | ```lua
220 | > mul(3,4)
221 | 3.000000 x 4.000000 = 12.000000
222 | > mul(3)
223 | 3.000000 x 3.000000 = 9.000000
224 | ```
225 |
226 | ### Default arguments function
227 |
228 | In some more complex cases, sometimes one needs to run a particular function when
229 | the given argument is not provided. The option `defaultf` is here to help.
230 | ```lua
231 |
232 | idx = 0
233 |
234 | check = argcheck{
235 | {name="x", type="number"},
236 | {name="y", type="number", defaultf=function() idx = idx + 1 return idx end}
237 | }
238 |
239 | function mul(...)
240 | local x, y = check(...)
241 | print(string.format('%f x %f = %f', x, y, x*y))
242 | end
243 | ```
244 |
245 | This will output the following:
246 | ```lua
247 | > mul(3)
248 | 3.000000 x 1.000000 = 3.000000
249 | > mul(3)
250 | 3.000000 x 2.000000 = 6.000000
251 | > mul(3)
252 | 3.000000 x 3.000000 = 9.000000
253 | ```
254 |
255 |
256 | ### Optional arguments
257 |
258 | Arguments with a default value can be seen as optional. However, as they
259 | do have a default value, the underlying function will never receive a `nil`
260 | value. In some situations, one might need to declare an optional argument
261 | with no default value. You can do this with the `opt` option.
262 | ```lua
263 | check = argcheck{
264 | {name="x", type="number", default=0, help="the age of the captain"},
265 | {name="msg", type="string", help="a message", opt=true}
266 | }
267 |
268 | function addfive(...)
269 | local x, msg = check(...)
270 | print(string.format('%f + 5 = %f', x, x+5))
271 | print(msg)
272 | end
273 | ```
274 | In this example, one might call `addfive()` without the `msg` argument. Of
275 | course, the underlying function must be able to handle `nil` values:
276 | ```lua
277 | > addfive('hello world')
278 | 0.000000 + 5 = 5.000000
279 | hello world
280 | > addfive()
281 | 0.000000 + 5 = 5.000000
282 | nil
283 | ```
284 |
285 | ### Torch Tensors
286 | Argcheck supports Torch Tensors type checks.
287 | Specific tensor types like `Int`, `Float`, or `Double` can be
288 | checked with `torch.Tensor`.
289 | Any tensor type can be checked with `torch.*Tensor`.
290 |
291 | ```lua
292 | check = argcheck{
293 | {name="anyTensor", type="torch.*Tensor"},
294 | {name="fTensor", type="torch.FloatTensor"}
295 | }
296 |
297 | check(torch.IntTensor(), torch.FloatTensor()) -- Good.
298 | check(torch.FloatTensor(), torch.FloatTensor()) -- Good.
299 | check(torch.FloatTensor(), torch.IntTensor()) -- Invalid.
300 | ```
301 |
302 |
303 | ### Specific per-rule check
304 |
305 | It is possible to add an extra specific checking function for a given checking
306 | rule, with the `check` option. This function will be called (with the corresponding argument)
307 | in addition to the standard type checking. This can be useful for refined argument selection:
308 | ```lua
309 | check = argcheck{
310 | {name="x", type="number", help="a number between one and ten",
311 | check=function(x)
312 | return x >= 1 and x <= 10
313 | end}
314 | }
315 |
316 | function addfive(...)
317 | local x = check(...)
318 | print(string.format('%f + 5 = %f', x, x+5))
319 | end
320 |
321 | > addfive(3)
322 | 3.000000 + 5 = 8.000000
323 |
324 | > addfive(11)
325 | stdin:2: invalid arguments
326 |
327 | arguments:
328 | {
329 | x = number -- a number between one and ten
330 | }
331 | ```
332 |
333 | ### Named arguments
334 |
335 | Argcheck handles named argument calls. Following the previous example, both
336 | ```lua
337 | addfive(1, "hello world")
338 | ```
339 | and
340 | ```lua
341 | addfive{x=1, msg="hello world"}
342 | ```
343 | are valid. However, ordered arguments are handled in a *much faster* way
344 | (especially with LuaJIT) than named arguments.
345 |
346 | ### Method named arguments
347 |
348 | The common way to define a "method" in Lua is by doing the following:
349 | ```lua
350 | local object = {}
351 |
352 | function object:foobar(x, msg) -- a method foobar
353 | end
354 | ```
355 |
356 | The syntax sugar call `object:foobar(x, msg)` is equivalent to the function call
357 | ```lua
358 | object.foobar(object, x, msg)
359 | ```
360 |
361 | Calling a method with named arguments would be done with
362 | ```lua
363 | object:foobar{x=..., msg=...}
364 | ```
365 | (where `...` is the actual content of x and msg). This translates to
366 | `foobar(object, {x=..., msg=...})`, which is not a regular named function
367 | call, given that the `object` itself should not be treated as a named
368 | argument. Argcheck will handle such calls, provided the name of the object
369 | argument is `self`, in the rule definition. For e.g.:
370 | ```lua
371 | local object = {checksum=1234567} -- the object is just a table here
372 | local check = argcheck{
373 | {name="self", type="table"}, -- check the type of self
374 | {name="x", type="number"},
375 | {name="msg", type="string", default="i know what i am doing"},
376 | }
377 |
378 | function object.foobar(...) -- note the '.', given we type-check self too
379 | local self, x, msg = check(...)
380 | print(string.format('%f + 5 = %f [msg = %s] [self.checksum=%s]', x, x+5, msg, self.checksum))
381 | end
382 |
383 | -- method ordered arguments call
384 | > object:foobar(5, 'hello world')
385 | 5.000000 + 5 = 10.000000 [msg = hello world] [self.checksum=1234567]
386 |
387 | -- method named arguments call (works too!)
388 | > object:foobar{x=5, msg='hello world'}
389 | 5.000000 + 5 = 10.000000 [msg = hello world] [self.checksum=1234567]
390 |
391 | -- default argument (and other things) work the same than previously
392 | > object:foobar(7)
393 | 7.000000 + 5 = 12.000000 [msg = i know what i am doing] [self.checksum=1234567]
394 |
395 | > object:foobar{x=7}
396 | 7.000000 + 5 = 12.000000 [msg = i know what i am doing] [self.checksum=1234567]
397 | ```
398 |
399 | Note: `argcheck` assumes the function defined by a set of rules is in fact
400 | a method, if the name of the first rule is `self`.
401 |
402 | ### Options global to all rules
403 |
404 | Argcheck has several interesting global options, as the `help` (or `doc`) we have introduced already.
405 | Those global options are simply set in the main `argcheck` table:
406 | ```lua
407 | check = argcheck{
408 | help = "blah blah", -- global help option
409 | ...
410 | }
411 | ```
412 | Other global options are described in the following.
413 |
414 | #### Function call
415 |
416 | An important feature of `argcheck` is its ability to call a function if the
417 | passed arguments match the defined rules.
418 |
419 | Taking back the first example, one could use the `call` option and rewrite
420 | it as:
421 | ```lua
422 | addfive = argcheck{
423 | {name="x", type="number"},
424 |
425 | call = function(x)
426 | print(string.format('%f + 5 = %f', x, x+5))
427 | end
428 | }
429 |
430 | > addfive(5)
431 | 5.000000 + 5 = 10.000000
432 |
433 | > addfive()
434 | stdin:1: arguments:
435 | {
436 | x = number --
437 | }
438 | ```
439 |
440 | As we will see below, `argcheck` can also handle function overloading, and
441 | other complex situations, in which the `call` feature can simplify the
442 | programmer's life. In that respect, it is highly encouraged to use this
443 | feature.
444 |
445 | #### Pack arguments into a table
446 |
447 | In some cases, it might be interesting to get all arguments into a
448 | table. This is not recommended in general, as creating a table slows down
449 | the checking process. However, when one was *a lot* of arguments, the
450 | `pack` option might be of interest. The function created by `argcheck`
451 | then returns a table containing all arguments with rule names as keys.
452 | ```lua
453 | check = argcheck{
454 | pack=true,
455 | {name="x", type="number", default=0, help="the age of the captain"},
456 | {name="msg", type="string", help="a message"}
457 | }
458 |
459 | function addfive(...)
460 | local args = check(...) -- now arguments are stored in this table
461 | print(string.format('%f + 5 = %f', args.x, args.x+5))
462 | print(args.msg)
463 | end
464 |
465 | > addfive(5, 'hello world')
466 | 5.000000 + 5 = 10.000000
467 | hello world
468 | ```
469 |
470 | #### Restrict to named-only or ordered-only arguments
471 |
472 | In some very special (rare) cases, one might want to disable named calls
473 | like `addfive{x=1, msg='blah'}`, and stick to only ordered arguments like
474 | `addfive(1, 'blah')`, or vice-versa. That might be to handle some ambiguous
475 | calls, e.g. when one has to deal with table arguments. The options
476 | `nonamed` and `noordered` can be used for that purpose:
477 |
478 | ```lua
479 | check = argcheck{
480 | nonamed=true,
481 | {name="x", type="number", default=0, help="the age of the captain"},
482 | {name="msg", type="string", help="a message"}
483 | }
484 |
485 | function addfive(...)
486 | local x, msg = check(...)
487 | print(string.format('%f + 5 = %f', x, x+5))
488 | print(msg)
489 | end
490 |
491 | > addfive('blah')
492 | 0.000000 + 5 = 5.000000
493 | blah
494 |
495 | > addfive{msg='blah'}
496 | stdin:2: invalid arguments
497 |
498 | arguments:
499 | {
500 | [x = number] -- the age of the captain [default=0]
501 | msg = string -- a message
502 | }
503 | ```
504 |
505 | #### Quiet
506 |
507 | If you want to handle errors yourself, you might want to make sure the
508 | checking function is quiet. The `quiet=true` option is here for this. If
509 | mentioned, the argument checker will return a boolean (`true` in case of
510 | success, `false` if arguments do not match rules), followed by the
511 | arguments (possibly packed). In case of failure `false` is followed by the
512 | help message.
513 |
514 | ```lua
515 | check = argcheck{
516 | quiet=true,
517 | {name="x", type="number", default=0, help="the age of the captain"},
518 | {name="msg", type="string", help="a message"}
519 | }
520 |
521 | > print(check(5, 'hello world'))
522 | true 5 hello world
523 |
524 | > print(check(5))
525 | false arguments:
526 | {
527 | [x = number] -- the age of the captain [default=0]
528 | msg = string -- a message
529 | }
530 | ```
531 |
532 | #### Overloading
533 |
534 | It is possible to overload previous created argchecks manually. E.g., in our example,
535 | if we want `addfive()` to handle the case of a number or string argument,
536 | one could leverage the `quiet` global option and do the following:
537 | ```lua
538 | checknum = argcheck{
539 | quiet=true,
540 | {name="x", type="number"}
541 | }
542 |
543 | checkstr = argcheck{
544 | quiet=true,
545 | {name="str", type="string"}
546 | }
547 |
548 | function addfive(...)
549 |
550 | -- first case
551 | local status, x = checknum(...)
552 | if status then
553 | print(string.format('%f + 5 = %f', x, x+5))
554 | return
555 | end
556 |
557 | -- second case
558 | local status, str = checkstr(...)
559 | if status then
560 | print(string.format('%s .. 5 = %s', str, str .. '5'))
561 | return
562 | end
563 |
564 | -- note that in case of failure with quiet, the error is returned after the status
565 | print('usage:\n\n' .. x .. '\n\nor\n\n' .. str)
566 | error('invalid arguments')
567 | end
568 |
569 | > addfive(123)
570 | 5.000000 + 5 = 10.000000
571 |
572 | > addfive('hi')
573 | hi .. 5 = hi5
574 |
575 | > addfive()
576 | usage:
577 |
578 | arguments:
579 | {
580 | x = number --
581 | }
582 |
583 | or
584 |
585 | arguments:
586 | {
587 | str = string --
588 | }
589 | stdin:19: invalid arguments
590 | ```
591 |
592 | This can however quickly become a burden, if there are many possible
593 | argument variations. Instead, one can use the `overload` option, which is
594 | supposed to be used together with `call`. The value provided to `overload`
595 | must be a function previously created by `argcheck`.
596 |
597 | If the arguments do not match any given variations, then the created
598 | argument checker will show a global error message, with usage summarizing
599 | all possibilites.
600 |
601 | When overloading, `argcheck` will create a new function (for efficiency
602 | reasons) including all possible cases which are being overloaded, as well
603 | as the new given case. _Beware_ to overwrite the returned `argcheck`
604 | function each time you overload one!
605 |
606 | The previous example is then equivalent to:
607 | ```lua
608 | addfive = argcheck{
609 | {name="x", type="number"},
610 | call = function(x) -- called in case of success
611 | print(string.format('%f + 5 = %f', x, x+5))
612 | end
613 | }
614 |
615 | addfive = argcheck{ -- overwrite it
616 | {name="str", type="string"},
617 | overload = addfive, -- overload the previous one
618 | call = function(str) -- called in case of success
619 | print(string.format('%s .. 5 = %s', str, str .. '5'))
620 | end
621 | }
622 |
623 | th> addfive(5)
624 | 5.000000 + 5 = 10.000000
625 |
626 | th> addfive('hi')
627 | hi .. 5 = hi5
628 |
629 | th> addfive()
630 | stdin:1: arguments:
631 | {
632 | x = number --
633 | }
634 |
635 | or
636 |
637 | arguments:
638 | {
639 | str = string --
640 | }
641 | ```
642 |
643 | #### Force
644 |
645 | `argcheck` hates ambiguities, and will spit out an error message if you try
646 | to create some rules which are ambiguous. This can in fact happen easily
647 | when overloading, or when mixing named/ordered arguments.
648 |
649 | For example:
650 | ```lua
651 | addfive = argcheck{
652 | {name="x", type="number"},
653 | call =
654 | function(x) -- called in case of success
655 | print(string.format('%f + 5 = %f', x, x+5))
656 | end
657 | }
658 |
659 | addfive = argcheck{
660 | {name="x", type="number"},
661 | {name="msg", type="string", default="i know what i am doing"},
662 | overload = addfive,
663 | call =
664 | function(x, msg) -- called in case of success
665 | print(string.format('%f + 5 = %f [msg = %s]', x, x+5, msg))
666 | end
667 | }
668 | ```
669 |
670 | will led to the error message "argcheck rules led to ambiguous
671 | situations". One can override this behavior, with the `force` flag:
672 | ```lua
673 | addfive = argcheck{
674 | {name="x", type="number"},
675 | {name="msg", type="string", default="i know what i am doing"},
676 | overload = addfive,
677 | force = true,
678 | call =
679 | function(x, msg) -- called in case of success
680 | print(string.format('%f + 5 = %f [msg = %s]', x, x+5, msg))
681 | end
682 | }
683 | ```
684 | In this case, consider the subsequent calls:
685 | ```lua
686 | > addfive(5, 'hello')
687 | 5.000000 + 5 = 10.000000 [msg = hello]
688 | > addfive(5)
689 | 5.000000 + 5 = 10.000000 [msg = i know what i am doing]
690 | ```
691 | Note that the first function is then never called (you know what you are doing!).
692 |
693 | #### Debug
694 |
695 | Adding `debug=true` as global option will simply dump in stdout the
696 | corresponding code for the given checking argument function. It will also
697 | return a [dot graph](http://www.graphviz.org), for better understanding of
698 | what is going on.
699 |
700 | ```lua
701 | check, dotgraph = argcheck{
702 | debug=true,
703 | {name="x", type="number", default=0, help="the age of the captain"},
704 | {name="msg", type="string", help="a message"}
705 | }
706 |
707 | local arg0403e9b0_1d
708 | local istype
709 | local graph
710 | return function(...)
711 | local narg = select("#", ...)
712 | if narg >= 1 and istype(select(1, ...), "number") then
713 | if narg >= 2 and istype(select(2, ...), "string") then
714 | if narg == 2 then
715 | local arg1 = select(1, ...)
716 | local arg2 = select(2, ...)
717 | return arg1, arg2
718 | end
719 | end
720 | end
721 | if narg >= 1 and istype(select(1, ...), "string") then
722 | if narg == 1 then
723 | local arg2 = select(1, ...)
724 | local arg1 = arg0403e9b0_1d
725 | return arg1, arg2
726 | end
727 | end
728 | if narg == 1 and istype(select(1, ...), "table") then
729 | local args = select(1, ...)
730 | local narg = 0
731 | for k,v in pairs(args) do
732 | narg = narg + 1
733 | end
734 | if narg >= 1 and istype(args.x, "number") then
735 | if narg >= 2 and istype(args.msg, "string") then
736 | if narg == 2 then
737 | local arg1 = args.x
738 | local arg2 = args.msg
739 | return arg1, arg2
740 | end
741 | end
742 | end
743 | if narg >= 1 and istype(args.msg, "string") then
744 | if narg == 1 then
745 | local arg2 = args.msg
746 | local arg1 = arg0403e9b0_1d
747 | return arg1, arg2
748 | end
749 | end
750 | end
751 | assert(graph)
752 | error(string.format("%s\ninvalid arguments!", graph:usage()))
753 | end
754 |
755 | > print(dotgraph)
756 | digraph ACN {
757 | edge [penwidth=.3 arrowsize=0.8];
758 | id0403efc0 [label="@" penwidth=.1 fontsize=10 style=filled fillcolor="#eeeeee"];
759 | edge [penwidth=.3 arrowsize=0.8];
760 | id0403f460 [label="number" penwidth=.1 fontsize=10 style=filled fillcolor="#eeeeee"];
761 | edge [penwidth=.3 arrowsize=0.8];
762 | id0403f530 [label="string" penwidth=.1 fontsize=10 style=filled fillcolor="#aaaaaa"];
763 | id0403f460 -> id0403f530;
764 | id0403efc0 -> id0403f460;
765 | edge [penwidth=.3 arrowsize=0.8];
766 | id0403f7b8 [label="table" penwidth=.1 fontsize=10 style=filled fillcolor="#eeeeee"];
767 | edge [penwidth=.3 arrowsize=0.8];
768 | id0403f618 [label="number (x)" penwidth=.1 fontsize=10 style=filled fillcolor="#eeeeee"];
769 | edge [penwidth=.3 arrowsize=0.8];
770 | id04040068 [label="string (msg)" penwidth=.1 fontsize=10 style=filled fillcolor="#aaaaaa"];
771 | id0403f618 -> id04040068;
772 | id0403f7b8 -> id0403f618;
773 | edge [penwidth=.3 arrowsize=0.8];
774 | id040408b0 [label="string (msg)" penwidth=.1 fontsize=10 style=filled fillcolor="#aaaaaa"];
775 | id0403f7b8 -> id040408b0;
776 | id0403efc0 -> id0403f7b8;
777 | edge [penwidth=.3 arrowsize=0.8];
778 | id04040390 [label="string" penwidth=.1 fontsize=10 style=filled fillcolor="#aaaaaa"];
779 | id0403efc0 -> id04040390;
780 | }
781 | ```
782 |
783 | As you can see, for a simple example like this one, the code is already not
784 | that trivial, but handles both named and ordered arguments. Generating an
785 | image out of the graph with dot (e.g. with `dot -Tpng`), leads to the
786 | following:
787 |
788 | 
789 |
790 | Nodes with `(...)` are nodes corresponding to named arguments. Dark gray
791 | nodes represent valid paths in the graph. Node with a `*` suffix (after the
792 | type name) are nodes which _might_ be `self` first argument of a method
793 | (not present in the shown example).
794 |
795 | ### Advanced usage
796 |
797 | By default, `argcheck` uses the standard `type()` Lua function to determine the type of your
798 | arguments. In some cases, like if you are handling your own class system, you might want to
799 | specify how to check types. This can be simply done by overriding the `istype()` function
800 | available in the `argcheck` environment.
801 | ```lua
802 | env = require 'argcheck.env' -- retrieve argcheck environement
803 |
804 | -- this is the default type function
805 | -- which can be overrided by the user
806 | function env.istype(obj, typename)
807 | return type(obj) == typename
808 | end
809 | ```
810 | Note that if you change the `istype()` function, it will *not* affect previously defined
811 | argument checking functions: `istype()` is passed as an upvalue for each created argument
812 | function.
813 |
814 | ### Real-life example
815 |
816 | See [our cairo FFI interface](https://github.com/torch/cairo-ffi), which
817 | leverages `argcheck`.
818 |
819 | ### Benchmark
820 |
821 | See [the `argcheck` benchmark page](benchmark/README.md) for detailed performance report.
822 |
--------------------------------------------------------------------------------