├── 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 | ![](doc/tree1.png) 79 | When it includes all the 7 types of tensors: 80 | ![](doc/tree7.png) 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 | ![](doc/tree.png) 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 | --------------------------------------------------------------------------------