Search code examples
lua

Converting a line of code to a char string


I recently wrote some code like this:

function enum(tbl)
    local length = #tbl
    for i = 1, length do
        local v = tbl[i]
        tbl[v] = i
    end

    return tbl
end
eItemType = enum
{
    "wpn",
    "outf",
    "helm",
    "art",
    "boost",
    "bkpk",
    "dev",
    "ammo",
    "none"
}

It works. But I would like to simplify it to this form:

enum eItemType
{
    "wpn",
    "outf",
    "helm",
    "art",
    "boost",
    "bkpk",
    "dev",
    "ammo",
    "none"
}

For the enum function to create a global variable eItemType in the file from which it is called. I don't know how to implement this (convert eItemType to string in string code). Functions from the debug library come to mind, namely getline, maybe it can handle it...


Solution

  • Enumerations in Lua

    First of all: You're shoehorning a foreign language concept into Lua, which will necessarily not be round on the corners. Languages like C use numbers (integers) for enums because of their efficiency: Integer comparison is fast. In the end, enums are just synctactic sugar for enumerating integer constants though. You don't need any of this in Lua: Lua has string interning, which means strings are only stored once in memory and can be compared as fast as numbers. That is, the naive way to implement enums in Lua, is to simply use strings right away!

    local enum_values = {"foo", "bar", "baz"}
    local function get_random_enum_value()
        return enum_values[math.random(#enum_values)]
    end
    if get_random_enum_value() == "foo" then
        print("Congratulations! The value is foo!")
    end
    

    The downside is that you now don't have any table holding your enum values, so you'll either want to brush up on your documentation or create a (redundant) hash table to hold your enum values (which really is just making things slower, but may help readability). You don't need to involve any numbers. If I inspect a table and see a "foo_something" string there, that's a lot more useful than 42.

    local my_enum = {foo = "foo", bar = "bar", baz = "baz"}
    local enum_values = {my_enum.foo, my_enum.bar, my_enum.baz}
    local function get_random_enum_value()
        return enum_values[math.random(#enum_values)]
    end
    
    -- May be considered more readable since we now have a scope for "foo"
    if get_random_enum_value() == my_enum.foo then
        print("Congratulations! The value is foo!")
    end
    

    then you'd probably write yourself a simple helper to generate these kinds of tables:

    function enum(namelist)
        local t = {}
        for _, v in pairs(namelist) do
            t[v] = v
        end
        return t
    end
    
    my_enum = enum{
        "foo",
        "bar",
        "baz",
    }
    

    Syntactic Sugar

    What you want is called syntactic sugar and yes, it requires Lua's metaprogramming capabilities. If you really want an enum keyword, you'll have to extend Lua or implement a preprocessor adding such a keyword.

    First, let's see how Lua sees your current code:

    someName = enum { ... }
    

    parses as "call the variable called enum with the table { ... } and assign the result to the variable called someName".

    enum someName { ... }
    

    does not parse: this is just a name, followed by... another name? Syntax error. By slightly abridging this syntax, it is however still possible to turn this into a valid expression. How about passing someName as a string to enum, which then returns a function to apply to your table?

    enum "someName" { ... }
    

    this would be implemented as

    function enum(name)
        return function(t)
            _G[name] = original_enum(t)
        end
    end
    

    where original_enum is your original enum function without the added layer for syntactic sugar. You might want to swap out _G with _ENV or getfenv(2) or similar.

    Or for another syntactic sugar, you could also use the indexing operator paired with the __index metamethod:

    enum.someName { ... }
    

    which is implemented as:

    enum = setmetatable({} --[[proxy table]], {__index = function(_, name)
        return function(t)
            _G[name] = original_enum(t)
        end
    })
    

    both of these have in common that they are basically just fancy ways of currying the name of the global variable you want to assign to.