John Smith
John Smith

Reputation: 12797

Create Lua function from string

I am creating functions (of x) from a string in Lua. The code I am using is

function fcreate(fs)
 return assert(loadstring("return function (x) return " .. fs.." end"))()
end

This works for globals, e.g.

u=fcreate("math.sin(x)")

does the right thing.

However, it does not seem to like local variables. So

local c=1
u=fcreate("math.sin(x)+c")

will not work because c is local.

Is this fixable?

Upvotes: 4

Views: 6786

Answers (3)

hugomg
hugomg

Reputation: 69924

Something you could do if you don't need to mutate the local variables is to pass those values as arguments to the generated function. You would still need to manually specify the variables to close over but its better then nothing.

For example, you can build up your closure to look like

return (function(a,b,c)
   return function(x) return print(a, x) end
end)(...)

We might do that by changing your function to look like

function fcreate(variables, fs)

  local varnames = {}
  local varvalues = {}
  local nvars = 0
  for n,v in pairs(variables) do
    nvars = nvars + 1
    table.insert(varnames, n)
    table.insert(varvalues, v)
  end

  local chunk_str = (
     'return (function(' .. table.concat(varnames, ',') .. ') ' ..
         'return function(x) return ' .. fs .. ' end ' ..
      'end)(...)'
  )

  return assert( loadstring(chunk_str) )( unpack(varvalues, 1, nvars) )

end

local a = 1;
local f = fcreate({a=a}, 'x+a')
print(f(1), f(2))

Upvotes: 1

John Calsbeek
John Calsbeek

Reputation: 36497

Can't be done in any reasonable way. For an example of why, look at this:

function makefunction(name)
    local a = 1
    local b = 2
    local c = 3
    -- ...
    return assert(loadstring("return " .. name))
end

local a = 4
local func = makefunction("a")
print(func())

If this worked, what is printed? 1 or 4? Does it capture the variable from the place where the function was loaded, even though that function doesn't exist anymore? Or does it look it up from the place where it was called?

The first would mean that the function is lexically scoped wherever it's created. Being able to access the variable after the function has exited means that the variable would need to be promoted into an upvalue dynamically, which is not something that Lua can do at the moment. As it is now, Lua can see every access to a local variable during compilation, so it knows which variables to turn into upvalues (at a performance hit) and which to keep as locals.

The second would mean that variable accesses inside a loadstring'd function would work completely different than every other access in Lua: Lua uses lexical scoping, not dynamic scoping. It'd be a huge implementation change in Lua, and an extremely inconsistent one.

So, neither is supported. You can control the environment of a dynamically loaded function, using setfenv in Lua 5.1 or the env parameter of load(...) in Lua 5.2, but neither of those let you access local variables automatically.

Upvotes: 2

Mud
Mud

Reputation: 28991

"loadstring does not compile with lexical scoping", so no, it can't see locals outside the loadstring call.


Is this fixable?

That depends. Why are you using loadstring in the first place? Lua supports closures as first class values, so I can't see from your example why you'd need loadstring.

Your example:

u = fcreate("math.sin(x)+c")

Can be rewritten without the need for loadstring or your fcreate function:

u = function(x) return math.sin(x)+c end

Which of course is the same as:

function u(x) return math.sin(x) + c end

I can see a case for loadstring if you have user-configurable expressions that you wanted to compile into some other function, but your case with the local c suggests that's not the case. Are you trying to make some kinda of home-rolled lamda syntax?

Upvotes: 13

Related Questions