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. 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:

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


  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. Essa forma de apontamento (uma linha inicial na qual inserimos os caracteres #! seguidos de um caminho) usualmente é chamada pelo termo shebang

  3. 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 e lua5.4, por exemplo. 

  4. 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á.