lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Mon, Aug 23, 2010 at 07:24, Wesley Smith <wesley.hoke@gmail.com> wrote:
>>  if config.foo.bar.baz then
>>    -- ...but this code will *always* be run, because if it *wasn't*
>>    -- already set, it will have been created dynamically by the metatable.
>>  end
>
> I see.  Well, this isn't possible without hacking Lua.  You could
> instead do this
>
>
> local BAD_VAL = {}
>
> local meta
> meta = {
>        __index = function(t, k)
>                print("__index", t, k)
>                if(not rawget(t, k)) then
>                        return BAD_VAL
>                end
>                return t[k]
>        end,
>
>        __newindex = function(t, k, v)
>                print("__newindex", t, k, v)
>                rawset(t, k, v)
>        end
> }
>
> setmetatable(BAD_VAL, meta)
>
>
> -- config.network.server.url
> local config = setmetatable({}, meta)
> if(config.network.server.url == BAD_VAL) then
>        print("config.network.server.url BAD_VAL")
> end
>

The idea of an operator for this purpose (as suggested by the thread
title) is interesting. I can imagine something like foo =
@config.network.server.url, where prefixing the table name with an @
(or some other symbol) has essentially the same effect as setting a
__index for nil as mentioned earlier in the thread, for only that
access.

You can emulate this with a function like:
function safenav(t, path)
	local nav = {}
	(path .. '.'):gsub('([^.]+).', function(p) table.insert(nav,p) end)
	for i = 1,#nav do
		t = t[nav[i]]
		if type(t) ~= 'table' then return t end
	end
	return t
end

local config = {network = {server = '127.0.0.1'}}
print(safenav(config, 'network.server'))
print(safenav(config, 'sandwich.bread.type'))

>From there you could improve it to default to _ENV/_G if no table is
given (so you could write safenav('config.network.server')), support
[] indexing, etc, and a similar function can be used for writing.


If you want to emulate it without using such a function, I've been
using this metamethod system to do exactly this for my language files.
The table itself, and every sub-table of it, are given a metatable.
The __index method, if it does not find the requested key, returns a
new 'dummy' table with this same metatable, which is empty except for
a string containing the path it would have been found at.

The metatable also has a __tostring which returns the path stored in
the dummy table. So if you use Text.Foo.Bar.Baz as a string and
Text.Foo was nil, the string you get is "<Text.Foo.Bar.Baz>" - thus
the program doesn't crash and you can see exactly which strings are
missing.

The problem with this system is __tostring is only used by tostring()
- not the C API nor other string functions. There are patches to fix
this; otherwise, you have to put the resulting string through
tostring() before using it to ensure you actually get a string.

You could use a similar method for config and other tables, with a
different function (instead of tostring) that returns nil if given a
dummy table. However this isn't a nice simple solution and it would be
nice to have some sort of function or operator that we can wrap table
accesses in to make this work without all the support framework. Maybe
tables could have a mode which returns an empty table (which also has
this mode set) instead of nil for nonexistent keys, or some other
object equivalent to nil but indexable (in which all keys return the
same object)?

-- 
Sent from my toaster.