A beginner's question about Lua and metatables, with a example as simple as an Hello‑World, involving the len event, which unfortunately does not returns the expected result (I'm using Lua 5.1 installed from Ubuntu's official repository).
The case
Here is the example:
Test_Type = {};
function Test_Type.__len (o)
return 1;
end;
function new_test ()
local result = {};
setmetatable(result, Test_Type);
return result;
end;
do
local a_test = new_test();
print (#a_test);
print(getmetatable(a_test).__len(a_test));
end;
And the result I get:
0
1
I was expecting the first print statement to display 1, but it displays 0, to my big surprise.
What did I missed?
According to Lua Reference Manual — Metatables and Metamethods, the # is equivalent to this:
function len_event (op)
if type(op) == "string" then
return strlen(op) -- primitive string length
else
local h = metatable(op).__len
if h then
return (h(op)) -- call handler with the operand
elseif type(op) == "table" then
return #op -- primitive table length
else -- no handler available: error
error(···)
end
end
end
So print (#a_test); and print(getmetatable(a_test).__len(a_test)); should result into the same, isn't it?
By the way, why is the above excerpt from the Reference Manual refers to metatable(op) while it should be getmetatable(op)? At least I've tried print(metatable(a_test).__len(a_test));, and it ends into an error.
Answer
As Nneonneo noticed, this is an issue with the Lua version in use. Lua 5.2 seems to be required for the above to work.
From http://lua-users.org/wiki/LuaFaq:
Why doesn't the __gc and __len metamethods work on tables?
__len on tables is scheduled to be supported in 5.2. See LuaFiveTwo.
Since you're using 5.1, __len on tables does not work. Indeed, running your code on Lua 5.2 produces
1
1
as expected.
Related
I was doing some anti metamethod hooks and I was curious on what metamethod is called in the code below between the parentheses
local test = "random string"
if (test == "random string") then --// What metamethod if any is being called here?
print("equals")
end
I've done some research and took a look at the __eq metamethod, but that is only called when comparing two tables which isn't what I'm tryna do.
If there isn't any metamethod being called then how would I protect the if condition?
-- Update --
What if I put every string inside of a table for example:
local _Table1 = {"Test1", "Test2"}
local _Table2 = {"Test1", "Test2"}
for Index, Value in next, _Table1 do
if Value == _Table2[Index] then
print("Tables Match!")
elseif Value ~= _Table2[Index]
print("Tables Don't Match!")
end
end
I'm not doing any string converting here, but I'm showing what I could try and do for a simple anti tamper.
The only operator in the parenthesized expression is ==. Thus the only metamethod in question is __eq. The Lua Reference Manual states the following on __eq:
__eq: the equal (==) operation. Behavior similar to the addition operation, except that Lua will try a metamethod only when the values being compared are either both tables or both full userdata and they are not primitively equal.
Your values are strings and thus no metamethod will be called - even if you were to modify the string metatable to alter __eq.
Strings in Lua are always interned, so this comparison will always run in constant time. Since it is a primitive comparison, it can't possibly throw an error. No metamethod is called.
There is nothing to protect from: No possible performance issue / DoS vulnerability, no possible fancy side effects or code execution, no possible error.
(Highly theoretically: If a debug hook running every n instructions is registered, it might fire as the comparison is executed. You can hardly "protect" against a debug hook though.)
But it makes sense, for example a check against the Length.
A check against the Content seems also possible but need more lines.
And i like one liner in Lua Standalone to show...
> _VERSION
Lua 5.4
> eqtab = setmetatable({"Test1", "Test2"}, {__eq = function(left, right) print('SIMON SAYS:') return(#left == #right) end})
> eqtab == {1}
SIMON SAYS:
false
> eqtab == {}
SIMON SAYS:
false
> eqtab == {"Test1", "Test2"}
SIMON SAYS:
true
> eqtab == {1, 2}
SIMON SAYS:
true
I'm trying to run the following in Lua 5.3
function new_t()
local self = {}
setmetatable(self, {
__add = function(lhs,rhs)
print('ok so',lhs,'+',rhs)
end
})
return self
end
local t1 = new_t()
local t2 = new_t()
t1 + t2
I get an error saying syntax error near '+'. However if I change the last line to x = t1 + t2, it runs and prints without error.
Is it possible to use a binary operator without using the resulting value? Why doesn't Lua let me do t1 + t2 or even 1 + 2 by itself?
Lua doesn't allow this, because all the operators (except function calls) are intended to always calculate a result. There's no good reason to throw away the result of an expression, and it usually indicates a coding mistake.
If you just want to test your code, I suggest using assert:
assert(not (t1 + t2))
I use not here, because your __add function doesn't return anything.
EDIT: Normally, when we add two numbers, we expect to get a new number, without changing the original numbers. Lua's metamethods are designed to work the same way. To do side-effects like printing or modifying an operand, it's easier and clearer to use a regular named method.
I found out the _VERSION returns "Luau" instead of "Lua 5.1". I also found out continue and the += operator works
print(_VERSION) -- Luau
value = 0
value += 1
print(value) -- Doesn't return a syntax error
for k, v in ipairs({1, 2, 3, 4}) do
if k == 1 then
continue -- This works?
end
print(v)
end
prints
1
2
3
4
I also messed around with it and realized type annotation works.
function foo(x: number, y: string): boolean
local k: string = y:rep(x)
return k == "a"
end
doesn't throw a syntax error.
I also found out table.find, table.create and math.clamp is removed in Lua 5.4 as well as typeof function
I also realized binary literal print(0b10) returns 2 in Lua 5.1 but throws an error in Lua 5.4, along with print(1_000) which returns 1000 in Lua 5.1, but doesn't work in Lua 5.4
Why does these suddenly work on Lua 5.1? Did not expect it to work Lua 5.1
When I switched to Lua 5.4, _VERSION returns "Lua 5.4" instead and continue doesn't work and typeof was removed (How do I check types in Lua 5.4?).
What's going on?
And why does Lua 5.4 remove the += , continue operator and why does _VERSION return Luau in Lua 5.1?
Simple as: it's not Lua 5.1.
It's Luau, a language derived from Lua 5.1 and backwards-compatible with it. That's why your usual 5.1 code works, and why you can use some new features. Luau is maintained by Roblox. Its source code is published under MIT license.
As for Lua 5.4, see its Reference Manual to know what to expect from it.
I'm learning Lua from a book, which is a bit old.
I've tried searching the web, but because of the use of the # sign in my search I get really confusing results.
It says that in order to use upvalue you need to use the % sign.
But when I write it in my code I get an error.
Account.new = function (starting_balance)
local self = {}
local balance = starting_balance
self.withdraw = function (v)
%balance = %balance - v;
end
return self
end
error is : unexpected symbol near '%'
Is there a new way to handle upvalues in Lua 5.x ?
Since Lua 5.0, there is no more such thing as a "upvalue sign". An upvalue is a local to the environment a function is declared in, and as such can simply be accessed as any other local variable.
In your case: just use balance instead of %balance.
From Lua 5.1, % is used as modulo operator.
A good but slightly outdated book is the online available version of Programming in Lua, and of course, the reference manual.
Must be a very old book!
The % as upvalue notation was removed in Lua 5.0. (Released 2003)
Since 5.0, Lua has lexical scoping;
that is, upvalues are automatic:
do
local balance = 0
function deposit ( v )
balance = balance + v
return balance
end
end
print ( deposit ( 5 ) )
Output:
5
I'd go with the closure http://www.lua.org/pil/6.1.html#closures
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?
"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?
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.
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))