Added closure creation.
This commit is contained in:
parent
8f956bf444
commit
588e5588ac
92
function.lua
92
function.lua
|
@ -63,11 +63,20 @@ local function get_line_index (str, line_nr)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_full_function_str (str, start_line, end_line)
|
local function get_full_function_str (str, start_line, end_line)
|
||||||
|
-- Will attempt to find a string which refer to a function starting on
|
||||||
|
-- line `start_line` and ending on line `end_line`.
|
||||||
local start_line_index = get_line_index(str, start_line)
|
local start_line_index = get_line_index(str, start_line)
|
||||||
local end_line_index = get_line_index(str, end_line + 1)
|
local end_line_index = get_line_index(str, end_line + 1)
|
||||||
|
|
||||||
local matched_function = str:sub(start_line_index, end_line_index):match('function.+end')
|
local function_body = str:sub(start_line_index, end_line_index):match('function%s*[a-zA-Z0-9_.]*%s*(.+)end')
|
||||||
return matched_function
|
return 'function '..function_body..'end'
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_function_str_from_file (filename, start_line, end_line)
|
||||||
|
local file = io.open(filename, 'r')
|
||||||
|
local str = file:read('*all')
|
||||||
|
file:close()
|
||||||
|
return get_full_function_str(str, start_line, end_line)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function width_of_strings_in_l (l, start_i, end_i)
|
local function width_of_strings_in_l (l, start_i, end_i)
|
||||||
|
@ -81,9 +90,40 @@ end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function format_function_with_closure (value, options, depth, l, format_value)
|
||||||
|
local info = get_function_info(value)
|
||||||
|
|
||||||
|
--assert(info.nups > 0)
|
||||||
|
local function_str = nil
|
||||||
|
if (info.defined_how == 'string') then
|
||||||
|
function_str = get_full_function_str(info.source, info.linedefined, info.lastlinedefined)
|
||||||
|
else
|
||||||
|
function_str = get_function_str_from_file(info.short_src, info.linedefined, info.lastlinedefined)
|
||||||
|
end
|
||||||
|
|
||||||
|
if info.nups > 0 then l[#l+1] = '(function () ' end
|
||||||
|
-- Upvalues
|
||||||
|
for k, v in pairs(info.ups) do
|
||||||
|
l[#l+1] = 'local '
|
||||||
|
l[#l+1] = tostring(k)
|
||||||
|
l[#l+1] = ' = '
|
||||||
|
format_value(v, options, depth + 1, l)
|
||||||
|
l[#l+1] = '; '
|
||||||
|
end
|
||||||
|
-- Return function
|
||||||
|
if info.nups > 0 then l[#l+1] = 'return ' end
|
||||||
|
l[#l+1] = function_str
|
||||||
|
--
|
||||||
|
if info.nups > 0 then l[#l+1] = ' end)()' end
|
||||||
|
end
|
||||||
|
|
||||||
return function (value, options, depth, l, format_value)
|
return function (value, options, depth, l, format_value)
|
||||||
local info = get_function_info(value)
|
local info = get_function_info(value)
|
||||||
|
|
||||||
|
if options.include_closure then
|
||||||
|
return format_function_with_closure(value, options, depth, l, format_value)
|
||||||
|
end
|
||||||
|
|
||||||
if info.defined_how == 'string' then
|
if info.defined_how == 'string' then
|
||||||
-- Function was defined as a string.
|
-- Function was defined as a string.
|
||||||
l[#l+1] = get_full_function_str(info.source, info.linedefined, info.lastlinedefined)
|
l[#l+1] = get_full_function_str(info.source, info.linedefined, info.lastlinedefined)
|
||||||
|
@ -105,23 +145,9 @@ return function (value, options, depth, l, format_value)
|
||||||
-- Cleanup and finish
|
-- Cleanup and finish
|
||||||
if not options.more_function_info or depth ~= 0 then
|
if not options.more_function_info or depth ~= 0 then
|
||||||
l[#l+1] = ' ... end'
|
l[#l+1] = ' ... end'
|
||||||
elseif options._all_function_info then
|
return;
|
||||||
-- NOTE: This is for testing/debugging/experimentation purposes.
|
end
|
||||||
|
|
||||||
local file = io.open(info.short_src, 'r')
|
|
||||||
local function_str = get_full_function_str(file:read('*all'), info.linedefined, info.lastlinedefined)
|
|
||||||
file:close()
|
|
||||||
|
|
||||||
l[#l+1] = '\n\t--[[ Function Body\n\t'
|
|
||||||
l[#l+1] = function_str
|
|
||||||
l[#l+1] = '\n\t--]]'
|
|
||||||
|
|
||||||
l[#l+1] = '\n\t--[[\n\tNative repr:'
|
|
||||||
l[#l+1] = tostring(value)
|
|
||||||
l[#l+1] = '\n\t'
|
|
||||||
format_value(info, options, depth + 1, l)
|
|
||||||
l[#l+1] = '--]]'
|
|
||||||
else
|
|
||||||
-- More info! --
|
-- More info! --
|
||||||
|
|
||||||
-- Name
|
-- Name
|
||||||
|
@ -147,19 +173,12 @@ return function (value, options, depth, l, format_value)
|
||||||
if not info.builtin then
|
if not info.builtin then
|
||||||
l[#l+1] = '\n'
|
l[#l+1] = '\n'
|
||||||
l[#l+1] = options.indent
|
l[#l+1] = options.indent
|
||||||
l[#l+1] = '-- source_file: \''
|
l[#l+1] = ('-- source_file: \'%s\' '):format(info.short_src)
|
||||||
l[#l+1] = info.short_src
|
|
||||||
l[#l+1] = '\' [Line'
|
|
||||||
if info.linedefined == info.lastlinedefined then
|
if info.linedefined == info.lastlinedefined then
|
||||||
l[#l+1] = ': '
|
l[#l+1] = ('[Line: %i]'):format(info.linedefined)
|
||||||
l[#l+1] = tostring(info.linedefined)
|
|
||||||
else
|
else
|
||||||
l[#l+1] = 's: '
|
l[#l+1] = ('[Lines: %i - %i]'):format(info.linedefined, info.lastlinedefined)
|
||||||
l[#l+1] = tostring(info.linedefined)
|
|
||||||
l[#l+1] = ' - '
|
|
||||||
l[#l+1] = tostring(info.lastlinedefined)
|
|
||||||
end
|
end
|
||||||
l[#l+1] = ']'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- upvalues
|
-- upvalues
|
||||||
|
@ -170,8 +189,23 @@ return function (value, options, depth, l, format_value)
|
||||||
format_value(info.ups, options, depth + 1, l)
|
format_value(info.ups, options, depth + 1, l)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options._all_function_info then
|
||||||
|
-- NOTE: This is for testing/debugging/experimentation purposes.
|
||||||
|
|
||||||
|
local function_str = get_function_str_from_file(info.short_src, info.linedefined, info.lastlinedefined)
|
||||||
|
|
||||||
|
l[#l+1] = '\n\t--[[ Function Body\n\t'
|
||||||
|
l[#l+1] = function_str
|
||||||
|
l[#l+1] = '\n\t--]]'
|
||||||
|
|
||||||
|
l[#l+1] = '\n\t--[[\n\tNative repr:'
|
||||||
|
l[#l+1] = tostring(value)
|
||||||
|
l[#l+1] = '\n\t'
|
||||||
|
format_value(info, options, depth + 1, l)
|
||||||
|
l[#l+1] = '--]]'
|
||||||
|
end
|
||||||
|
|
||||||
l[#l+1] = '\n\n'
|
l[#l+1] = '\n\n'
|
||||||
l[#l+1] = options.indent
|
l[#l+1] = options.indent
|
||||||
l[#l+1] = '...\nend'
|
l[#l+1] = '...\nend'
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ if not loadstring then loadstring = load end -- Lua 5.3 compa
|
||||||
local function format_test (t)
|
local function format_test (t)
|
||||||
if t.longterm then return end
|
if t.longterm then return end
|
||||||
if t.adv_getlocal and not HAS_ADV_GETLOCAL then return end
|
if t.adv_getlocal and not HAS_ADV_GETLOCAL then return end
|
||||||
SUITE:addTest(t.expect, function ()
|
SUITE:addTest(t.name or t.expect, function ()
|
||||||
local input_value = t.input
|
local input_value = t.input
|
||||||
local input_options = t.options
|
local input_options = t.options
|
||||||
local expected_result = t.expect
|
local expected_result = t.expect
|
||||||
|
@ -220,6 +220,126 @@ format_test {
|
||||||
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',
|
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 <name> () ...` 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
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
-- Indent functions nicely
|
-- Indent functions nicely
|
||||||
|
|
||||||
format_test {
|
format_test {
|
||||||
|
@ -244,6 +364,16 @@ format_test {
|
||||||
expect = '{\n\tabs = function (x) ... end,\n\tmax = function (a, b) ... end\n}',
|
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
|
return SUITE
|
||||||
|
|
Loading…
Reference in New Issue
Block a user