Lu-a-Bá - O bê-a-bá de Lua para a comunidade lusófona

Executando código Lua

Existem várias maneiras de executar código Lua. Neste tutorial, veremos algumas delas.

Nossa principal ferramenta será o interpretador lua, que usamos para executar código Lua. Ele pode receber o código por vários métodos diferentes, e então se encarrega da execução.

Por enquanto, vamos trabalhar com três métodos: interativo, por arquivos e por argumentos de linha de comando.

Também vamos explorar outra ferramenta, o compilador luac, que podemos usar para agilizar o carregamento de códigos Lua e verificar por erros eventuais.

Se você ainda não obteve Lua, deverá cumprir este requisito primeiro. Feito isso, terá os programas lua e luac, que usaremos aqui.

Modo interativo

Lua é uma linguagem dinâmica. Podemos informar um comando ao interpretador e já observar os seus resultados, antes de passar para a execução do próximo comando. Isto facilita o aprendizado. Vamos começar explorando essa característica.

Como vimos no tutorial anterior, ao executar o programa lua sem nenhum parâmetro, veremos o seu prompt de comandos.

$ lua
Lua 5.4.6  Copyright (C) 1994-2023 Lua.org, PUC-Rio
> 

Recapitulando, o sinal > (isto é, o prompt) quer dizer que o interpretador está à espera de um próximo comando. Ao inserir o comando (seguido da tecla Enter), o interpretador vai executar o comando informado, que pode ou não ter alguma saída na tela (ou seja, algum efeito visível).

Vamos começar com um comando bem simples, que apenas exibirá na tela do seu terminal o texto “Estou na Lua”.

> print("Estou na Lua")
Estou na Lua

Neste comando estamos chamando a função print que serve para exibir algo na tela. Informamos o valor que quermos exibir, e a função print escreve (ou imprime, na terminologia original) na tela.

Essa não é a única forma de fazer algo aparecer na tela. Existe outro método, que é retornar algum valor. Um valor como o texto “Estou na Lua” que usamos antes, por exemplo.

> return "Estou na Lua"
Estou na Lua

Parece confuso que haja duas maneiras de obter o mesmo efeito? Pode ser, mas logo veremos a diferença entre esses dois métodos. Por enquanto, vamos nos ater a um fato mais pragmático: podemos omitir o comando return:

> "Estou na Lua"
Estou na Lua

Há uma observação importante a fazer, sobre essa omissão do comando return: até Lua 5.2, era necessário inserir um sinal de igual antes do valor retornado (o que ainda funciona nas versões mais novas):

> = "Estou na Lua"
Estou na Lua

A partir da versão 5.3, isso deixou de ser necessário, e podemos omitir também o sinal de igual.

Até aqui trabalhamos apenas com um valor, que é um texto. A rigor, ele é o que chamamos de uma expressão. Em Lua, existem vários tipos de expressão, e o texto é um deles.

Mas além do texto, números são expressões, assim como operações aritméticas sobre números. Vejamos:

> 3
3
> 2 + 2
4

O exemplo de operação aritmética nos mostra que o interpretador pode ser usado como uma calculadora. Mas ele vai muito além disso. Podemos trabalhar com variáveis também:

> i = 3

Note que esse comando não emite nenhuma saída. Note também que eu disse comando e não expressão. Tecnicamente, há uma diferença. Expressões sempre são retornadas, e portanto você sempre verá uma saída para elas. Comandos não são retornados, e eles podem emitir alguma saída, ou não.

Neste caso, estamos executando um comando de atribuição de um valor (3) a uma variável (i). Dito isso, podemos criar expressões mais complexas e até mesmo utilizar variáveis dentro delas.

> i = 3
> 2 * (i + 14) + i * i
43

Ou seja, na primeira linha, informamos um comando (de atribuição), e na segunda linha informamos uma expressão. Relembrando, ao informar uma expressão ao interpretador, ele retorna seu resultado.

Para ser ainda mais preciso, quando visualizamos o prompt, isso significa que o interpretador está à espera do que chamamos, na terminologia de Lua, de um trecho.

Um trecho é um agrupamento lógico (um bloco), que é interpretado de uma vez só, e pode conter um ou mais comandos ou blocos internos.

Então podemos em uma mesma linha informar mais de um comando.

> i = 3 print(i)
3
> i = 2 return i
2

Veja que não foi necessário nem mesmo separar a atribuição de i da chamada ao comando posterior. Podemos, por uma questão puramente estética, inserir um ponto-e-vírgula entre os dois comandos. Isso não afeta em nada a saída.

> i = 3 ; print(i)
3

E por falar em estética, outro recurso que ajuda a melhorar a visualização do código ao usar o interpretador, é dividir partes de um mesmo trecho em mais de uma linha. Quando está óbvio para o interpretador que o trecho informado está incompleto, ele aguardará pelo resto antes de executar.

Vejamos um exemplo:

> print (
>> 2 +
>> 2
>> )
4

Veja que ao chamar a função print, abrimos parênteses, mas não fechamos na mesma linha, dando a entender que isso será feito em outra linha. O interpretador então altera o prompt para dois sinais de maior (>>) no lugar de apenas um, indicando que aguarda pelo resto do trecho, antes de executá-lo.

Antes de encerrar a chamada de função informamos uma expressão (2 + 2), que por acaso também foi quebrada em mais de uma linha. Somente ao informar o fechamento dos parênteses o trecho é dado como completo e o interpretador o executa.

Esse prompt modificado que vimos, chamamos usualmente de prompt secundário. No exemplo acima, pode não fazer tanto sentido dividir o trecho, por ser demasiado simples, mas em trechos envolvendo definições de funções ou laços repetitivos (que veremos em outros tutoriais) o uso desse recurso será mais produtivo.

Para encerrar o modo interativo, você pode usar, como vimos no tutorial anterior, uma chamada à função os.exit() (ainda exploraremos funções em outro tutorial).

> os.exit()

Um modo ainda mais prático é inserir um caractere de fim de linha, embora isso seja específico do sistema operacional. Tipicamente, em sistemas POSIX, podemos inserí-lo com a combinação Ctrl+D, e no terminal do Windows com a combinação Ctrl+Z.

Bem, até aqui acho que já deu para entender o básico de como funciona o interpretador no modo interativo.

E a propósito, não subestime esse método de desenvolvimento. Ele não apenas é uma ferramenta de grande valor para o aprendizado, como também permite explorar o seu programa enquanto o desenvolve. E ele pode ainda ser enriquecido por outros métodos, como o uso de arquivos-fonte Lua, como veremos a seguir.

Arquivos-fonte Lua

Do mesmo modo que podemos informar trechos interativamente ao interpretador lua, podemos também armazenar todo o seu conteúdo em um arquivo, e então passar este arquivo como argumento para o interpretador.

Convencionalmente chamamos esses arquivos de arquivo-fonte1, visto que ele contém código-fonte Lua, que será executado pelo interpretador. Um arquivo-fonte não possui nada de muito especial, é um arquivo de texto comum, no qual inserimos os trechos Lua que queremos executar.

Também, por convenção, utilizamos a extensão .lua nesses arquivos, para denotar que se trata de um arquivo-fonte Lua. Você pode utilizar qualquer editor de texto simples como o editor vi em sistemas POSIX ou o bloco de notas do Windows, por exemplo.

Talvez eles não ofereçam muito conforto para programar2, mas é fato que eles servem a esse propósito, na falta de uma opção mais ergonômica.

Alguns editores de texto, como vim ou geany por exemplo, já oferecem mais recursos para a programação, e talvez sejam mais ao seu estilo.

Seja como for, Lua é uma linguagem simples, o que elimina a necessidade de um editor muito avançado para programar. Essa é uma das vantagens de ter a simplicidade como uma característica impressa na linguagem.

Então vamos começar com um teste básico. Crie um arquivo chamado astronauta.lua com o conteúdo:

print("Estou na Lua")

Depois, execute no terminal o seguinte comando:

$ lua astronauta.lua
Estou na Lua

O resultado, como podemos ver, será a exibição na tela do texto “Estou na Lua”, igual já vimos antes na execução interativa. É importante notar que como não estamos no modo interativo, precisamos recorrer à função print para exibir uma saída.

Perceba também que nesse texto de exemplo não temos nenhuma ocorrência de acentos ou cedilha. Isso não foi por acaso. Arquivos-fonte são, como falei, arquivos de texto, e portanto estão sujeitos a questões relacionadas à codificação de texto, algo que não vamos explorar agora (vide “Codificação de caracteres”, na seção adendos).

Por hora, apenas saiba que existem situações em que, executar no terminal um arquivo-fonte contendo acentos e cedilhas pode emitir alguns caracteres estranhos na tela, então por enquanto vamos evitá-los.

Voltando ao tema central deste tópico, a execução de códigos a partir de arquivo-fonte possui como vantagem o fato de que podemos armazenar nosso código em um arquivo e reexecutá-lo tantas vezes quanto for preciso, e gradualmente fazer modificações no programa, quando necessário.

Para ver como isso é prático faça uma modificação no arquivo astronauta.lua, insira nele uma segunda linha, com o comando:

print("Sinto falta da Terra")

Agora execute novamente, e você verá não uma, mas duas mensagens, como a seguir:

$ lua astronauta.lua
Estou na Lua
Sinto falta da Terra

O ponto a se observar aqui é que você não precisou reescrever o primeiro comando, apenas inserir o segundo. À medida que criar programas maiores, perceberá o quanto é conveniente salvar em arquivos-fonte as partes que quer reaproveitar (ou que pode querer modificar no futuro).

Comentários

Outra vantagem no uso de arquivos-fonte é o fato de que podemos inserir neles alguns comentários. Como em várias outras linguagens, comentários não afetam a execução do programa, mas podem ser úteis para o programador, ao ler o código do programa.

Comentários servem para facilitar a compreensão do programa, em geral para alertar sobre algo que não deve ser modificado, ou para explicar por que algo foi feito de determinado modo.

Em Lua, comentários são iniciados pela sequência de caracteres -- (dois hífens), e se estendem até o fim da linha. Vamos adicionar um comentário ao programa astronauta.lua, de modo que ele possua o seguinte conteúdo:

-- Mensagens de um astronauta

print("Estou na Lua")
print("Sinto falta da Terra")

Veja que na primeira linha inserimos um comentário. Execute o programa, e perceba que o comentário em nada alterou o comportamento do programa, mas ao ler o arquivo astronauta.lua você verá a explicação de que o programa emite mensagens de um astronauta.

Comentários podem ser inseridos na mesma linha onde há um comando (ou mesmo parte de um comando). Muitas vezes, fazemos isso para anunciar qual é o resultado que se espera da execução de uma linha específica. Por exemplo:

print(2+2) --> 4

O sinal de maior (>) logo após o início do comentário não muda absolutamente nada, apenas é uma convenção utilizada quando a finalidade é justamente essa de anunciar um resultado esperado, pois o formato de seta pode ter essa conotação de “resultado”.

Além desse comentário de linha, existem também os comentários de blocos, eles abrangem um grupo de linhas entre os seus delimitadores. Vejamos um exemplo.

--[[
    Astronauta
    ----------
    
    Mensagens de um astronauta na Lua.
--]]

print("Estou na Lua")
print("Sinto falta da Terra")

Veja que usamos os delimitadores --[[ e --]] para, respectivamente, abrir e fechar o bloco, que vai da primeira à sexta linha desse exemplo.

Comentários de bloco são úteis para mensagens mais longas, que necessitem várias linhas, mas não apenas isso. Também são úteis em situações nas quais você pode querer facilmente “ativar/desativar” determinado trecho de código.

Vejamos um exemplo:

i = 3
--[[
print("ALERTA: usando valor dobrado")
i = 2 * i
--]]
print(i)

Neste exemplo, atribuímos um valor à variável i, e depois imprimimos esse valor na tela. Porém no meio há um comentário de bloco. Sendo um comentário, não afeta o resultado da execução.

Mas digamos que eu queira testar o resultado da execução fazendo com que o código dentro do comentário passe a ser válido. Muito simples: adicionamos um hífen extra no delimitador de abertura do bloco, do seguinte modo:

i = 3
---[[
print("ALERTA: usando valor dobrado")
i = 2 * i
--]]
print(i)

Notou a diferença na abertura do comentário? Agora são três hífens no lugar de dois. Isso faz com que ele passe a ser um comentário normal, e não mais uma abertura de comentário de bloco (e o de fechamento também passa a ser tratado como um comentário normal).

Logo, todo o código dentro do bloco passa a ser válido. Para desativar novamente, basta remover esse hífen extra.

Chamando arquivo em código Lua

Lembra quando eu disse que podemos combinar o método interativo com o uso de arquivos-fonte? Chegou a hora de ver como. Em Lua, existe uma função chamada dofile, que lê o conteúdo de um arquivo-fonte e o executa.

Podemos chamar essa função enquanto executamos interativamente o interpretador. Vejamos um exemplo com nosso script, astronauta.lua.

> dofile("astronauta.lua")
Estou na Lua
Sinto falta da Terra

Nosso arquivo astronauta.lua apenas emite algumas mensagens, portanto se executarmos de novo, o resultado será exatamente o mesmo, ou seja, as mensagens serão exibidas de novo. Em outros casos, poderíamos ter um arquivo-fonte cuja execução depende do valor de alguma variável, e nesse caso o resultado poderá ser diferente a cada nova chamada.

Podemos usar este recurso para realizar uma sequência de cálculos e validações, e ao final retornar o resultado. Vamos ver um outro exemplo de arquivo-fonte, que realiza uma operação aritmética e retorna seu resultado.

return 2 * (i + 14) + i * i

Salve este arquivo como calculo.lua, e em seguida, execute interativamente, definindo previamente o valor de i:

> i = 3
> dofile("calculo.lua")
43

Veja que o arquivo utilizado possui um retorno, que corresponde ao resultado da expressão. Neste caso, poderíamos definir qualquer valor para i e reexecutar o cálculo, obtendo um novo resultado. Esse retorno é apresentado na tela, como qualquer outro retorno seria (o que já vimos na seção anterior).

Se quisermos, podemos salvar este retorno em uma variável, no lugar de exibir na tela:

> i = 3
> resultado = dofile("calculo.lua")
> resultado
43

A função dofile não precisa ser chamada apenas no modo interativo. Podemos usá-la dentro de arquivos-fonte também, de modo que teremos assim chamadas aninhadas, um arquivo chamando outro arquivo. Porém, em tutoriais futuros veremos maneiras mais interessantes de realizar esse tipo de operação.

Pré-executar arquivo

Além da função dofile, temos ainda outra possibilidade de combinação dos modos de execução. Em vez de chamar o arquivo após iniciado o modo interativo, podemos informar um parâmetro (-i) para o interpretador lua, seguido do nome de um ou mais arquivos-fonte Lua que queremos pré-executar.

Isto fará com que o arquivo seja executado antes de entrar no modo interativo. Vejamos um exemplo com o nosso já conhecido astronauta.lua:

$ lua -i astronauta.lua
Lua 5.4.6  Copyright (C) 1994-2023 Lua.org, PUC-Rio
Estou na Lua
Sinto falta da Terra
> 

Perceba a ordem dos eventos neste caso: primeiro o interpretador Lua é iniciado (a mensagem de direitos autorais que aparece no início está aí para nos mostrar isso), depois nosso arquivo astronauta.lua é executado, exibindo as mensagens de saída, e por fim vemos o prompt de comandos do interpretador.

Neste caso, usamos um arquivo simples, que apenas emite mensagens. Mas poderíamos ter usado um arquivo que define variáveis ou novas funções, o que nos habilita a criar um ambiente que pode ser reutilizado toda vez que iniciarmos o interpretador.

A título de exemplo, crie um arquivo chamado ambiente.lua, com o seguinte conteúdo:

i = 3
function dobro(n) return n * 2 end

Neste exemplo definimos uma variável e uma função (veremos mais sobre criação de funções em outro tutorial), que vamos utilizar no modo interativo. Então inicie o modo interativo importando esse arquivo, como a seguir:

$ lua -i ambiente.lua
> i
3
> dobro(i)
6

Isto ficará ainda mais interessante quando explorarmos o conceito de módulos, mas por enquanto acho que você já possui algumas ideias para colocar em prática. Vamos explorar módulos em outro momento.

Na linha de comando

Além de informar código Lua em um interpretador interativo e via arquivos-fonte, podemos ainda informar o código como parâmetro de linha de comando para o interpretador lua, isto é, de modo não interativo.

Veja um exemplo:

$ lua -e 'print("Amigo, estou aqui.")'
Amigo, estou aqui.

O que acontece neste exemplo é que o interpretador lua é iniciado, recebe um pequeno trecho de código como argumento e o executa. Uma vez executado esse trecho, o interpretador é encerrado.

Não parece muito útil, não é mesmo? Bem, é útil para alguns pequenos testes, mas podemos incrementar essa execução não interativa, ao encadear o parâmetro -e diversas vezes.

$ lua -e 'i = 2 + 2' -e 'print(i)'
4

Note que no primeiro trecho temos uma atribuição da variável i e no segundo trecho passamos i como argumento para a função print. Isso mostra que a ordem dos trechos informados importa, e que variáveis definidas em um trecho anterior podem ser usadas em um trecho posterior.

Podemos combinar esse modo de execução com arquivos-fonte, ao executar a função dofile em um dos trechos informados, veja:

$ lua -e 'i = 3' -e 'print(dofile("calculo.lua"))'
43

Existe diferença entre usar vários parâmetros -e separados ou usar um só com todo o código? Não muita, mas existe.

Além da diferença estética óbvia, o que permite reorganizar a chamada de linha de comando para ficar mais legível, cada parâmetro -e informado é um trecho Lua por si só.

E vamos lembrar, cada trecho de código é “compilado” individualmente (veremos na seção seguinte o que essa compilação significa), o que permite que você determine explicitamente a ordem de compilação dos trechos, como se os estivesse informando, um por um, no modo interativo.

O compilador Lua

Falar em compilação em Lua pode confundir algumas pessoas, ainda mais para quem leu o tutorial anterior, que trata da instalação de Lua, e que menciona a compilação do código-fonte da biblioteca Lua e dos programas lua e luac.

No contexto da linguagem Lua, ou seja, ao falar em compilação de código Lua, o significado do termo compilação muda bastante. Aqui não se trata de gerar código em linguagem de máquina pronto para executar, e sim de gerar bytecode, um formato intermediário entre a linguagem Lua e a linguagem de máquina.

Lembra-se quando disse, no tutorial anterior, que a biblioteca Lua transforma o código Lua em código de máquina, e que essa biblioteca é usada pelo interpretador lua? Pois bem, essa biblioteca possui dentro de si uma máquina virtual3, que obtém instruções na forma de bytecode, e converte em linguagem de máquina, para executar.

Portanto, quando você informa um trecho ao interpretador, ele converte primeiro esse trecho em bytecode e depois a máquina virtual lê o bytecode gerado, para enfim transformar em linguagem de máquina e executar.

Esse procedimento é repetido para cada trecho que você informa ao interpretador. O que o compilador luac faz é antecipar essa primeira parte, da conversão para o bytecode. Vamos ver como isso é feito (novamente com o astronauta.lua).

$ luac astronauta.lua

Feito isso, o arquivo luac.out terá sido gerado. Você não conseguirá ler diretamente o seu conteúdo por se tratar de um formato binário. Mas você pode executá-lo, como se ele fosse um arquivo-fonte Lua. Veja:

$ lua luac.out
Estou na Lua
Sinto falta da Terra

A princípio, pode não parecer muito diferente de uma execução direta, porém nesse caso encurtamos o processo em uma etapa, o que faz diferença quando trabalhamos com arquivos-fonte maiores e/ou em maior quantidade.

No entanto, é preciso dizer que a compilação Lua não afeta em nada o tempo de execução do programa, apenas antecipa a conversão para o bytecode.

Se preferir, você pode usar outro nome para o arquivo de saída, no lugar de luac.out. Basta usar a opção -o.

$ luac -o astronauta.out astronauta.lua

Isto é útil quando precisar gerar vários arquivos intermediários dentro de um mesmo diretório. Ainda outra opção é não gerar arquivo de saída nenhum, para isto utilizamos o parâmetro -p:

$ luac -p astronauta.lua

E por que isso é útil? Porque se houver erros de sintaxe no arquivo-fonte, o compilador vai avisar, permitindo que você identifique e corrija esses erros. Caso nenhum erro seja detectado, o programa não emite nenhuma saída. Caso encontre, informará a linha e uma breve mensagem de erro.

Essa técnica pode inclusive ser usada com arquivos de bytecode gerados anteriormente, para se certificar de que são válidos e/ou não foram corrompidos.

$ luac -p astronauta.out

É importante lembrar que bytecodes gerados para uma versão específica de Lua não podem ser usados em outra versão. Sempre tenha o arquivo-fonte original como garantia de que poderá gerar um bytecode válido, quando necessário.

Resumo

Muito bem! Agora você já sabe como executar código Lua de três maneiras diferentes:

Também viu como combinar esses métodos de execução, usando a função dofile e importações com a opção -i do interpretador. Além disso, viu alguns exemplos de comentários em arquivos-fonte, para facilitar o entendimento do código.

E ainda compreendeu o que é um processo de compilação de código Lua e quando faz sentido usar essa técnica, com o programa luac.

Adendos

As seções a seguir tem relação com este tutorial, mas visam cenários de uso mais específicos ou podem não interessar a uma parte do público, então mantive em separado:

Conteúdo relacionado


  1. Também é comum usar o termo script (do inglês, roteiro), já que esse tipo de programa funciona como um passo a passo, composto por programas subjacentes, que sabemos para que servem, mas abstraímos seu funcionamento. 

  2. Para ser justo com o vi, ele é um editor bastante capaz, para a programação também, embora menos intuitivo. Exploro esse assunto no texto Simples casa com simples, no blog. 

  3. Talvez você já tenha visto o termo máquina virtual em outros contextos, possivelmente com significados um pouco diferentes. Não se trata aqui de emular um sistema operacional completo, e mesmo o comparativo com máquinas virtuais de outros ambientes de desenvolvimento seria potencialmente equivocado. Vide Máquina virtual, no glossário do Lu-a-Bá.