Suporte ao desenvolvimento de jogos!


    [Lua] Enum

    Compartilhe
    avatar
    Paulo Soreto
    Lenda
    Lenda

    Mensagens : 1953
    Créditos : 356

    Ficha do personagem
    Nível: 1
    Vida:
    30/30  (30/30)
    Energia:
    0/0  (0/0)

    [Lua] Enum

    Mensagem por Paulo Soreto em Sex Out 27, 2017 6:08 pm

    Enum é uma lista de certa forma uma lista de identificadores que possuem valores numéricos (variam de acordo com a definição do usuário e de linguagem).
    Na maioria dos casos o uso é basicamente o seguinte:
    Código:
    enum Abc {
      A = 1,
      B,
      C
    }
    Abc.A // 1
    Abc.B // 2
    Abc.C // 3

    Não da pra obter o mesmo resultado em Lua mas a gente pode chegar perto. A representação em Lua para quem ainda não mentalizou seria assim:
    Código:
    local Abc = {
      A = 1,
      B = 2,
      C = 3
    }
    print(Abc.A) -- 1
    print(Abc.B) -- 2
    print(Abc.C) -- 3

    Nota-se que temos que definir o valor de cada item manualmente e o resultado final é uma table que pode ser modificada (adicionar ou remover itens por exemplo podem fazer com que o funcionamento do enum se torne inválido).

    Nós podemos corrigir esse problema de definir novos valores de uma forma muito simples: usando metatables.
    Código:
    local Abc = setmetatable({
      A = 1,
      B = 2,
      C = 3
    }, {
      __newindex = function()
        error('nenhum elemento pode ser adicionado a um enum')
      end
    })

    Abc.X = 4
    Se executrarmos o código acima teremos a seguinte mensagem de erro:
    Código:
    nenhum elemento pode ser adicionado a um enum
    Isso acontece por que definimos uma nova metatable para a table que foi atribuida a variável Abc (falarei sobre metatables em outro post).

    Quando um valor é adicionado a uma table (seja pelo table.insert ou uma atribuição direta: a = b) a função __newindex definida dentro da metatable do objeto é chamada. Para impedir que novos valores sejam adicionados nós sobreescrevemos esta função e liberamos um erro caso ela seja chamada.

    Ou seja, se por engano você ou alguém tentar definir um novo campo no enum dessa forma o sistema irá encontrar um erro. Isso não é tudo que podemos fazer, meu objetivo é no final deste post vocẽs consigam declarar enumerações em lua de forma semelhante a linguagens como C (eu disse semelhante >.>)

    O primeiro passo pra atingirmos esse objetivo é pararmos de usar diretamente a table para definir os valores do enum. Para isso vamos criar uma função chamada enum e ela retornará uma table com todos os elementos definidos a partir dos parametros recebidos. Seria mais ou menos assim:
    Código:
    local abc = enum('A', 'B', 'C')
    print(abc.A) -- 1
    print(abc.B) -- 2
    print(abc.C) -- 3
    E aqui temos nossa função:
    Código:
    function enum(...)
      local args, ntable, count = {...}, {}, 1
      for i = 1, #args do
        ntable[args[i]] = count
        count = count + 1
      end
      return setmetatable(ntable, { __newindex = function()
        error('nenhum elemento pode ser adicionado a um enum')
      end})
    end
    Na primeira linha dentro dela nós definimos três variáveis que são respectivamente: os argumentos passados pra função, a table que será retornada pela função e uma variável que é usada como contador. Após isso, em um loop nós pegamos cada argumento passado e adicionamos a nova table, o valor é o valor atual do count.

    Acredito que alguns de vocês já percebeu que tem um problema na nossa implementação, ela não permite atribuir valores aos elementos. Para resolvermos isso, ao inves de recebermos os elementos que farão parte do enum por direto do argumentos, vamos passar eles para uma table.

    O código acima com essas modificações é:
    Código:
    function enum(otable)
      local ntable, count = {}, 1
      for i = 1, #otable do
        ntable[otable[i]] = count
        count = count + 1
      end
      return setmetatable(ntable, { __newindex = function()
        error('nenhum elemento pode ser adicionado a um enum')
      end})
    end

    local abc = enum({'A', 'B', 'C'})
    print(abc.A) -- 1
    print(abc.B) -- 2
    print(abc.C) -- 3

    Agora que estamos utilizando uma table para passar os elementos podemos mudar um pouco a forma como usamos. Quando o argumento de uma função é passado como um objeto literal de string ou table (desde que apenas um argumento) o uso de parêntesis se torna desnecessário, logo temos o seguinte código:
    Código:
    local abc = enum { 'A', 'B', 'C' }
    print(abc.A) -- 1
    print(abc.B) -- 2
    print(abc.C) -- 3
    Ou melhor ainda:
    Código:
    local abc = enum {
      'A',
      'B',
      'C'
    }
    print(abc.A) -- 1
    print(abc.B) -- 2
    print(abc.C) -- 3
    Agora nosso sistema está ganhando forma mas o problema de atribuir valores para os elementos durante a definição do enum ainda persiste. Para resolvermos isso percisaremos verificar o tipo de cada chave dentro da table passada, se for uma chave numérica (ou índice se preferir) significa que o campo que passamos  é somente o nome do elemento, sem valor algum. Nesse caso o código anterior persiste mas quando a chave for do tipo string precisaremos adicionar a chave como nome do elemento.

    Para fazer uma parte do trabalho usaremos a função pairs que retorna dois valores (chave e valor) para cada item de uma table. O código descrito seria o seguinte:
    Código:
    function enum(otable)
      local ntable, count = {}, 1
      for key, value in pairs(otable) do
        local tk = type(key)
        if tk == 'number' then
          ntable[value] = count
          count = count + 1
        elseif tk == 'string' then
          ntable[key] = value
        else
          error('nenhum objeto do tipo "'..tk..'" pode ser usado como elemento de um enum')
        end
      end
      return setmetatable(ntable, { __newindex = function()
        error('nenhum elemento pode ser adicionado a um enum')
      end})
    end

    local abc = enum {
      'A',
      B = 3,
      'C'
    }
    print(abc.A) -- 1
    print(abc.B) -- 3
    print(abc.C) -- 2
    Agora temos uma implementação funcional enum que você pode usar no seu projeto em lua. Lembrando que todo o custo de processamento é na inicialização do objeto, após isso ele age como uma table normal.

    Espero que tenham gostado do tutorial, se tiver feedback posso fazer outros posts sobre Lua.


    _________________
    avatar
    Valentine
    Administrador
    Administrador

    Medalhas :
    Mensagens : 4751
    Créditos : 1009

    Re: [Lua] Enum

    Mensagem por Valentine em Sex Out 27, 2017 7:45 pm

    Nossa! Muito bom, Soreto.

    Amo enum I love you

    + 1 crédito


    _________________

      Data/hora atual: Ter Fev 20, 2018 2:33 pm