Executando código Lua
Existem várias maneiras de executar código Lua. Neste tutorial, veremos algumas delas. Não cabe explorar todas, porque ainda estamos em um tutorial iniciante, mas veremos as principais.
Nossa principal ferramenta será o interpretador lua
, que usamos para
executar código Lua. Existem diferentes métodos pelos quais podemos
transmitir código para ele.
Por enquanto, veremos 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
Um dos principais facilitadores para o aprendizado da linguagem Lua é justamente a sua natureza dinâmica, no sentido de que podemos usar um interpretador para ler um comando e então observarmos os resultados antes de prosseguir para o próximo comando.
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) indica que o interpretador
está à espera de um próximo comando. Ao inserir o comando, o interpretador
vai executá-lo, podendo ou não ter alguma saída na tela.
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
Este comando é uma chamada à função print
que serve para exibir o valor
informado na tela. No entanto, o interpretador lua
nos oferece outra maneira
de exibir algo na tela. Ao informar uma expressão qualquer que seja, o
seu resultado será exibido (como se tivéssemos usado print
).
O texto que usamos no exemplo anterior é uma expressão por si só, que
resulta no próprio texto. Veja que textos devem ser informados entre aspas
(duplas ou simples). A expressão 3
resulta no próprio número 3. Podemos
também trabalhar com expressões aritméticas, como 2 + 2
, por exemplo.
> "Estou na Lua"
Estou na Lua
> 3
3
> 2 + 2
4
Esse comportamento (retornar o resultado de uma expressão) nem sempre funcionou dessa maneira. Até a versão 5.2 era necessário inserir um sinal de igual antes da expressão (o que ainda funciona nas versões mais novas). A partir da versão 5.3, isso deixou de ser necessário.
> = 2 + 2
4
Esse sinal de igual (ou sua ausência, a partir de Lua 5.3) equivale a um
comando de retorno (return
) da linguagem Lua, o que significa um comando
como “retorne o resultado desta expressão”.
> return 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 inserir expressões mais complexas e até mesmo utilizar variáveis.
> i = 3
> 2 * (i + 14) + i * i
43
Note que o primeiro comando não emite nenhuma saída. Logo você verá que alguns comandos não possuem retorno, e o comando de atribuição de variáveis é um deles.
Na realidade, existe uma diferença entre comandos e expressões. Talvez não
pareça assim, porque o interpretador lua
encapsula expressões em um
comando (return
) que retorna o resultado da expressão.
Ou seja, na primeira linha, informamos um comando (de atribuição), e na segunda linha informamos uma expressão, que foi automaticamente encapsulada em um comando de retorno, e que portanto possui uma saída.
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
Veja que não foi necessário nem mesmo separar a atribuição de i
da chamada à
função print
. 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.
E a propósito, não subestime esse método de desenvolvimento interativo. 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 programar, 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 recursos mais voltados para a programação, como destaque da sintaxe e definição de comandos para execução do programa.
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.
Quando trabalhamos com arquivos-fonte, um fator que precisamos sempre considerar é o modo como o texto do arquivo é codificado binariamente. Existem vários esquemas de codificação, como ASCII, EBCDIC, UTF-8, UTF-16, ISO-8859-1, e vários outros.
Quando o arquivo usa uma codificação diferente daquela esperada pelo terminal, podemos notar alguns erros na exibição do texto, o que é típico quando usamos caracteres acentuados ou cedilha.
Não cabe detalhar muito mais esse assunto neste tutorial introdutório, mas voltaremos a isso em outra oportunidade.
Por enquanto, apenas tenha ciência de que a depender da codificação do arquivo e do terminal usados, esse tipo de problema poderá se manifestar. Ignore-o por enquanto, e se necessário remova a acentuação ou cedilha.
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).
Vamos discutir outro ponto sobre a execução de arquivos-fonte: no exemplo
anterior chamamos o interpretador lua
passando o arquivo como argumento,
certo?
Em sistemas POSIX, temos uma outra opção, que consiste em adicionar ao arquivo, logo na primeira linha um apontamento2 para o interpretador, como no exemplo a seguir:
#!/usr/bin/lua
Neste caso, estamos informando já no próprio arquivo o caminho completo para o
programa lua
, que neste exemplo está dentro de /usr/bin/
. Porém,
isto pode variar, dependendo de onde Lua foi instalado no sistema3.
Ao adicionar essa linha e tornar o arquivo executável, podemos chamar o arquivo diretamente para execução, conforme a seguir:
$ ./astronauta.lua
Estou na Lua
Sinto falta da Terra
Evidentemente, depender desse apontamento afeta a portabilidade do programa,
pois se mudar para um sistema onde a localização do interpretador lua
for
outra (ou o nome utilizado for outro), essa forma de execução deixará de
funcionar.
Uma mitigação comum para esse problema é usar o comando env
nessa linha, que
é uma forma indireta de chamar algum programa (como lua
). Esse é um recurso
bastante comum, pois no geral sistemas POSIX possuem esse comando no mesmo
local. Veja como ficaria:
#!/usr/bin/env lua
Caso não esteja em um sistema POSIX ou caso prefira executar o arquivo
chamando o programa lua
explicitamente, essa linha inicial não surtirá
nenhum efeito, então não há nenhum problema em mantê-la nesses casos.
Ao longo dos tutoriais, omitirei essa instrução, mas apenas saiba que você poderá se deparar com ela em alguns arquivos Lua, especialmente em sistemas POSIX.
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. Podemos
ainda criar comentários de bloco aninhados, isto é, um comentário de
bloco dentro de outro. Vejamos:
--[[
Este é um comentário de bloco mais externo
--[-[
E este é outro comentário de bloco
mais interno. Note que ele possui um hífen
a mais entre os sinais de colchetes para
diferenciar do bloco externo.
--]-]
--]]
Agora, você talvez esteja se perguntando, “qual seria a serventia de comentários aninhados, se no fim tudo são comentários”? Realmente, não serve muito se você não precisar mexer nos comentários. Mas será útil, se vez ou outra precisar descomentar o bloco mais externo.
Vejamos um cenário mais útil:
--[[
i = 3
--[-[
Não estou certo se quero que i seja igual a 3,
então por enquanto essa atribuição está dentro
de um comentário.
--]-]
--]]
i = i or 5 -- se i não possui valor, será 5
print(i) --> 5
Muito bem, nesse exemplo, temos um comando de atribuição dentro do bloco mais externo. Por enquanto não o utilizamos, mas podemos mudar de ideia no futuro.
Há um pequeno truque com comentários de bloco que podemos utilizar: ao adicionar mais um hífen ao início do delimitador de abertura do bloco, o bloco se desfaz, pois o início e o fim do bloco se transformam em comentários de linha comuns.
Veja como fica:
---[[
i = 3
--[-[
...
--]-]
--]]
i = i or 5 -- se i não possui valor, será 5
-- (mas agora possui, 3)
print(i) --> 3
Percebeu a mudança na primeira linha? Agora a atribuição de i
funciona, pois
o bloco mais externo foi desativado.
Chamando arquivo em código Lua
Lembra-se quando eu disse que podemos combinar o método interativo com o uso
de arquivos-fonte? Pois bem, agora veremos como. Em Lua, existe uma função
chamada dofile
, a qual 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
Observaremos esse resultado (as mensagens emitidas) tantas vezes quantas
reexecutarmos a chamada da função dofile
com o arquivo astronauta.lua
.
Esse recurso é bastante útil quando queremos salvar alguns trechos de código para utilização posterior no modo interativo. É comum incluir nesses arquivos algumas atribuições de variáveis e definição de funções (algo que ainda vamos explorar mais em outro tutorial).
Também 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
, passando o
nome do arquivo como valor.
Isto fará com que ele seja executado antes de entrar no modo interativo.
Vejamos um exemplo com o nosso já conhecido astronauta.lua
:
$ lua -i astronauta.lua
Estou na Lua
Sinto falta da Terra
>
Perceba que o código do programa astronauta.lua
é executado primeiro,
emitindo as mensagens, e logo em seguida o prompt do interpretador Lua é
aberto para inserir novos comandos.
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.
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 virtual4, 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:
- Interativamente, chamando o interpretador
lua
sem parâmetros; - Via arquivos-fonte, passados como argumento para o interpretador;
- Como argumentos de linha de comando para o interpretador, usando a
opção
-e
.
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
.
Conteúdo relacionado
- Curadoria – Lua – Conceitos básicos e API C
- Curadoria – Uma introdução à programação em Lua
- Glossário – Bloco
- Glossário – Compilação Lua
- Glossário – Distribuição padrão
- Glossário – Interpretador Lua
- Glossário – Máquina virtual
- Glossário – Trecho
-
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. ↩
-
Essa forma de apontamento (uma linha inicial na qual inserimos os caracteres
#!
seguidos de um caminho) usualmente é chamada pelo termo shebang. ↩ -
Em alguns casos, até o nome do programa pode ser modificado durante a instalação. Tipicamente, isto é feito para manter versões simultâneas de Lua no mesmo sistema. Nesses casos, é comum que o interpretador possua a versão em seu nome, como
lua5.3
elua5.4
, por exemplo. ↩ -
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á. ↩