1 A linguagem de programação LUA - Parte 2 Qua 03 Out 2012, 19:05
Halt
Administrador
Resumo
Lua é uma linguagem de script amplamente usada nas mais diversas áreas, desde grandes
aplicativos para desktops, como o Adobe Photoshop Lightroom, até software para sistemas embarcados.
Lua é a linguagem mais usada atualmente para scripting em jogos, e é parte do padrão Ginga
para o Sistema Brasileiro de TV Digital. Lua também é muito usada na área de segurança, sendo a
linguagem de script embutida em ferramentas como Wireshark, snort e nmap.
Este texto apresenta a linguagem Lua com ênfase nos seus mecanismos menos convencionais.
O objetivo é introduzir a linguagem e ao mesmo tempo apresentar algumas técnicas de programação
não convencionais, como o uso de funções de mais alta ordem, co-rotinas e APIs entre linguagens.
Espera-se do leitor alguma maturidade na área de programação e conhecimento da linguagem C,
para a discussão da API entre Lua e C.
Introdução
O objetivo deste texto é introduzir o leitor à programação na linguagemLua.
Assumimos que o leitor (você) possui uma certa maturidade em programação
com alguma linguagem qualquer.
Programar em Lua não é muito diferente de programar em outras linguagens
dinâmicas, mas é diferente. Cada linguagem apresenta características
próprias, e um bom programador sabe explorar as características particulares
de cada linguagem. Neste texto, vamos procurar enfatizar as particularidades
de Lua, aspectos que tornam a programação em Lua diferente da programação
em outras linguagens dinâmicas. Em particular, em Lua, temos como importantes
diferenciais o uso de técnicas de programação funcional, o uso ubíquo
de tabelas como estruturas de dados para os mais variados fins, o uso de corotinas
e a comunicação com código escrito em C.
Bom, então programar em Lua não é tão diferente de programar em outras
linguagens dinâmicas. Mas afinal, o que é uma linguagem dinâmica? Como
ocorre frequentemente em computação, esse termo não possui uma definição
precisa e universalmente aceita. Mas existe um certo consenso de que linguagens
dinâmicas apresentam as seguintes características:
Interpretação dinâmica: isso significa que a linguagem é capaz de executar
trechos de código criados dinamicamente, no mesmo ambiente de execução
do programa. Como exemplos dessa facilidade temos a função
loadstring em Lua e a função eval em Scheme/Lisp e Perl.
Tipagem dinâmica forte: tipagem dinâmica significa que a linguagem faz verificação
de tipos em tempo de execução do programa. Linguagens com
tipagem dinâmica em geral não possuem declarações de tipos no código
e não fazem verificação de tipos em tempo de compilação. Tipagem
forte significa que a linguagem jamais aplica uma operação a um tipo
incorreto.
Gerência automática de memória dinâmica (coleta de lixo): isso significa que
não precisamos gerenciar memória explicitamente no nosso programa;
em especial, não há necessidade de um comando para liberar memória
após seu uso.
Em geral, linguagens dinâmicas são interpretadas, e não compiladas para
código nativo da máquina; mas essa é uma característica das implementações
dessas linguagens, não das linguagens em si. Obviamente, as características
acima favorecem uma implementação via um interpretador e dificultam a
construção de compiladores.
Dessas características, a interpretação dinâmica é a mais exclusiva de linguagens
dinâmicas. Obviamente, em qualquer linguagem Turing-completa podemos
escrever um interpretador para a própria linguagem, mas os trechos de
código interpretados não serão executados no mesmo ambiente do programa
interpretador. Por exemplo, podemos escrever um interpretador para C em C,
mas os programas interpretados não terão acesso às variáveis e funções declaradas
no programa compilado onde o interpretador está sendo usado.
Apesar de não ser uma característica exclusiva de linguagens dinâmicas, a
gerência automática de memória é um mecanismo importante dessa lista, por
haver uma enorme diferença entre programarmos em uma linguagem com e
em uma linguagem sem gerência automática de memória. Mesmo na programação
em ponto grande (programming in the large) a gerência automática de
memória tem um impacto significativo, ao simplificar as interfaces entre componentes.
(Como um exercício, pegue a API de qualquer biblioteca C de porte
razoável e verifique quanto de sua complexidade é devida à gerência de memória.)
Na verdade, existe um contínuo entre linguagens estáticas e dinâmicas. Por
exemplo, Java é uma linguagem muito mais dinâmica do que C, pois apresenta
gerência automática de memória, um certo grau de tipagemdinâmica e um mecanismo
embrionário de interpretação dinâmica (por meio da carga dinâmica
de classes, que por sua vez podem ser criadas dinamicamente). Mesmo entre
as linguagens reconhecidamente dinâmicas existem diferenças. Por exemplo,
nem todas as linguagens dinâmicas têm gerência automática de memória sobre
módulos ou classes.
Lua se destaca de outras linguagens dinâmicas por ser uma linguagem de
script. Uma linguagem de script é uma linguagem projetada para controlar e
coordenar componentes geralmente escritos em outra linguagem. As primeiras
linguagens de script foram as linguagens de shell do Unix, usadas para
conectar e controlar a execução de programas. Apesar de várias linguagens
dinâmicas poderem ser usadas para script, poucas foram projetadas para essa
finalidade. Lua seguiu um caminho criado por Tcl [Ousterhout 1990], onde a
linguagem é estruturada como uma biblioteca C com uma API que permite
tanto código na linguagem chamar funções escritas em C como código C chamar
funções escritas na linguagem. Lua se destaca de outras linguagens de
script por sua simplicidade, portabilidade, economia de recursos e desempenho
[Ierusalimschy et al. 2007].
Como usar Lua
A linguagem Lua conta com uma única implementação principal, mantida
pelos autores da linguagem no site [Tens de ter uma conta e sessão iniciada para poderes visualizar este link] mas essa implementação
conta com diversas distribuições mantidas por desenvolvedores independentes.
Em muitos usos reais de Lua, o interpretador é distribuido embutido na aplicação
final. Afinal, um dos principais objetivos de Lua é exatamente esse tipo
de uso. Nesses casos, detalhes de como usar Lua são dependentes da aplicação.
Esses detalhes incluem que editor usar, onde e como armazenar os
programas, como executar um programa, etc. Neste texto, como não estamos
visando nenhuma aplicação particular, vamos usar o interpretador independente
(stand alone) de Lua.
Para máquinas Windows, uma ótima opção de instalação é a distribuição
Lua for Windows (LfW).1 Essa distribuição é um pacote completo para Windows,
incluindo não apenas o interpretador Lua com suas bibliotecas padrão,
mas também um editor e várias bibliotecas extras populares.
Para máquinas Linux não há uma receita pronta, dada a diversidade de
distribuições de Linux. Compilar Lua em uma máquina Linux é muito simples
e rápido. Algumas distribuições já vêm com Lua instalado por default. Outras
oferecem pacotes prontos: por exemplo, em Ubuntu e Debian, basta instalar
o pacote lua5.1, que é o interpretador com as bibliotecas padrão. Várias
bibliotecas externas também são oferecidas como pacotes extras. Em qualquer
caso, o interpretador independente de Lua é um programa de linha de
comando, para ser executado por meio de um terminal.
Para máquinas Mac OS X, existe a opção de compilar diretamente o fonte,
desde que a máquina já tenha as ferramentas de desenvolvimento em C instaladas.
O processo é simples e rápido como no Linux. Outra opção é usar um
gerenciador de pacotes; por exemplo, tanto o MacPorts quanto o Fink oferecem
pacotes prontos para Lua.
Uma vez instalado, é muito fácil usarmos o interpretador. Lua não tem o
conceito de uma função main; qualquer comando passado ao interpretador é
imediatamente executado. O exemplo a seguir é um programa completo para
imprimir p2:
print(2^(1/2)) --> 1.4142135623731
O operador ^ é o operador de exponenciação em Lua. Lua trabalha sempre
com números reais em ponto flutuante e a exponenciação funciona para expoentes
fracionários (e negativos também). Em geral, usamos a notação -->
para indicar o resultado de um comando. Como em Lua dois traços -- iniciam
um comentário que vai até o final da linha, podemos incluir aquela indicação
no programa.
Ao usar Lua via um terminal, você tem pelo menos quatro maneiras de
executar esse pequeno “programa”:
• Você pode usar a opção de linha de comando -e:
$ lua -e "print(2^0.5)"
(Estou assumindo que $ é o prompt do terminal.)
• Você pode entrar com o programa em modo interativo:
$ lua
> print(2^0.5)
(O > é o prompt do interpretador Lua em modo interativo.)
• Você pode escrever esse programa em um arquivo e executá-lo via linha
de comando:
$ lua nome-do-arquivo
• Você pode escrever esse programa em um arquivo e executá-lo via modo
interativo, por meio da função predefinida dofile:
$ lua
> dofile("nome-do-arquivo")
Podemos dispensar os parênteses em chamadas de função onde o único argumento
é uma string literal. Assim, você pode reescrever o exemplo anterior
como a seguir:
> dofile"nome-do-arquivo"
Quando você chama o interpretador, ele cria um estado Lua que persiste
até o fim da sua execução. Assim, todos os efeitos colaterais de cada comando
se propagam para os próximos comandos, mesmo que eles sejam executados
como “programas” em separado. Veja o exemplo a seguir:
$ lua
> x = 1
> print(x) --> 1
Cada uma das linhas é executada como um trecho (chunk, em inglês) separado,
mas o valor da variável global x se mantém após o primeiro trecho ter
terminado.
Alguns Exemplos
À primeira vista, Lua é uma linguagem imperativa, razoavelmente convencional.
Como já discutimos, não vamos perder muito tempo descrevendo
essa parte mais convencional da linguagem. Uma boa parte você vai aprender
apenas vendo os exemplos; se precisar de maiores detalhes, consulte
o manual de referência [Ierusalimschy et al. 2006] ou o livro Programming in
Lua [Ierusalimschy 2006].
Seguem alguns exemplos de funções simples em Lua, para você se familiarizar
com o básico da linguagem.
Soma dos elementos de um array
• Como a linguagem tem tipagem dinâmica, não há tipos nas declarações
de variáveis, parâmetros, etc.
• A palavra reservada local declara uma variável local, cujo escopo vai
da declaração até o fim do bloco mais interno que contém a declaração.
No exemplo, sum é visível até o fim da função add.
• A expressão #a retorna o comprimento do array a. Como arrays em Lua
começam no índice 1, o comprimento é também o valor do último índice.
• O comando for vai repetir seu corpo com o valor da variável i variando
de 1 até o comprimento do array (#a). A variável de controle i é
declarada pelo próprio comando for e só é visível dentro do seu corpo.
• Todas as estruturas de controle têm um terminador explícito. Tanto o
corpo da função quanto o corpo do for terminam com a palavra reservada
end.
Soma das linhas de um arquivo
A função a seguir recebe o nome de um arquivo texto, que deve conter uma
lista de números, e retorna a soma desses números:
Essa função é semelhante à do exemplo anterior, com exceção do for. No
exemplo anterior, usamos um for numérico, que iterage sobre uma progressão
aritmética de números. Neste exemplo, usamos um for genérico, que usa um
gerador (io.lines, no caso, fornecida pela biblioteca padrão de Lua) para
gerar os valores da iteração.
Note também o uso da função tonumber, para converter o numeral lido
(uma string) para um número. Lua faz esse tipo de conversão automaticamente
sempre que uma string é usada em uma operação aritmética, mas consideramos
mais educado efetuar a conversão explicitamente.
Casamento de prefixos
Um problema comum em várias áreas é, dada uma lista de palavras, decidir
se uma dada string é prefixo de alguma das palavras da lista. Por exemplo,muitos
sistemas de linha de comando permitem que entremos com um comando
digitando apenas os primeiros caracteres do nome do comando.
Uma solução usual para esse problema é construir uma tabela de prefixos,
que mapeia todos os prefixos de cada palavra na lista para a palavra completa.
Dada essa tabela, o problema original é resolvido com uma simples consulta.
Função para construir uma tabela de prefixos.
A função na Figura 3.1 recebe uma lista (array) de palavras e retorna sua
tabela de prefixos. Nesse código temos novamente várias novidades:
• A expressão {} cria uma tabela vazia, que é atribuida à variável local t.
• O laço externo usa o gerador ipairs, que percorre todos os índices
e valores do array dado (list). Os índices são atribuídos à primeira
variável (que nomeamos _, já que não estamos interessados no seu
valor), e os valores à segunda variável (name).
• A função string.sub retorna uma substring de uma dada string (name,
no caso). Assim como arrays, caracteres em strings são indexados a
partir de 1. Em particular, a chamada como feita no exemplo vai retornar
um prefixo de name com comprimento len.
• Na condição do if, usamos o fato de que o valor nil, que é o valor
de campos não inicializados em uma tabela, é equivalente a falso em
qualquer condição. Assim, o que está sendo testado é se o campo da
tabela com chave prefix já foi preenchido anteriormente.
• No caso do teste dar positivo (isto é, a tabela já ter um elemento com a
dada chave), a função coloca o valor true na posição já ocupada. Como
este valor não é uma string, ele serve como uma marca para colisões.2
Após a construção da tabela de prefixos, seu uso é bem simples. Dado um
2 Outra opção seria colocar na posição do conflito uma lista com todas as possíveis palavras
para o dado prefixo.
prefixo, o código a seguir retorna a palavra completa ou dá um erro adequado:
A função predefinida type, quando aplicada a qualquer valor, retorna uma
string com seu tipo. Os valores de retorno possíveis são "nil", "number",
"string", "boolean", "table", "function", "thread" e "userdata".
Já vimos, pelo menos brevemente, os tipos number, string, table e nil (que é o
tipo do valor nil). Iremos abordar os tipos function e thread mais a frente. O
tipo userdata é usado para representar objetos externos a Lua (e.g., arquivos).
Isso deixa faltando apenas o tipo boolean.
Como em outras linguagens, o tipo boolean em Lua tem apenas dois valores,
true e false. Mas os valores booleanos não têm exclusividade para
testes. Em qualquer teste da linguagem (if, while e mesmo operadores lógicos)
os valores nil e false resultam em um teste negativo, e qualquer outro
valor (incluindo true, mas também 0, a string vazia, etc.) resulta em um teste
positivo. Antes de sua versão 5.0, Lua nem tinha um tipo booleano. A principal
motivação para a inclusão desse tipo na linguagem foi permitir a distinção entre
variáveis com valor falso (false) e variáveis não inicializadas (e portanto
com valor nil). Um teste como if not x dá positivo nos dois casos, mas um
teste como if x == false só dá positivo se o valor de x for false.
Em Lua, assim como em várias outras linguagens dinâmicas, booleanos
não têm exclusividade como resultado de operadores lógicos. O operador or
retorna sempre o valor do primeiro operando que define o valor final da disjunção.
A seguir listamos algumas expressões e seus respectivos resultados:
5 or 7 --> 5
nil or 7 --> 7
nil or false --> false
false or nil --> nil
De forma análoga, o operador and retorna o valor do primeiro operando que
define o valor final da conjunção:
"a" and "b" --> "b"
nil and "alo" --> nil
nil and false --> nil
false and nil --> false
O operador not, entretanto, sempre retorna um booleano. Em particular, a expressão
not not x normaliza o valor de x para um booleano correspondente.
Lua é uma linguagem de script amplamente usada nas mais diversas áreas, desde grandes
aplicativos para desktops, como o Adobe Photoshop Lightroom, até software para sistemas embarcados.
Lua é a linguagem mais usada atualmente para scripting em jogos, e é parte do padrão Ginga
para o Sistema Brasileiro de TV Digital. Lua também é muito usada na área de segurança, sendo a
linguagem de script embutida em ferramentas como Wireshark, snort e nmap.
Este texto apresenta a linguagem Lua com ênfase nos seus mecanismos menos convencionais.
O objetivo é introduzir a linguagem e ao mesmo tempo apresentar algumas técnicas de programação
não convencionais, como o uso de funções de mais alta ordem, co-rotinas e APIs entre linguagens.
Espera-se do leitor alguma maturidade na área de programação e conhecimento da linguagem C,
para a discussão da API entre Lua e C.
Introdução
O objetivo deste texto é introduzir o leitor à programação na linguagemLua.
Assumimos que o leitor (você) possui uma certa maturidade em programação
com alguma linguagem qualquer.
Programar em Lua não é muito diferente de programar em outras linguagens
dinâmicas, mas é diferente. Cada linguagem apresenta características
próprias, e um bom programador sabe explorar as características particulares
de cada linguagem. Neste texto, vamos procurar enfatizar as particularidades
de Lua, aspectos que tornam a programação em Lua diferente da programação
em outras linguagens dinâmicas. Em particular, em Lua, temos como importantes
diferenciais o uso de técnicas de programação funcional, o uso ubíquo
de tabelas como estruturas de dados para os mais variados fins, o uso de corotinas
e a comunicação com código escrito em C.
Bom, então programar em Lua não é tão diferente de programar em outras
linguagens dinâmicas. Mas afinal, o que é uma linguagem dinâmica? Como
ocorre frequentemente em computação, esse termo não possui uma definição
precisa e universalmente aceita. Mas existe um certo consenso de que linguagens
dinâmicas apresentam as seguintes características:
Interpretação dinâmica: isso significa que a linguagem é capaz de executar
trechos de código criados dinamicamente, no mesmo ambiente de execução
do programa. Como exemplos dessa facilidade temos a função
loadstring em Lua e a função eval em Scheme/Lisp e Perl.
Tipagem dinâmica forte: tipagem dinâmica significa que a linguagem faz verificação
de tipos em tempo de execução do programa. Linguagens com
tipagem dinâmica em geral não possuem declarações de tipos no código
e não fazem verificação de tipos em tempo de compilação. Tipagem
forte significa que a linguagem jamais aplica uma operação a um tipo
incorreto.
Gerência automática de memória dinâmica (coleta de lixo): isso significa que
não precisamos gerenciar memória explicitamente no nosso programa;
em especial, não há necessidade de um comando para liberar memória
após seu uso.
Em geral, linguagens dinâmicas são interpretadas, e não compiladas para
código nativo da máquina; mas essa é uma característica das implementações
dessas linguagens, não das linguagens em si. Obviamente, as características
acima favorecem uma implementação via um interpretador e dificultam a
construção de compiladores.
Dessas características, a interpretação dinâmica é a mais exclusiva de linguagens
dinâmicas. Obviamente, em qualquer linguagem Turing-completa podemos
escrever um interpretador para a própria linguagem, mas os trechos de
código interpretados não serão executados no mesmo ambiente do programa
interpretador. Por exemplo, podemos escrever um interpretador para C em C,
mas os programas interpretados não terão acesso às variáveis e funções declaradas
no programa compilado onde o interpretador está sendo usado.
Apesar de não ser uma característica exclusiva de linguagens dinâmicas, a
gerência automática de memória é um mecanismo importante dessa lista, por
haver uma enorme diferença entre programarmos em uma linguagem com e
em uma linguagem sem gerência automática de memória. Mesmo na programação
em ponto grande (programming in the large) a gerência automática de
memória tem um impacto significativo, ao simplificar as interfaces entre componentes.
(Como um exercício, pegue a API de qualquer biblioteca C de porte
razoável e verifique quanto de sua complexidade é devida à gerência de memória.)
Na verdade, existe um contínuo entre linguagens estáticas e dinâmicas. Por
exemplo, Java é uma linguagem muito mais dinâmica do que C, pois apresenta
gerência automática de memória, um certo grau de tipagemdinâmica e um mecanismo
embrionário de interpretação dinâmica (por meio da carga dinâmica
de classes, que por sua vez podem ser criadas dinamicamente). Mesmo entre
as linguagens reconhecidamente dinâmicas existem diferenças. Por exemplo,
nem todas as linguagens dinâmicas têm gerência automática de memória sobre
módulos ou classes.
Lua se destaca de outras linguagens dinâmicas por ser uma linguagem de
script. Uma linguagem de script é uma linguagem projetada para controlar e
coordenar componentes geralmente escritos em outra linguagem. As primeiras
linguagens de script foram as linguagens de shell do Unix, usadas para
conectar e controlar a execução de programas. Apesar de várias linguagens
dinâmicas poderem ser usadas para script, poucas foram projetadas para essa
finalidade. Lua seguiu um caminho criado por Tcl [Ousterhout 1990], onde a
linguagem é estruturada como uma biblioteca C com uma API que permite
tanto código na linguagem chamar funções escritas em C como código C chamar
funções escritas na linguagem. Lua se destaca de outras linguagens de
script por sua simplicidade, portabilidade, economia de recursos e desempenho
[Ierusalimschy et al. 2007].
Como usar Lua
A linguagem Lua conta com uma única implementação principal, mantida
pelos autores da linguagem no site [Tens de ter uma conta e sessão iniciada para poderes visualizar este link] mas essa implementação
conta com diversas distribuições mantidas por desenvolvedores independentes.
Em muitos usos reais de Lua, o interpretador é distribuido embutido na aplicação
final. Afinal, um dos principais objetivos de Lua é exatamente esse tipo
de uso. Nesses casos, detalhes de como usar Lua são dependentes da aplicação.
Esses detalhes incluem que editor usar, onde e como armazenar os
programas, como executar um programa, etc. Neste texto, como não estamos
visando nenhuma aplicação particular, vamos usar o interpretador independente
(stand alone) de Lua.
Para máquinas Windows, uma ótima opção de instalação é a distribuição
Lua for Windows (LfW).1 Essa distribuição é um pacote completo para Windows,
incluindo não apenas o interpretador Lua com suas bibliotecas padrão,
mas também um editor e várias bibliotecas extras populares.
Para máquinas Linux não há uma receita pronta, dada a diversidade de
distribuições de Linux. Compilar Lua em uma máquina Linux é muito simples
e rápido. Algumas distribuições já vêm com Lua instalado por default. Outras
oferecem pacotes prontos: por exemplo, em Ubuntu e Debian, basta instalar
o pacote lua5.1, que é o interpretador com as bibliotecas padrão. Várias
bibliotecas externas também são oferecidas como pacotes extras. Em qualquer
caso, o interpretador independente de Lua é um programa de linha de
comando, para ser executado por meio de um terminal.
Para máquinas Mac OS X, existe a opção de compilar diretamente o fonte,
desde que a máquina já tenha as ferramentas de desenvolvimento em C instaladas.
O processo é simples e rápido como no Linux. Outra opção é usar um
gerenciador de pacotes; por exemplo, tanto o MacPorts quanto o Fink oferecem
pacotes prontos para Lua.
Uma vez instalado, é muito fácil usarmos o interpretador. Lua não tem o
conceito de uma função main; qualquer comando passado ao interpretador é
imediatamente executado. O exemplo a seguir é um programa completo para
imprimir p2:
print(2^(1/2)) --> 1.4142135623731
O operador ^ é o operador de exponenciação em Lua. Lua trabalha sempre
com números reais em ponto flutuante e a exponenciação funciona para expoentes
fracionários (e negativos também). Em geral, usamos a notação -->
para indicar o resultado de um comando. Como em Lua dois traços -- iniciam
um comentário que vai até o final da linha, podemos incluir aquela indicação
no programa.
Ao usar Lua via um terminal, você tem pelo menos quatro maneiras de
executar esse pequeno “programa”:
• Você pode usar a opção de linha de comando -e:
$ lua -e "print(2^0.5)"
(Estou assumindo que $ é o prompt do terminal.)
• Você pode entrar com o programa em modo interativo:
$ lua
> print(2^0.5)
(O > é o prompt do interpretador Lua em modo interativo.)
• Você pode escrever esse programa em um arquivo e executá-lo via linha
de comando:
$ lua nome-do-arquivo
• Você pode escrever esse programa em um arquivo e executá-lo via modo
interativo, por meio da função predefinida dofile:
$ lua
> dofile("nome-do-arquivo")
Podemos dispensar os parênteses em chamadas de função onde o único argumento
é uma string literal. Assim, você pode reescrever o exemplo anterior
como a seguir:
> dofile"nome-do-arquivo"
Quando você chama o interpretador, ele cria um estado Lua que persiste
até o fim da sua execução. Assim, todos os efeitos colaterais de cada comando
se propagam para os próximos comandos, mesmo que eles sejam executados
como “programas” em separado. Veja o exemplo a seguir:
$ lua
> x = 1
> print(x) --> 1
Cada uma das linhas é executada como um trecho (chunk, em inglês) separado,
mas o valor da variável global x se mantém após o primeiro trecho ter
terminado.
Alguns Exemplos
À primeira vista, Lua é uma linguagem imperativa, razoavelmente convencional.
Como já discutimos, não vamos perder muito tempo descrevendo
essa parte mais convencional da linguagem. Uma boa parte você vai aprender
apenas vendo os exemplos; se precisar de maiores detalhes, consulte
o manual de referência [Ierusalimschy et al. 2006] ou o livro Programming in
Lua [Ierusalimschy 2006].
Seguem alguns exemplos de funções simples em Lua, para você se familiarizar
com o básico da linguagem.
Soma dos elementos de um array
- Código:
function add (a)
local sum = 0
for i = 1, #a do sum = sum + a[i] end
return sum
end
• Como a linguagem tem tipagem dinâmica, não há tipos nas declarações
de variáveis, parâmetros, etc.
• A palavra reservada local declara uma variável local, cujo escopo vai
da declaração até o fim do bloco mais interno que contém a declaração.
No exemplo, sum é visível até o fim da função add.
• A expressão #a retorna o comprimento do array a. Como arrays em Lua
começam no índice 1, o comprimento é também o valor do último índice.
• O comando for vai repetir seu corpo com o valor da variável i variando
de 1 até o comprimento do array (#a). A variável de controle i é
declarada pelo próprio comando for e só é visível dentro do seu corpo.
• Todas as estruturas de controle têm um terminador explícito. Tanto o
corpo da função quanto o corpo do for terminam com a palavra reservada
end.
Soma das linhas de um arquivo
A função a seguir recebe o nome de um arquivo texto, que deve conter uma
lista de números, e retorna a soma desses números:
- Código:
function addfile (filename)
local sum = 0
for line in io.lines(filename) do
sum = sum + tonumber(line)
end
return sum
end
Essa função é semelhante à do exemplo anterior, com exceção do for. No
exemplo anterior, usamos um for numérico, que iterage sobre uma progressão
aritmética de números. Neste exemplo, usamos um for genérico, que usa um
gerador (io.lines, no caso, fornecida pela biblioteca padrão de Lua) para
gerar os valores da iteração.
Note também o uso da função tonumber, para converter o numeral lido
(uma string) para um número. Lua faz esse tipo de conversão automaticamente
sempre que uma string é usada em uma operação aritmética, mas consideramos
mais educado efetuar a conversão explicitamente.
Casamento de prefixos
Um problema comum em várias áreas é, dada uma lista de palavras, decidir
se uma dada string é prefixo de alguma das palavras da lista. Por exemplo,muitos
sistemas de linha de comando permitem que entremos com um comando
digitando apenas os primeiros caracteres do nome do comando.
Uma solução usual para esse problema é construir uma tabela de prefixos,
que mapeia todos os prefixos de cada palavra na lista para a palavra completa.
Dada essa tabela, o problema original é resolvido com uma simples consulta.
- Código:
function buildPrefixTable (list)
local t = {}
for _, name in ipairs(list) do
for len = 1, #name do
local prefix = string.sub(name, 1, len)
if t[prefix] then
t[prefix] = true -- colisao
else
t[prefix] = name
end
end
end
return t
end
Função para construir uma tabela de prefixos.
A função na Figura 3.1 recebe uma lista (array) de palavras e retorna sua
tabela de prefixos. Nesse código temos novamente várias novidades:
• A expressão {} cria uma tabela vazia, que é atribuida à variável local t.
• O laço externo usa o gerador ipairs, que percorre todos os índices
e valores do array dado (list). Os índices são atribuídos à primeira
variável (que nomeamos _, já que não estamos interessados no seu
valor), e os valores à segunda variável (name).
• A função string.sub retorna uma substring de uma dada string (name,
no caso). Assim como arrays, caracteres em strings são indexados a
partir de 1. Em particular, a chamada como feita no exemplo vai retornar
um prefixo de name com comprimento len.
• Na condição do if, usamos o fato de que o valor nil, que é o valor
de campos não inicializados em uma tabela, é equivalente a falso em
qualquer condição. Assim, o que está sendo testado é se o campo da
tabela com chave prefix já foi preenchido anteriormente.
• No caso do teste dar positivo (isto é, a tabela já ter um elemento com a
dada chave), a função coloca o valor true na posição já ocupada. Como
este valor não é uma string, ele serve como uma marca para colisões.2
Após a construção da tabela de prefixos, seu uso é bem simples. Dado um
2 Outra opção seria colocar na posição do conflito uma lista com todas as possíveis palavras
para o dado prefixo.
prefixo, o código a seguir retorna a palavra completa ou dá um erro adequado:
- Código:
function complete (t, prefix)
local w = t[prefix]
if type(w) == "string" then return w
elseif w == true then error("ambiguous prefix")
else error("invalid prefix")
end
end
A função predefinida type, quando aplicada a qualquer valor, retorna uma
string com seu tipo. Os valores de retorno possíveis são "nil", "number",
"string", "boolean", "table", "function", "thread" e "userdata".
Já vimos, pelo menos brevemente, os tipos number, string, table e nil (que é o
tipo do valor nil). Iremos abordar os tipos function e thread mais a frente. O
tipo userdata é usado para representar objetos externos a Lua (e.g., arquivos).
Isso deixa faltando apenas o tipo boolean.
Como em outras linguagens, o tipo boolean em Lua tem apenas dois valores,
true e false. Mas os valores booleanos não têm exclusividade para
testes. Em qualquer teste da linguagem (if, while e mesmo operadores lógicos)
os valores nil e false resultam em um teste negativo, e qualquer outro
valor (incluindo true, mas também 0, a string vazia, etc.) resulta em um teste
positivo. Antes de sua versão 5.0, Lua nem tinha um tipo booleano. A principal
motivação para a inclusão desse tipo na linguagem foi permitir a distinção entre
variáveis com valor falso (false) e variáveis não inicializadas (e portanto
com valor nil). Um teste como if not x dá positivo nos dois casos, mas um
teste como if x == false só dá positivo se o valor de x for false.
Em Lua, assim como em várias outras linguagens dinâmicas, booleanos
não têm exclusividade como resultado de operadores lógicos. O operador or
retorna sempre o valor do primeiro operando que define o valor final da disjunção.
A seguir listamos algumas expressões e seus respectivos resultados:
5 or 7 --> 5
nil or 7 --> 7
nil or false --> false
false or nil --> nil
De forma análoga, o operador and retorna o valor do primeiro operando que
define o valor final da conjunção:
"a" and "b" --> "b"
nil and "alo" --> nil
nil and false --> nil
false and nil --> false
O operador not, entretanto, sempre retorna um booleano. Em particular, a expressão
not not x normaliza o valor de x para um booleano correspondente.
Última edição por GM HaLT em Qua 03 Out 2012, 19:14, editado 1 vez(es)