# General Lua Libraries for Lua 5.1, 5.2 & 5.3 # Copyright (C) 2011-2018 stdlib authors before: | this_module = 'std' global_table = '_G' exported_apis = {'assert', 'elems', 'eval', 'getmetamethod', 'ielems', 'ipairs', 'npairs', 'pairs', 'require', 'ripairs', 'rnpairs'} -- Tables with iterator metamethods used by various examples. __pairs = setmetatable({content='a string'}, { __pairs = function(t) return function(x, n) if n < #x.content then return n+1, string.sub(x.content, n+1, n+1) end end, t, 0 end, }) __index = setmetatable({content='a string'}, { __index = function(t, n) if n <= #t.content then return t.content:sub(n, n) end end, __len = function(t) return #t.content end, }) M = require(this_module) M.version = nil -- previous specs may have autoloaded it specify std: - context when required: - it does not touch the global table: expect(show_apis {added_to=global_table, by=this_module}). to_equal {} - it exports the documented apis: t = {} for k in pairs(M) do t[#t + 1] = k end expect(t).to_contain.a_permutation_of(exported_apis) - context when lazy loading: - it has no submodules on initial load: for _, v in pairs(M) do expect(type(v)).not_to_be 'table' end - it loads submodules on demand: lazy = M.math expect(lazy).to_be(require 'std.math') - it loads submodule functions on demand: expect(M.math.round(3.141592)).to_be(3) - describe assert: - before: f = M.assert - context with bad arguments: badargs.diagnose(f, 'std.assert(?any, ?string, ?any*)') - context when it does not trigger: - it has a truthy initial argument: expect(f(1)).not_to_raise 'any error' expect(f(true)).not_to_raise 'any error' expect(f 'yes').not_to_raise 'any error' expect(f(false == false)).not_to_raise 'any error' - it returns the initial argument: expect(f(1)).to_be(1) expect(f(true)).to_be(true) expect(f 'yes').to_be 'yes' expect(f(false == false)).to_be(true) - context when it triggers: - it has a falsey initial argument: expect(f()).to_raise() expect(f(false)).to_raise() expect(f(1 == 0)).to_raise() - it throws an optional error string: expect(f(false, 'ah boo')).to_raise 'ah boo' - it plugs specifiers with string.format: | expect(f(nil, '%s %d: %q', 'here', 42, 'a string')). to_raise(string.format('%s %d: %q', 'here', 42, 'a string')) - describe elems: - before: f = M.elems - context with bad arguments: badargs.diagnose(f, 'std.elems(table)') - it is an iterator over table values: t = {} for e in f {'foo', bar='baz', 42} do t[#t + 1] = e end expect(t).to_contain.a_permutation_of {'foo', 'baz', 42} - it respects __pairs metamethod: | t = {} for v in f(__pairs) do t[#t + 1] = v end expect(t). to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} - it works for an empty list: t = {} for e in f {} do t[#t + 1] = e end expect(t).to_equal {} - describe eval: - before: f = M.eval - context with bad arguments: badargs.diagnose(f, 'std.eval(string)') - it diagnoses invalid lua: # Some internal error when eval tries to call uncompilable '=' code. expect(f '=').to_raise() - it evaluates a string of lua code: expect(f 'math.min(2, 10)').to_be(math.min(2, 10)) - describe getmetamethod: - before: f = M.getmetamethod - context with bad arguments: badargs.diagnose(f, 'std.getmetamethod(?any, string)') - context with a table: - before: method = function() return 'called' end functor = setmetatable({}, {__call=method}) t = setmetatable({}, { _type='table', _method=method, _functor=functor, }) - it returns nil for missing metamethods: expect(f(t, 'not a metamethod on t')).to_be(nil) - it returns nil for non-callable metatable entries: expect(f(t, '_type')).to_be(nil) - it returns a method from the metatable: expect(f(t, '_method')).to_be(method) expect(f(t, '_method')()).to_be 'called' - it returns a functor from the metatable: expect(f(t, '_functor')).to_be(functor) expect(f(t, '_functor')()).to_be 'called' - describe ielems: - before: f = M.ielems - context with bad arguments: badargs.diagnose(f, 'std.ielems(table)') - it is an iterator over integer-keyed table values: t = {} for e in f {'foo', 42} do t[#t + 1] = e end expect(t).to_equal {'foo', 42} - it ignores the dictionary part of a table: t = {} for e in f {'foo', 42; bar='baz', qux='quux'} do t[#t + 1] = e end expect(t).to_equal {'foo', 42} - it respects __len metamethod: t = {} for v in f(__index) do t[#t + 1] = v end expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} - it works for an empty list: t = {} for e in f {} do t[#t + 1] = e end expect(t).to_equal {} - describe ipairs: - before: f = M.ipairs - context with bad arguments: badargs.diagnose(f, 'std.ipairs(table)') - it is an iterator over integer-keyed table values: t = {} for i, v in f {'foo', 42} do t[i] = v end expect(t).to_equal {'foo', 42} - it ignores the dictionary part of a table: t = {} for i, v in f {'foo', 42; bar='baz', qux='quux'} do t[i] = v end expect(t).to_equal {'foo', 42} - it respects __len metamethod: t = {} for k, v in f(__index) do t[k] = v end expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} - it works for an empty list: t = {} for i, v in f {} do t[i] = v end expect(t).to_equal {} - describe npairs: - before: f = M.npairs - context with bad arguments: badargs.diagnose(f, 'std.npairs(table)') - it is an iterator over integer-keyed table values: t = {} for i, v in f {'foo', 42, nil, nil, 'five'} do t[i] = v end expect(t).to_equal {'foo', 42, nil, nil, 'five'} - it ignores the dictionary part of a table: t = {} for i, v in f {'foo', 42, nil, nil, 'five'; bar='baz', qux='quux'} do t[i] = v end expect(t).to_equal {'foo', 42, nil, nil, 'five'} - it respects __len metamethod: t = {} for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do t[#t + 1] = tostring(v) end expect(table.concat(t, ',')).to_be 'nil,false,nil,nil' - it works for an empty list: t = {} for i, v in f {} do t[i] = v end expect(t).to_equal {} - describe pairs: - before: f = M.pairs - context with bad arguments: badargs.diagnose(f, 'std.pairs(table)') - it is an iterator over all table values: t = {} for k, v in f {'foo', bar='baz', 42} do t[k] = v end expect(t).to_equal {'foo', bar='baz', 42} - it respects __pairs metamethod: | t = {} for k, v in f(__pairs) do t[k] = v end expect(t). to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} - it works for an empty list: t = {} for k, v in f {} do t[k] = v end expect(t).to_equal {} - describe require: - before: f = M.require - context with bad arguments: badargs.diagnose(f, 'std.require(string, ?string, ?string, ?string)') - it diagnoses non-existent module: expect(f('module-not-exists', '', '')).to_raise 'module-not-exists' - it diagnoses module too old: expect(f('std', '9999', '9999')). to_raise "require 'std' with at least version 9999," - it diagnoses module too new: expect(f('std', '0', '0')). to_raise "require 'std' with version less than 0," - context when the module version is compatible: - it returns the module table: expect(f('std', '0', '9999')).to_be(require 'std') - it places no upper bound by default: expect(f('std', '0')).to_be(require 'std') - it places no lower bound by default: expect(f 'std').to_be(require 'std') - it uses _VERSION when version field is nil: expect(luaproc [[ package.loaded['poop'] = {_VERSION='41.1'} f = require 'std'.require print(f('poop', '41', '9999')._VERSION) ]]).to_succeed_with '41.1\n' - context with semantic versioning: - before: std = require 'std' ver = std.version std.version = '1.2.3' - after: std.version = ver - it diagnoses module too old: expect(f('std', '1.2.4')). to_raise "require 'std' with at least version 1.2.4," expect(f('std', '1.3')). to_raise "require 'std' with at least version 1.3," expect(f('std', '2.1.2')). to_raise "require 'std' with at least version 2.1.2," expect(f('std', '2')). to_raise "require 'std' with at least version 2," expect(f('std', '1.2.10')). to_raise "require 'std' with at least version 1.2.10," - it diagnoses module too new: expect(f('std', nil, '1.2.2')). to_raise "require 'std' with version less than 1.2.2," expect(f('std', nil, '1.1')). to_raise "require 'std' with version less than 1.1," expect(f('std', nil, '1.1.2')). to_raise "require 'std' with version less than 1.1.2," expect(f('std', nil, '1')). to_raise "require 'std' with version less than 1," - it returns modules with version in range: expect(f('std')).to_be(std) expect(f('std', '1')).to_be(std) expect(f('std', '1.2.3')).to_be(std) expect(f('std', nil, '2')).to_be(std) expect(f('std', nil, '1.3')).to_be(std) expect(f('std', nil, '1.2.10')).to_be(std) expect(f('std', '1.2.3', '1.2.4')).to_be(std) - context with several numbers in version string: - before: std = require 'std' ver = std.version std.version = 'standard library for Lua 5.3 / 41.0.0' - after: std.version = ver - it diagnoses module too old: expect(f('std', '42')).to_raise() - it diagnoses module too new: expect(f('std', nil, '40')).to_raise() - it returns modules with version in range: expect(f('std')).to_be(std) expect(f('std', '1')).to_be(std) expect(f('std', '41')).to_be(std) expect(f('std', nil, '42')).to_be(std) expect(f('std', '41', '42')).to_be(std) - describe ripairs: - before: f = M.ripairs - context with bad arguments: badargs.diagnose(f, 'std.ripairs(table)') - it returns a function, the table and a number: fn, t, i = f {1, 2, 3} expect({type(fn), t, type(i)}).to_equal {'function', {1, 2, 3}, 'number'} - it iterates over the array part of a table: t, u = {1, 2, 3; a=4, b=5, c=6}, {} for i, v in f(t) do u[i] = v end expect(u).to_equal {1, 2, 3} - it returns elements in reverse order: t, u = {'one', 'two', 'five'}, {} for _, v in f(t) do u[#u + 1] = v end expect(u).to_equal {'five', 'two', 'one'} - it respects __len metamethod: t = {} for i, v in f(__index) do t[i] = v end expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'} t = {} for _, v in f(__index) do t[#t + 1] = v end expect(t).to_equal {'g', 'n', 'i', 'r', 't', 's', ' ', 'a'} - it works with the empty list: t = {} for k, v in f {} do t[k] = v end expect(t).to_equal {} - describe rnpairs: - before: f = M.rnpairs - context with bad arguments: badargs.diagnose(f, 'std.rnpairs(table)') - it returns a function, the table and a number: fn, t, i = f {1, 2, nil, nil, 3} expect({type(fn), t, type(i)}). to_equal {'function', {1, 2, nil, nil, 3}, 'number'} - it iterates over the array part of a table: t, u = {1, 2, nil, nil, 3; a=4, b=5, c=6}, {} for i, v in f(t) do u[i] = v end expect(u).to_equal {1, 2, nil, nil, 3} - it returns elements in reverse order: t, u, i = {'one', 'two', nil, nil, 'five'}, {}, 1 for _, v in f(t) do u[i], i = v, i + 1 end expect(u).to_equal {'five', nil, nil, 'two', 'one'} - it respects __len metamethod: t = {} for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do t[#t + 1] = tostring(v) end expect(table.concat(t, ',')).to_be 'nil,nil,false,nil' - it works with the empty list: t = {} for k, v in f {} do t[k] = v end expect(t).to_equal {}