Is it possible to print a table without using metatables in Lua?
In Roberto's book Programming in Lua, he mentions "The function print always calls tostring to format its output". However, if I override tostring in my table, then I get the following results:
> a = {}
> a.tostring = function() return "Lua is cool" end
> print(a)
table: 0x24038c0
It can NOT be done without metatables.
The function print always calls tostring to format its output.
You misunderstood this. Here, tostring is the function tostring, not a field of a table. So what it means is that print(t) will call print(tosstring(t)), that's it.
For tables, tostring(t) will then find if it has a metamethod __tostring, and uses that as the result. So eventually, you still need a metatable.
local t = {}
local mt = {__tostring = function() return "Hello Lua" end}
setmetatable(t, mt)
print(t)
Related
This question has some reference to the question Evaluating expression in Lua in Mathematics Environment
The following code works.
tbl = {}
tbl.sin = math.sin
tbl.cos = math.cos
function mathEval(exp)
return load("return " .. exp, exp, "t", tbl)()
end
print(mathEval("sin(0)"))
print(mathEval("sin(0)+cos(1)+2^2"))
However, the following code does not work.
tbl = {}
tbl.sin = math.sin
tbl.cos = math.cos
function mathEval(exp)
return load("return " .. tostring(exp), tostring(exp), "t", tbl)()
end
print(mathEval(sin(0)))
print(mathEval(sin(0)+cos(1)+2^2))
I want to evaluate expressions without using quotes. How can that be done?
The problem with the line print(mathEval(sin(0)+cos(1)+2^2)) is that the argument of mathEval is evaluated before mathEval runs, so evaluating the variables sin and cos can not be deferred to the environment of mathEval; that is, mathEval gets a value, and no expression to evaluate at all!
First of all, one option to evaluate such mathematical expressions without the use of mathEval would be to simply temporarily change your environment:
local prev_env = _ENV -- this is needed to restore the environment later on
_ENV = tbl -- enter custom environment
local result = sin(0)+cos(1)+2^2
_ENV = prev_env -- restore environment
print(result)
if you want mathEval as a convenience helper, you'll have to pass the expression as a function returning the value to the expression such that calling the function will evaluate the expression; this allows you to defer the initialization. You'll have to use a powerful function called setfenv which allows you to change the environment of func; this was unfortunately removed in favor of _ENV in Lua 5.2 and later. The code then becomes trivial:
local function mathEval(func)
setfenv(func, tbl)
return func
end
mathEval(function() return sin(0)+cos(1)+2^2 end)
setfenv can be replicated in Lua 5.2 using the debug library, since Lua internally implements _ENV as an upvalue, as shown by Leafo:
local function setfenv(fn, env)
local i = 1
while true do
local name = debug.getupvalue(fn, i)
if name == "_ENV" then
debug.upvaluejoin(fn, i, (function()
return env
end), 1)
break
elseif not name then
break
end
i = i + 1
end
return fn
end
I assume you do not want to evaluate the expression before passing the result as an argument? Then you could wrap your expression into a function, which is then lazily called. It replaces the environment with tbl, executes the function, and reverts the environment.
tbl = {}
tbl.sin = math.sin
tbl.cos = math.cos
function mathEval(func)
local old = _ENV
_ENV = tbl
local r = func()
_ENV = old
return r
end
print(mathEval(function() return sin(0)+cos(1)+2^2 end))
You are passing sin(0) and sin(0)+cos(1)+2^2 to the mathEval(exp) function, but since sin and cos are not global variables and the math library is not being passed as an environment, Lua is unable to find them.
If you want to use math functions with the mathEval function, you can use the tbl table created before, to call the math functions, for example:
print(mathEval("tbl.sin(0)"))
print(mathEval("tbl.sin(0)+tbl.cos(1)+2^2"))
or alternatively you can pass the math library or the math functions as an upvalue to the load() function:
function mathEval(exp)
return load("return " .. tostring(exp), tostring(exp), "t", _ENV)()
end
print(mathEval("math.sin(0)"))
print(mathEval("math.sin(0)+math.cos(1)+2^2"))
It is possible to do this without quotes, this should work:
expression = string.format("%s(%d)", "math.sin", 0)
fn = load( "return " .. expression)
result = fn()
print(result) -- Output: 0
In this example, the string.format() function is used to create the string "math.sin(0)". The resulting string is then passed to the load() function as before.
In this way you don't need to use quotes on the string, and you can use placeholders to concatenate the variables.
I'd like to have a write-once table in Lua (specifically LuaJIT 2.0.3), so that:
local tbl = write_once_tbl()
tbl["a"] = 'foo'
tbl["b"] = 'bar'
tbl["a"] = 'baz' -- asserts false
Ideally, this would otherwise function like a regular table (pairs() and ipairs() work).
__newindex is basically the opposite of what I'd want for implementing this easily, and I am unaware of any techniques for making a proxy table pattern work with pairs() and ipairs().
You need to use a proxy table, that is, an empty table that catches all access to the actual table:
function write_once_tbl()
local T={}
return setmetatable({},{
__index=T,
__newindex=
function (t,k,v)
if T[k]==nil then
T[k]=v
else
error("table is write-once")
end
end,
__pairs= function (t) return pairs(T) end,
__ipairs= function (t) return ipairs(T) end,
})
end
Note that __pairs and __ipairs only work from Lua 5.2 onwards.
I want to know how to get the table hex id. I know that doing:
local some_var = {}
print (some_var)
the result is (for instance):
table: 0x21581c0
I want the hex without the table: string. I know that maybe some of you suggest me to make a regular expression (or something similar) to remove those chars, but I want to avoid that, and just get the 0x21581c0
Thanks
This is simpler and works for all types that are associated with pointers:
local function getId(t)
return string.format("%p", t)
end
print("string:", getId("hi"))
print("table:", getId({}))
print("userdata:", getId(io.stdin))
print("function:", getId(print))
print("number:", getId(1))
print("boolean:", getId(false))
print("nil:", getId(nil))
Result:
string: 0x0109f04638
table: 0x0109f0a270
userdata: 0x01098076c8
function: 0x0109806018
number: NULL
boolean: NULL
nil: NULL
In the standard implementation, there is the global 'print' variable that refers to a standard function that calls, through the global variable 'tostring', a standard function described here. The stanard 'tostring' function is the only way to retrieve the hexadecimal number it shows for a table.
Unfortunately, there is no configuration for either of the functions to do anything differently for all tables.
Nonetheless, there are several points for modification. You can create you own function and call that every time instead, or point either of the the global variables print or tostring to you own functions. Or, set a __tostring metamethod on each table you need tostring to return a different answer for. The advantage to this is it gets you the format you want with only one setup step. The disadvantage is that you have to set up each table.
local function simplifyTableToString(t)
local answer = tostring(t):gsub("table: ", "", 1)
local mt = getmetatable(t)
if not mt then
mt = {}
setmetatable(t, mt)
end
mt.__tostring = function() return answer end
end
local a = {}
local b = {}
print(a, b)
simplifyTableToString(a)
print(a, b)
Without complex patterns, you can just search for the first space, and grab the substring of what follows.
function get_mem_addr (object)
local str = tostring(object)
return str:sub(str:find(' ') + 1)
end
print(get_mem_addr({})) -- 0x109638
print(get_mem_addr(function () end)) -- 0x108cf8
This function will work with tables and functions, but expect errors if you pass it anything else.
Or you can use a little type checking:
function get_mem_addr (o)
return tostring(o):sub(type(o):len() + 3)
end
The table id stated by the OP is invalid in the version of Lua I am using (5.1 in Roblox). A valid ID is length 8, not 9 as in your example. Either way, just use string.sub to get the sub-string you are after.
string.sub(tostring({}), 8)
The reason is, 'table: ' is 7 characters long, so we take from index 8 through the end of the string which returns the hex value.
According to the documentation _G "holds the global environment". I wanted to see what's inside it so I wrote the following code to print _G but it doesn't work:
function f(x)
return 2*x
end
a=3
b="hello world"
print("_G has "..#_G.." elements")
for k,v in pairs(_G) do
print(k)
print(_G[k])
print("G["..k.."]=".._G[k])
end
Error:
_G has 0 elements
a
3
G[a]=3
string
table: 003C8448
lua: try_G.lua:10: attempt to concatenate field '?' (a table value)
stack traceback:
try_G.lua:10: in main chunk
[C]: ?
>Exit code: 1
You could also use the table.foreach(t,f) function. It iterates over a table t, calling the function f with each key and value pair. Use with print to get a quick view:
table.foreach(_G,print)
This is really handy at the interactive prompt as it is reasonably succinct and easy enough to type.
C:\Users\Ross>lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> table.foreach(_G,print)
string table: 005CE3D0
xpcall function: 00717E80
package table: 005CE088
tostring function: 00717DE0
print function: 00711CB8
os table: 005CE358
unpack function: 00717E40
require function: 00718360
getfenv function: 00711B58
setmetatable function: 00717DA0
next function: 00711C38
assert function: 00711A38
tonumber function: 00717DC0
io table: 005CE218
rawequal function: 00711CF8
collectgarbage function: 00711A78
getmetatable function: 00711B98
module function: 00718320
rawset function: 00711D58
math table: 005CE448
debug table: 005CE498
pcall function: 00711C78
table table: 005CE128
newproxy function: 00711E10
type function: 00717E00
coroutine table: 005CDFE8
_G table: 00713EC8
select function: 00711D98
gcinfo function: 00711B18
pairs function: 00711F98
rawget function: 00711D18
loadstring function: 00711C18
ipairs function: 00711F68
_VERSION Lua 5.1
dofile function: 00711A98
setfenv function: 00717D60
load function: 00711BD8
error function: 00711AD8
loadfile function: 00711BB8
>
Update: Unfortunately, as Alexander Gladysh reminds me, the table.foreach function was deprecated in Lua 5.1, and a quick check of the current beta release of 5.2 shows that it has been removed in Lua 5.2. It is easy to write the same loop in terms of pairs:
for k,v in pairs(_G) do print(k,v) end
which should give the same output as table.foreach(_G,print) would. The key feature that I'm leaning on here is that print is defined to call tostring() on each argument you pass, and tostring() is defined to return some sort of sensible string for every kind of value, even those like functions that don't have a good representation as a string. The details will differ on each platform, but the default implementation of tostring() includes the address of the table or function in its string result, allowing you to at least recognize that _G.os and _G.io are distinct tables.
For more human-friendly table printing, there are a lot of solutions, ranging from examples in PiL to several persistent data libraries. Personally, I like the pl.pretty.write() function provided by steve donavan's penlight library.
Your code works exactly as expected- it loops through _G and attempts to print the contents. Unfortunately, _G contains many tables which cannot be concatenated into a string. The code fails because _G["_G"] = _G. That means that when the interpreter comes to
print("G["..k.."]=".._G[k])
then k is "_G" and _G[k] is _G, and you attempt to concatenate a table- which the interpreter can't do, so it dies on you. There are numerous other tables in _G which would also cause this failure.
To follow up on DeadMG, change your
print("G["..k.."]=".._G[k])
to
print("G["..k.."]=",_G[k])
and you should be fine.
Here is the final code using DeadMG's solution:
function f(x)
return 2*x
end
a=3
b="hello world"
print("_G has "..#_G.." elements")
for k,v in pairs(_G) do
if k~="_G" then
if type(v)=="string" or type(v)=="number" then
print("G["..k.."]="..v)
else
print("G["..k.."]=("..type(v)..")")
end
end
end
I'm trying to make an __index function in my table which can process ALL of the field it receives.. What I want to do is that if I call the table in the following way
mytable.str1.str2.str3
I should be able to return the table
{"str1", "str2", "str3"}
Note that str1,str2,str3 are undefined, they are just strings. I am not trying to create subtables str1, str2, I just want __index to see everything beyond the first period.
Unfortunately what I have seems that __index only captures str1, and complains that "attempt to index field 'str1' (a nil value)"
Anyone know how this can be done?
I'm not sure why you'd want to do this, but here's how you do it. The comments explain the trick, but basically you need a second metatable to handle the table that's returned from the first call to the __index metamethod.
If this isn't clear, let me know and I can explain in more detail.
-- This metatable appends the new key to itself, then returns itself
stringtablemeta = {}
function stringtablemeta.__index(self, key)
table.insert(self, key)
return self
end
-- In response to the question in the comments:
function stringtablemeta.__tostring(self)
local str = ""
for i, v in ipairs(self) do
if i > 1 then str = str .. "-" end
str = str .. v
end
return str
end
-- This metatable creates a new table, with stringmetatable as its metatable
mytablemeta = {}
function mytablemeta.__index(self, key)
local temp = { key }
setmetatable(temp, stringtablemeta)
return temp
end
-- set mytable to have mymetatable as it's metatable. This makes it so when
-- you index into it, it will call the mytablemeta.__index method.
--
-- That will return a talb with a single string, the key that was passed
-- in. that table will have it's own metatable, the stringmetatable, which
-- will cause it to append keys that are called on it with its own __index
-- metamethod
mytable = {}
setmetatable(mytable, mytablemeta)
test = mytable.str1.str2.str3
for k, v in pairs(test) do
print(k, v)
end
It can't. Not without having a metatable on each of those tables.
mytable is a table. str1 is a different table. So you can do the same thing by doing this:
local temp = mytable.str1
temp.str2.str3
And as far as Lua is concerned, these are equivalent. Therefore, the only way to know what was done at each stage is to give all of them a special metatable. How you concatenate the different values into a table is something you'll have to investigate on your own.
As Nicol said, you cannot do that directly in Lua. However, by returning specially crafted tables, you can achieve a similar result to what you want. Take a look at AutomagicTables at the Lua-users Wiki for inspiration.