1
votes

I am trying to make a quote function in lua, so i can use the arguments as strings but without quotes or access them in some environment. Much like in the second comment on this question

w = print
function test()
   local function _ix( _ , k )
w( "              _ix \\ " , _ , k )
            local v = rawget( _G , k )
w( "              <-- " , k )
         return k
         end
  local _ = setmetatable( {} , { __index = _ix } )
  local function q( _ )    return _  end
        q = setfenv( q , _ )
return q
end

So, when I run it:

q = test()
w( "q( uno )" , q( uno ) )

It doesn't even call the __index metamethod:

---------- Capture Output ----------

q( uno )    nil

> Terminated with exit code 0.

So, what I'm doing wrong?

1
Start by using sensible variable names, formatting, and less self-induced indirection. - Oka
@oka I made an edit which made the code as readable as I could make out. Once it's applied globally it should be far easier to read. - warspyking
@warspyking Your edit has clear conflicts with the original code. You can rewrite the original like this, but it's still nonsensical in that it's trying to look things up in the wrong order. See my answer below. - Oka

1 Answers

0
votes

If I'm understanding correctly, then what you're trying to do doesn't make much sense. uno will be looked up in the environment that q is called in, not with. In your example it's like calling q(nil). The example from the other question works because they're working in the same, global environment.

You can use write a helper function to intercept your current environments nil-lookups, but it must be called preemptively in any environment you want to use these nil-to-string lookups.

local function intercept (tab)
    setfenv(2, setmetatable(tab or {}, {
        __index = function (_, key)
            return key
        end
    }))
end

And you'll need an environment cloning function, unless you want to manually create your sandboxes every time, else you'll probably mess up parent environments (e.g., _G). You could move this logic inside of intercept for a cleaner function call, but with less flexibility.

local function clone_current_env ()
    local env = {}

    for key, value in pairs(getfenv(2)) do
        env[key] = value
    end

    return env
end

Using them together, you can cause nil lookups in whichever environment you're in to become strings.

intercept(clone_current_env())
print(type(foo), type(bar)) --> string string

This is some ugly metaprogramming, and I don't really know why you'd want to write code like this, except as a proof of concept.


A full example.

DEMO

local function clone (tab)
    local new = {}

    for key, value in pairs(tab) do
        new[key] = value
    end

    return new
end

local function enable_nil_strings ()
    setfenv(2, setmetatable(clone(getfenv(2)), {
        __index = function (env, key)
            return key
        end 
    }))
end

local function disable_nil_strings()
    setmetatable(getfenv(2), nil)
end

-----------------------------------------------------

print(type(foo), type(bar)) --> nil nil
enable_nil_strings()
print(type(foo), type(bar)) --> string string
disable_nil_strings()
print(type(foo), type(bar)) --> nil nil

Finally, arguably the best way to implement this would be to simply wrap around an execution context:

local function with_nil_strings (context, ...)
    local env = {}

    for key, value in pairs(getfenv(2)) do
        env[key] = value
    end

    setfenv(
        context,
        setmetatable(env, {
            __index = function (_, key) return key end 
        })
    )

    context(...)
end

print(type(foo)) --> nil

with_nil_strings(function ()
    print(type(foo)) --> string
end)

print(type(foo)) --> nil