local SUITE = require('TestSuite').new('function') SUITE:setEnviroment{ format = require('pretty') } -------------------------------------------------------------------------------- local HAS_ADV_GETLOCAL = not not debug.getinfo(1, 'u').nparams -- Lua 5.1 compat if not loadstring then loadstring = load end -- Lua 5.3 compat local function format_test (t) if t.longterm then return end if t.adv_getlocal and not HAS_ADV_GETLOCAL then return end SUITE:addTest(t.name or t.expect, function () local input_value = t.input local input_options = t.options local expected_result = t.expect local actual_result = format(input_value, input_options) if not t.approx or type(actual_result) ~= 'string' then assert_equal(expected_result, actual_result) else if not actual_result:match(expected_result) then error(ASSERT_ERROR_APPROX:format(expected_result, actual_result)) end end end, { line = debug.getinfo(2).currentline }) end local function curline (delta) return debug.getinfo(2).currentline + (delta or 0) end -------------------------------------------------------------------------------- -- Function printing format_test { adv_getlocal = true, input = function () end, expect = 'function () ... end', } format_test { adv_getlocal = true, input = function (a) end, expect = 'function (a) ... end', } format_test { adv_getlocal = true, input = function (a, b) end, expect = 'function (a, b) ... end', } format_test { adv_getlocal = true, input = function (...) end, expect = 'function (...) ... end', } format_test { adv_getlocal = true, input = function (a, b, ...) end, expect = 'function (a, b, ...) ... end', } do local SOME_RANDOM_UPVALUE = false format_test { adv_getlocal = true, input = function () l = SOME_RANDOM_UPVALUE end, expect = 'function () ... end', } format_test { adv_getlocal = true, input = function () SOME_RANDOM_UPVALUE = true end, expect = 'function () ... end', } format_test { -- More function info is ignored if not at depth 0. adv_getlocal = true, input = { a = function () SOME_RANDOM_UPVALUE = true end }, options = { more_function_info = true }, expect = '{ a = function () ... end }', } local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () l = SOME_RANDOM_UPVALUE end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Line: '..func_line..']\n\t-- up_values: { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' } local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () SOME_RANDOM_UPVALUE = true end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Line: '..func_line..']\n\t-- up_values: { SOME_RANDOM_UPVALUE = false }\n\n\t...\nend' } end do local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Line: '..func_line..']\n\n\t...\nend' } end do local index = 0 format_test { input = function () index = index + 1; return index end, adv_getlocal = true, expect = 'function () ... end' } local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () index = index + 1; return index end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Line: '..func_line..']\n\t-- up_values: { index = 0 }\n\n\t...\nend' } end format_test { adv_getlocal = true, input = loadstring('return function () end')(), expect = 'function () end', } format_test { adv_getlocal = true, input = loadstring('return function () return function () end end')(), expect = 'function () return function () end end', } format_test { adv_getlocal = true, input = loadstring('return function () return function () end\nend')()(), expect = 'function () end', } format_test { -- NOTE: This is HARD to fix. It's thus longerterm adv_getlocal = true, input = loadstring('return function () return function () end end')()(), expect = 'function () end', } format_test { -- More function info allows one to even get the function whole, if it was defined in a string. input = loadstring('return function (a, b) return a + b end')(), options = { more_function_info = true }, expect = 'function (a, b) return a + b end', } format_test { -- More function info allows one to even get the function whole, if it was defined in a string. input = loadstring('return function (a, b)\n\treturn a + b\nend')(), options = { more_function_info = true }, expect = 'function (a, b)\n\treturn a + b\nend', } do local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () -- NOTE: This function must cover 3 lines of code! end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Lines: '..func_line..' - '..(func_line+2)..']\n\n\t...\nend', } local func_line = curline(2) -- Must be exactly 2 lines above function format_test { input = function () --[[ NOTE: This function must cover a single line of code! ]] end, adv_getlocal = true, options = { more_function_info = true }, expect = 'function ()\n\t-- source_file: \'./test/test_function.lua\' [Line: '..func_line..']\n\n\t...\nend', } end format_test { input = math.abs, expect = 'builtin function (x) ... end', } format_test { input = math.abs, options = { more_function_info = true }, expect = 'builtin function (x)\n\t-- math.abs\n\t-- Returns the absolute value of x.\n\n\t...\nend', } format_test { input = math.random, expect = 'builtin function ([m [, n]) ... end', } format_test { input = math.random, options = { more_function_info = true }, expect = 'builtin function ([m [, n])\n\t-- math.random\n\t-- When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n].\n\n\t...\nend', } format_test { input = string.byte, options = { more_function_info = true }, expect = 'builtin function (s [, i [, j]])\n\t-- string.byte\n\t-- Returns the internal numerical codes of the characters s[i], s[i+1], ..., s[j]. The default value for i is 1; the default value for j is i.\n\t-- Note that numerical codes are not necessarily portable across platforms.\n\n\t...\nend', } -- short_builtins option: If an builtin is expected to be available by some name -- in a standard enviroment, return that name, instead of other complex info. format_test { input = math.random, options = { short_builtins = true }, expect = 'math.random', } format_test { input = { math.cos, math.sin, math.abs }, options = { short_builtins = true }, expect = '{ math.cos, math.sin, math.abs }', } -------------------------------------------------------------------------------- -- Closure creation -- include_closure option: If a function uses upvalues, return code that creates -- a closure for that function, including the function code. -- NOTE: Without AST traversal, it's impossible to garentee that values refered -- by multiple recursive closures are the same, because they can actually refer -- to different variables with the same name, as the following example shows: -- -- local val_a = 1 -- local func_a = function () return val_a end -- local val_a = 2 -- local func_c = function () return func_a() + val_a end do format_test { adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = function (a, b) return (a > b) and a or b end, expect = 'function (a, b) return (a > b) and a or b end', } end do local input_func = (function () local i = 0 return function (a) i = i + a; return i end end)() for i = 1, 10 do input_func(1) end format_test { adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = input_func, expect = '(function () local i = 10; return function (a) i = i + a; return i end end)()', } end do local input_func = loadstring([[ return function () local i = 0 return function (a) i = i + a; return i end end]])()() for i = 1, 10 do input_func(1) end format_test { name = 'Can include variables in closure', adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = input_func, expect = '(function () local i = 10; return function (a) i = i + a; return i end end)()', } end format_test { name = 'Can include functions in closure', adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = (function () local custom_max = function (a, b) return (a > b) and a or b end return function (a, b) return custom_max(a, b) + 2 end end)(), expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', } format_test { name = 'Can include functions defined with `function () ...` style', adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = (function () local function custom_max (a, b) return (a > b) and a or b end return function (a, b) return custom_max(a, b) + 2 end end)(), expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', } do local custom_max = function (a, b) return (a > b) and a or b end format_test { name = 'Can include functions from outside scope into closure', adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = (function () return function (a, b) return custom_max(a, b) + 2 end end)(), expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', } end do local a_func = function (x) return x + 2 end local b_func = function (x) return a_func(x) * a_func(x) end local c_func = function (x) return b_func(a_func(x)) end format_test { name = 'Can support recursive closures', adv_getlocal = true, options = { more_function_info = true, include_closure = true }, input = c_func, expect = '(function () local b_func = (function () local a_func = function (x) return x + 2 end; return function (x) return a_func(x) * a_func(x) end end)(); local a_func = function (x) return x + 2 end; return function (x) return b_func(a_func(x)) end end)()', } end format_test { name = 'Closures do not affect builtins', input = math.abs, options = { more_function_info = true, include_closure = true }, expect = 'builtin function (x)\n\t-- math.abs\n\t-- Returns the absolute value of x.\n\n\t...\nend', } -------------------------------------------------------------------------------- -- Indent functions nicely format_test { -- The tail part should align, letting people focus on the important aspects. input = { random = math.random, abs = math.abs }, expect = '{\n\tabs = builtin function (x) ... end,\n\trandom = builtin function ([m [, n]) ... end\n}', } format_test { -- The function part should align, if some are builtin and some are not. adv_getlocal = true, input = { random = math.random, abs = function (x) return x < 0 and -x or x end }, expect = '{\n\tabs = function (x) ... end,\n\trandom = builtin function ([m [, n]) ... end\n}', } format_test { -- No special indent if no special function modifier. adv_getlocal = true, input = { max = function(a, b) return a > b and a or b end, abs = function (x) return x < 0 and -x or x end }, expect = '{\n\tabs = function (x) ... end,\n\tmax = function (a, b) ... end\n}', } -------------------------------------------------------------------------------- --[[ format_test { adv_getlocal = true, options = { more_function_info = true, _all_function_info = true, max_depth = 2 }, input = function() end, expect = '', } --]] return SUITE