Trabalho linguagem de Programação Lua

Transcrição

Trabalho linguagem de Programação Lua
Estudo sobre a linguagem de programação Lua*
Bruno Lopes Dalmazo1, Francisco Tiago Avelar1
1
Curso de Ciência da Computação – Universidade Federal de Santa Maria (UFSM) – Santa Maria, RS – Brasil
{dalmazo, avelar}@inf.ufsm.br
Resumo. Este trabalho apresenta funcionalidades da linguagem brasileira de programacão Lua através de uma abordagem simples e informativa. Noções gerais de histórico demonstram seu propósito e seus domínios de aplicação exemplificam a aceitação do trabalho perante os outros países. Além disso, detalhes mais específicos são explicados de modo a apresentar ao profissional de computação a grande facilidade de programação sob a abordagem envolvendo os conceitos de linguagens de programação.
1. História do surgimento da linguagem
A Petrobrás, parceira da TeCGraf, grupo de Tecnologia em Computação gráfica da PUC­RJ, formado pelos professores Roberto Ierusalimschy, Waldemar Celes e Luiz Henrique Figueiredo, necessitava traçar um perfil de um poço de petróleo. Durante uma perfuração, junto com a broca, vários sensores descem em conjunto, para que os geólogos acompanhem o processo. Estes sensores recebem as informações à medida que o poço é perfurado. Os valores capturados pelos sensores eram colocados em um gráfico na tela do computador, com dados diversos, como temperatura, condutividade do material naquela profundidade, resistência e pressão. Os programas coletam estas informações e as manipulam para que o gráfico seja montado. Contudo, existem vários fatores complexos de configuração que o geólogo pode desejar visualizar. Às vezes, é preciso ver uma coluna do lado da outra; outras; há necessidade de verificar outro tipo de informação, mudar a cor ou a escala. A complexidade de realizar esse trabalhado era grande com o uso de uma única interface gráfica, pois sempre fica faltando algum detalhe. Sendo assim, quando o geólogo necessitava de mais comandos, era obrigado a contatar os desenvolvedores da aplicação para que o software fosse alterado ou para que fosse criado um novo programa, enviando em seguida para a plataforma. Desta forma foi criado o Lua, para colocar um final para esse problema. Com esta linguagem, alguém que tenha uma pequena formação técnica pode fazer, ele mesmo, essa configuração. A linguagem é simples o suficiente para que a pessoa possa modificar e * Trabalho Conceitos de Linguagem de Programação (ELC1007) – primeiro semestre 2007.
adaptar às suas necessidades. Assim, o programa é um pouco de linguagem de programação C, que cria a parte gráfica, e um pouco de Lua, que pode estar gravada em um arquivo texto, encarregado de dizer para o C como as informações são apresentadas.
2. Domínios de aplicação
A linguagem de programação Lua possui grande uso no domínio corporativo voltado para o mercado de jogos de computadores [WoW] e consoles de video­game [Psychonauts]. Além disso, é bastante empregada em aplicações Web e no desenvolvimento de ferramentas para auxiliar a pesquisa científica, como a Biologia Molecular [GUPPY]. Lua foi projetada para estender aplicações, sendo freqüentemente utilizada como uma linguagem de propósito geral. Dessa forma, diversos casos de uso podem ter Lua como parte da implementação de uma solução, seja para o domínio empresarial, científico e industrial. A comunidade de desenvolvimento em linguagem Lua é de domínio geral. Assim, qualquer interessado pode ter acesso a documentação e o código­
fonte da implementação da linguagem, pois Lua é software livre de código aberto. Demais exemplos de uso da linguagem Lua de forma a descrever seus domínios de aplicação podem ser vistos da Internet, através do website oficial, nas referências finais deste trabalho.
3. Variáveis e tipos de dados
As variáveis globais de Lua não precisam ser declaradas. Assim, é possível escrever o comando a = 2.3 sem necessidade prévia de declarar a variável global a. Uma característica fundamental de Lua é que as variáveis não têm tipo: os tipos estão associados aos dados armazenados na variável. Assim, o código acima armazena em a um dado do tipo numérico, com valor 2.3, enquanto, após o comando a = "Linguagem Lua" a variável a passa a armazenar um dado do tipo cadeia de caracteres (string). Os dados em Lua podem ser de oito tipos básicos: nil, lógico, number, string, function, userdata, thread e table.
3.1. Tipo nil
O tipo nil representa o valor indefinido. Todas as variáveis ainda não inicializadas assumem o valor nil. Assim, se o código: a = b for encontrado antes de qualquer atribuição à variável b, então esta é assumida como contendo o valor nil, significando que a também passa a armazenar nil, independentemente do valor anteriormente armazenado em a. A palavra reservada nil pode ser usada na programação para expressar o valor do tipo nil. Com isto, pode­se escrever: a = nil que atribui o valor nil à variável a (a partir deste ponto, é como se a variável a ainda não tivesse sido atribuída). Pode­se testar se uma variável foi inicializada comparando o seu valor com nil: a == nil
3.2. Tipo lógico
O tipo lógico, também conhecido como tipo booleano, pode assumir somente dois valores: verdadeiro (true) ou falso (false).
3.3. Tipo number
O tipo number representa valores numéricos. Lua não faz distinção entre valores numéricos com valores inteiros e reais. Todos os valores numéricos são tratados como sendo do tipo number. Assim, o código a = 4 b = 4.0 c = 0.4e1 d = 40e­1 armazena o valor numérico quatro nas variáveis a, b, c e d.
3.4. Tipo string
O tipo string representa cadeia de caracteres. Uma cadeia de caracateres em Lua é definida por uma seqüência de caracteres delimitadas por aspas simples (' ') ou duplas (" "). A seqüência de caracteres deve estar numa mesma linha de código. Por simplicidade, quando a cadeia de caracteres é delimitada por aspas duplas, pode­se usar aspas simples no seu conteúdo, sem necessidade de tratá­las como seqüências de escape. Entretanto, para reproduzir na cadeia de caracteres as aspas usadas como delimitadoras, é necessário usar os caracteres de escape. Assim, são válidas e equivalentes as seguintes atribuições: s = "Marca d'agua" s = 'Marca d\'agua' Colchetes duplos ([[ ]]) também podem ser utilizados como delimitadores de strings. Diferentemente dos delimitadores aspas simples e aspas duplas, esse delimitador não interpreta sequências de escape e tolera que a string tenha “quebras de linhas” e pode conter strings com delimitadores aninhados. Exemplo: s = [[Esse é um texto que atravessa mais de uma linha
e contém uma string aninhada: [[aninhada]] no final !]]
3.5. Tipo function
Funções em Lua são consideradas valores de primeira classe. Isto significa que funções podem ser armazenadas em variáveis, passadas como parâmetros para outras funções, ou retornadas como resultados. A definição de uma função equivale a atribuir a uma variável global o valor do código que executa a função (ver definição de escopo). Esta variável global passa a armazenar um dado do tipo function. Assim, adiantando um pouco a sintaxe de definição de funções, o trecho ilustrativo de código abaixo armazena na variável func1 um valor do tipo function: function func1 (...) ... end que pode posteriormente ser executada através de uma chamada de função: func1(...) 3.6. Tipo userdata
O tipo userdata permite armazenar numa variável de Lua um ponteiro qualquer de C. Este tipo corresponde ao tipo void* de C e somente pode ser atribuído ou comparado para igualdade a outro valor de mesmo tipo em Lua. Este tipo é muito útil para programadores de aplicação que fazem a ligação Lua­C, mas não é manipulado por usuários que programam somente em Lua, pois não é possível criar dados deste tipo diretamente em Lua.
O próposito de oferecer esse tipo de dado na linguagem Lua é oferecer integração com as demais linguagens, pois assim mantem o ideal de ser uma linguagem simples, facilmente estensível e de propósito geral.
3.7. Tipo thread
O tipo thread representa diferentes fluxos de execução, sendo utilizado para implementar subrotinas (coroutines). Não confundir Lua threads com as do sistema operacional. Lua suporta subrotinas em todos os sistemas, mesmo os que não possuem suporte a threads. A forma especial de coroutines na linguagem Lua também é chamada de multithreadings colaborativas, representando um fluxo independente de execução. Entretanto, diferentemente das threads em sistemas multithreads, uma coroutine somente encerra sua execução através da chamada explícita de função de entrega (yield function). Simplesmente invocando coroutine.create, uma coroutine é criada retornado um objeto do tipo thread de manipulação. A função não inicia sua execução nesse ponto de desenvolvimento do programa. Quando chamado pela primeira vez coroutine.resume, passando o argumento do tipo thread retornado por coroutine.create, a coroutine inicia sua execução na primeira linha da função principal. Os argumentos adicionais são passados juntamente com o tipo thread para a função da coroutine. Depois de iniciar a execução, permanece até terminar ou ocorre uma interrupção yield. Há somente duas maneiras de uma coroutine encerrar: normalmente, quando sua função principal retorna (explicitamente ou implicitamente após a última instrução); ou incorretamente, se há presença de erros. No primeiro caso, coroutine.create retorna true, além das variáveis retornadas pela função principal de coroutine. No caso de erros, coroutine.resume retorna false mais uma mensagem de erro. Ocorre yield através da chamada explícita de coroutine.yield. Acontecendo isso, o correspondente coroutine.resume retorna imediatamente, mesmo se um yield ocorre dentro de função aninhada, ou seja, não na função principal, mas na função diretamente ou indiretamente chamada pela função principal. No caso de yield, coroutine.resume também retorna true, além dos valores passados para coroutine.yield. Na próxima vez que a mesma coroutine é retomada, a execução continua do ponto onde ocorreu yield, com a chamada de coroutine.yield retornando quaisquer argumentos extras passados por coroutine.resume.
function func_secundaria (a) print("func_secundaria", a) return coroutine.yield(2 * a) end co_rotina= coroutine.create( function (a,b) print("co_rotina", a, b) local r = func_secundaria(a + 1) print("co_rotina", r) local r, s = coroutine.yield(a + b, a­b) print("co_rotina", r, s) return b, "fim" end) print("principal", coroutine.resume(co_rotina, 1, 10)) print("principal", coroutine.resume(co_rotina, “r”)) print("principal", coroutine.resume(co_rotina, “x”, “y”)) print("principal", coroutine.resume(co_rotina, “x”, “y”)) A saída da execucão do programa é representado abaixo:
co_rotina 1 10 func_sec 2 principal true 4 co_rotina r principal true 11 ­9 co_rotina x y principal true 10 fim principal false cannot resume dead coroutine 3.8. Tipo table
O tipo table (tabela) é o tipo mais expressivo da linguagem. Este tipo implementa os chamados vetores associativos, que permitem indexação por valores de qualquer outro tipo, com exceção do tipo nil. As tabelas em Lua permitem a construção de vetores convencionais, listas e records numa mesma estrutura. Tabelas devem ser explicitamente criadas. Adiantando a sintaxe de criação de uma tabela, o código a = { } cria uma tabela vazia e armazena em a este valor. Novos campos podem ser adicionados posteriormente a esta tabela.
A partir de table, é possível implementar diversas estruturas de dados, como arrays comuns, tabela de símbolos, conjuntos, registros, grafos, árvores, etc.
4. Comandos de controle
Lua possui os mecanismos usuais para controle de fluxo. Estes controladores agrupam diversos comandos em blocos que podem ou não serem executados uma ou mais vezes. Lua também permite a declaração de variáveis locais. Estas variáveis deixam de existir ao final do bloco onde foram declaradas.
4.1. Estrutura If
O comando de decisão básico de Lua é o if, sendo representado por uma das formas abaixo:
if expr then bloco end ou if expr then bloco1... else bloco2... end ou ainda if expr1 then bloco1 elseif expr2 then bloco2 ... elseif expr N then bloco N else bloco N+1 end
Na primeira forma, o bloco de comandos representado por bloco é executado se a expressão expr produzir um valor diferente de nil. Na segunda forma, bloco2 será executado se expr produzir o valor nil. Caso contrário, bloco1 será executado. A terceira forma ilustra a possibilidade de tomada de decisão entre diversas alternativas usando­se o comando if­then­elseif­then­...­else­end. No código ilustrado, bloco1 é executado se expr1 produzir um valor verdadeiro (diferente de nil), bloco2 é executado de expr2 for verdadeiro, ..., e bloco N+1 é executado se todas as expressões forem falsas (i.e., iguais a nil).
4.2. Estrutura while (tomadas de decisão no início)
A instrução while permite que a tomada de decisão (se os comandos do laço devem ou não ser executados) seja feita no início do bloco. Sua forma geral é: while expr do bloco end isto é, enquanto a expressão expr produzir um valor verdadeiro (diferente de nil), os comandos do bloco são executados. Para exemplificar, considere o código abaixo que calcula o fatorial de um número (assumido como inteiro positivo) armazenado na variável n: f = 1 ­­ valor do fatorial i = n ­­ controle do laço while i > 0 do f = f * i i = i – 1 end Ao final da execução do código acima, f armazena o valor do fatorial de n e i armazena o valor zero (condição de fim de execução do laço).
4.3. Estrutura repeat (tomadas de decisão no final)
A construção de um laço com tomada de decisão no fim pode ser feita através do comando repeat. Sua forma geral é: repeat bloco until expr Nesta construção, o bloco de comandos é executado pelo menos uma vez (pois o teste de decisão só ocorre no final do laço) e é repetido até se alcançar o valor verdadeiro para a expressão. O mesmo exemplo de cálculo de fatorial pode ser escrito: f = 1 ­­ valor do fatorial i = 1 ­­ controle do laço repeat f = f * i i = i + 1 until i > n
4.4. Estrutura for
A estrutura for apresenta duas formas: uma numérica e outra genérica. A forma numérica repete as instruções do bloco numa progressão aritmética definida como primeiro valor a atribuição numérica exp1 em var, com limite máximo exp2 numa razão exp3. No caso da ausência exp3, o incremento padrão é unitário.
for var = exp1, exp2 [, exp3] do
bloco end
A declaração do for genérico trabalha sobre funções, conhecidos como iteradores. A cada iteração, a função de iteração é chamada para produzir um novo valor, encerrando quando nil é encontrado. O laço genérico é representado de acordo com a sintaxe
for namelist in explist do bloco end
ou de forma mais representativa,
for var_1, ∙∙∙, var_n in explist do
bloco
end
Conforme foi definido na seção 3.8, a estrutura table é um tipo primitivo em Lua com comportamento dinâmico. O exemplo abaixo implementa um percurso numa tabela para procurar um determinado valor na estrutura, retornando verdadeiro ou falso como resultado na busca de um determinado valor no interior da tabela. Através do código, é simples de perceber como os valores inicializados da tabela representam tipos distintos de dados.
­­ devo declarar explicitamente uma tabela local local tabela = {} function func() ­­ funcao func() eh uma variavel ­­ que sera inserida na tabela end tabela = {5, false, 3.14, func, nil, "texto"} function existe_valor(valor) for k, v in pairs(tabela) do ­­ 'for' genérico
if v == valor then return true end end return false end parametro = "texto" ­­ apenas atribui o que será procurado if( existe_valor(parametro) ) then io.write( tostring(parametro).." 'encontrado." ) else io.write( tostring(parametro).." ' NAO encontrado." ) end io.write("\n")
5.
Subprogramas (funções)
Conforme foi dito na seção 3.5, a linguagem Lua trata funções como valores de primeira classe. Quando definimos uma função em Lua, o armazenando é feito na variável global cujo nome corresponde ao nome da função o código “de máquina” interno da função, que pode posteriormente ser executado através de chamadas para a função.
Funções em Lua podem ser definidas em qualquer lugar do ambiente global. A forma básica para definição de funções é function nome ( [lista­de­parâmetros] ) bloco de comandos end
onde nome representa a variável global que armazenará a função. Pode­se fornecer uma lista de parâmetros que são tratados como variáveis locais da função e inicializados pelos valores dos argumentos na chamada da função. A chamada da função segue a forma básica nome ( [lista­de­argumentos] )
isto é, o nome da função é seguido de abre e fecha parênteses, que pode ou não conter uma lista de argumentos. Se nome não for uma variável que armazena um valor de função (i.e., do tipo function), Lua reporta um erro de execução (TAG METHODS). Se presente, a lista de argumentos é avaliada antes da chamada da função. Posteriormente, os valores dos argumentos são ajustados para a atribuição dos parâmetros seguindo uma regra de atribuição múltipla na qual os parâmetros recebem os valores dos argumentos. Lua passa parâmetros por valor. Isto quer dizer que quando se altera o valor de um parâmetro dentro de uma função, altera­se apenas o valor da variável local correspondente ao parâmetro. O valor da variável passada como argumento na chamada da função permanece inalterado. Funções em Lua podem retornar zero, um ou mais valores através do comando return. A possibilidade de retorno múltiplo evita a necessidade de passagem de parâmetros por referência. Quando, durante a execução de uma função, encontra­se o comando return, a execução da função é terminada e o controle volta para o ponto imediatamente posterior à chamada da função. O comando return pode vir seguido de uma lista de expressões; sua forma geral é
return [lista­de­expressões]
Por razões sintáticas, o comando return deve sempre ser o último comando de um bloco de comandos. Deste modo, evita­se a ocorrência de comandos inalcançáveis (tendo em vista que comandos após return nunca serão executados). Se return não for o último comando do bloco, Lua reporta um erro de sintaxe. Os valores retornados por uma função são ajustados para a atribuição na linha que faz a chamada. Para exemplificar, supõe­se uma função que incrementa os valores de um ponto cartesiano (x, y): function translada (x, y, dx, dy) return x+dx, y+dy end Considera­se, agora, que a chamada da função é feita por: a, b = translada(20, 30, 1, 2) Assim, a recebe o valor 21 (= 20 + 1) e b recebe o valor 32 (= 30 + 2). Se a chamada fosse a = translada(20, 30, 1, 2) então o segundo valor retornado seria desprezado, e a receberia o valor 21. Por outro lado, a atribuição: a, b, c = translada(20, 30, 1, 2) faria a = 21, b = 32 e c = nil.
É importante notar que uma chamada de função que retorna múltiplos valores não pode estar no meio de uma lista de expressões. Por razões de ajustes de valores em atribuições múltiplas, se uma função estiver sendo chamada no meio de uma lista de expressões, só se considera o primeiro valor retornado. Para exemplificar, duas situações sutilmente diferentes são analisadas; considera­se inicialmente a chamada de função abaixo: a, b, c = 10, translada(20, 30, 1, 2) que faz com que a receba o valor 10, b o valor 21 e c o valor 32. Neste caso, o funcionamento é o esperado, tendo em vista que a chamada da função é a última expressão numa lista de expressões. No entanto, se o código fosse a, b, c = translada(20, 30, 1, 2), 10 então teria­se resultados incorretos, já que a chamada da função está no meio de uma lista de expressões. Conforme mencionado, independentemente do número de valores retornados pela função, considera­se apenas o primeiro (no caso, o valor 21). O valor 32 retornado pela função é perdido durante o ajuste. Assim, teríamos como resultado o valor 21 armazenado em a e o valor 10 armazenado em b. A variável c, então, receberia o valor nil, pois não se tem outra expressão após a constante 10. É possível definir funções em Lua que aceitem número variável de argumentos. Para tanto é necessário acrescentar à lista de argumentos da definição a indicação “...” (três pontos seguidos). Uma função definida dessa forma não faz ajuste do número de parâmetros em relação ao número de argumentos. Ao invés disso, quando a função é chamada os argumentos extras são colocados em um parâmetro implícito de nome arg. Esse parâmetro é sempre inicializado como uma tabela, onde o campo n contém o número de argumentos extras e os campos 1, 2, ... os valores dos argumentos, seqüencialmente. Para exemplificar, duas diferentes definições de funções são apresentadas: function f(a, b) end function g(a, b, ...) end Os seguintes mapeamentos de argumentos a parâmetros seriam efetuados na chamada dessas funções:
Chamada
Parâmetros
f(3)
a = 3, b = nil
f(3, 4)
a = 3, b = 4
f(3, 4, 5)
a = 3, b = 4
g(3)
a = 3, b = nil, arg = { n = 0 }
g(3, 4)
a = 3, b = 4, arg = { n = 0 }
g(3, 4, 5, 8)
a = 3, b = 4, arg = { 5, 8; n = 2 }
6.
Subprogramas sobrecarregados e genéricos.
A linguagem Lua não apresenta declaração formal para tipos de dados, conforme mencionado na seção 3. Dessa forma, subprogramas possuem comportamento genérico quando a aceitabilidade de dados de entrada e retorno de valores. Por outro lado, em se tratando de sobrecarga de operadores na linguagem Lua, uma estrutura interessante, chamada metatable, foi criada para permitir maior flexibilidade no desenvolvimento de programas.
Elemento presente em qualquer variável na linguagem Lua, um metatable é uma table comum cuja função é definir o comportamento do valor original perante certas operações. É possível efetuar diversas alterações do comportamento das operações sobre o valor através de ajustes dos campos específicos na metatable. Por exemplo, quando um valor não­numérico é operando de uma adição, Lua verifica a função no campo "__add" na sua metatable. Ao encontrar alguma, ocorre a invocação dessa função para desempenhar a adição. É possível consultar eventos de um metatable e os metamétodos de qualquer valor através da função getmetatable. No caso anterior, o evento é "add" para o metamétodo da função que efetua a adição. No caso de substituir um metatable em tabelas, passa a ser usado a função setmetatable. Tables e userdata possuem metatables individuais (apesar que múltiplas tabelas e userdata compartilham suas metatables), ao passo que todos os outros tipos compartilham um único metatable por tipo. Desse modo, há uma única metatable para todos os numbers, uma para todas as strings, etc. Um metatable pode controlar o comportamento de um objeto em operações aritméticas, comparações, concatenação, cardinalidade, indexação, etc. Além disso, pode definir uma função para ser chamada quando um userdata é coletado para lixo. Para cada uma dessas operações, Lua associa uma chave específica denominado evento. Ao desempenhar uma dessas operações sobre um valor, Lua verifica caso esse valor possui uma metatable com o evento correspondente. Em caso afirmativo, o valor associado com aquela chave (o metamétodo) controla como a linguagem irá efetuar a operação. Cada operação é identifidada pelo nome correspondente. A chave é uma string com o nome prefixado de duas sublinhas, '__', por exemplo. Desse modo, a chave para a operação de adição é a string "__add". A função getbinhandler abaixo define como Lua escolhe um manipulador para uma operação de binária. Primeiramente, o primeiro operando é verificado, caso seu tipo não define um manipulador para a operação, o segundo operando é analisado. function getbinhandler (op1, op2, event) return metatable(op1)[event] or metatable(op2)[event] end
A tabela segue com a representação string dos operadores em Lua que possuem a capacidade de sobrecarga através de metatables.
Nome
Descrição
Símbolo
“add”
adição
+
“sub”
subtração
­
“mul”
multiplicação
*
“div”
divisão
/
“mod”
resto da divisão
%
“pow”
exponenciação
^
“unm”
unário
­
“concat”
concatenação
..
“len”
cardinalidade
#
“eq”
equivalência
==
“lt”1
menor a
<
“le”2
menor ou igual a
<=
“index”
acesso indexado
table[key]
“newindex”
atribuição de indexação
table[key] = value
“call”
chamada de valor
ausente
O exemplo abaixo representa o uso de metatable para o operador de adição de forma a ser sobrecarregado através da atribuição de um manipulador m.
1 a>b é equivalente a b<a
2 a>=b é equivalente a b<=a. Notar que, na ausência do metamétodo “le”, Lua tenta “lt”, assumindo que a<=b é equivalente a not(b < a)
function evento_add (op1, op2) local o1, o2 = tonumber(op1), tonumber(op2) if o1 and o2 then ­­ ambos operandos são numéricos? return o1 + o2 ­­ '+' primitiva 'add' else ­­ pelo menos um não é numérico
local m = getbinhandler(op1, op2, "__add") if m then ­­ invoca o manipulador com os dois operandos
return m(op1, op2) else ­­ não há manipulador: comportamento padrão
error(∙∙∙) end end end
7.
Escopo (regras de visibilidade)
Lua é uma linguagem com escopo léxico de variáveis, iniciando na primeira especificação após as declarações e permanece até o final do bloco mais interno que inclui a declaração. Considerando o exemplo abaixo, fica simples de deduzir.
x = 10
do
local x = x
print(x)
x = x+1 do
local x = x+1
print(x)
end print(x)
end print(x)
­­ variável global
­­ novo bloco
­­ novo 'x', com valor 10 ­­ imprime 10
­­ outro bloco
­­ outro 'x' ­­ imprime 12 ­­ imprime 11 ­­ imprime 10 (variável global)
Lua permite que se defina explicitamente variáveis de escopo local. A declaração de uma variável local pode ocorrer em qualquer lugar dentro de um bloco de comandos, e seu escopo termina quando termina o bloco no qual foi declarada. A declaração de uma variável local com mesmo nome de uma variável global obscurece temporariamente (i.e., dentro do bloco da declaração local) o acesso à variável global. Quando o programador escrever o nome da variável, estará se referindo à variável local. Variáveis locais podem ser inicializadas na declaração seguindo a sintaxe de atribuições. Para exemplificar, considere o código abaixo: a = 2 ­­ variável global igual a 2 if a > 0 then local b = a ­­ variável local 'b' com atribuição 2
a = a + 1
­­ incrementa a variável global 'a'
­­ de uma unidade local a = b ­­ declara­se uma variável local 'a'
­­ que recebe o valor de 'b' print(a) ­­ a refere­se a variável local, ­­ logo imprime o valor 2 end ­­ fim do bloco e do escopo de ­­ 'a' e 'b' locais print(a) ­­ 'a' refere­se à variável global, logo
­­ imprime o valor 3 Pode­se ainda declarar e inicializar várias variáveis locais num mesmo comando: local a, b, c = 2, 5+6, ­3 Neste caso, a recebe 2, b recebe 11 e c recebe ­3. Variáveis locais não inicializadas assumem o valor nil.
8.
Acesso a ambientes não locais
Na linguagem Lua, funções são variáveis locais tratadas como qualquer outro tipo de dado. Desse modo, quando ocorre o caso de funções estarem definidas no interior de outras, um mecanismo para acessar variáveis em diferentes níveis de programa criou o conceito de encerramento de variáveis e funções não­globais. Nas subseções seguintes, ambos os casos são verificados.
8.1. Closures (“encerramentos”)
Quando uma função é escrita no interior de outra, a função incluída passa a ter acesso completo a todas as variáveis locais da função detentora; funcionalidade conhecida como escopo léxico. Embora pareça simples, às vezes não é tão óbvia quanto seja. O pequeno código abaixo ilustra o conceito simples envolvendo closures. Suponha uma lista de estudantes e as notas ambos armazenados numa table. O objetivo é ordenar a lista de nomes de acordo com as notas em ordem decrescente. nomes = {"Pablo", "Paula", "Mariana"} notas = {Mariana = 10, Paula = 7, Pablo = 8} table.sort(nomes, function (n1, n2) return notas[n1] > notas[n2] ­­ compara as notas end) Agora, suponha que o código que contém a função que desempenha a tarefa de ordenação dos nomes de alunos através da nota maior até a nota menor:
function ordena_por_nota(nomes, notas) table.sort(nomes, function (n1, n2) return notas[n1] > notas[n2] ­­ compara as notas end) end O exemplo demonstra que a função anônima interna a sort acessa o parâmetro notas, o qual é local (parâmetro formal) na função ordena_por_nota. No interior na função anônima, notas não é nem uma variável global e tanto como não é uma variável local. O conceito envolvido é denominado variável local externa, ou upvalue3. Qual o diferencial dessa idéia? O motivo é devido às funções serem variáveis de primeira classe. Considere o código: function novo_contador() local i = 0 return function() ­­ função anônima i = i + 1 return i end end c1 = novo_contador() print( c1() ) ­­> 1 print( c1() ) ­­> 2 A função anônima utiliza um upvalue, i, para manter seu contador. Entretanto, na ocasião de invocação da função anônima, i está fora de escopo, pois a função que criou a variável (novo_contador) encerrou com o retorno. Apesar disso, Lua gerencia a situação corretamente através do conceito de closures. Em outras palavras, uma closure é uma função incluindo todas as características para acessar upvalues de forma correta. Ao invocar novo_contador novamente, uma nova variável local i será criada, assim como uma nova closure, atuando sobre a nova variável: c2 = novo_contador() print( c2() ) ­­> 1 print( c1() ) ­­> 3 print( c2() ) ­­> 2 Como se pode concluir, c1 e c2 são diferentes closures sobre a mesma função e cada ação atua sob instanciação independente da variável local i. Mais especificamente, todo valor em Lua é uma closure, não uma função. Por si só, função é apenas um protótipo para closures. 3 O termo "upvalue" é um tanto enganoso, pois notas é uma variável, não um valor. Entretanto, esse termo possui raízes históricas da linguagem Lua e também é uma forma de abreviatura para "variável local externa".
Através do mecanismo de closures, programas em Lua incorporam técnicas de programação personalizáveis, como uso de callbacks, muito úteis para aplicativos GUI. Outro uso de closures justifica a grande flexibilidade de Lua, pois funções são variáveis regulares e podem ser facilmente redefinidas. Por exemplo, suponha que queremos redefinir a função seno para operar com ângulos em graus ao invés de arcos em radianos. Essa nova função deve converter seu argumento e então chamar a função seno original para efetuar o trabalho real. Considere o código: seno_velho = math.sin math.sin = function (x) return seno_velho(x * math.pi/180) end Um exemplo de uso da função está abaixo :
do local seno_velho = math.sin local l = math.pi/180 math.sin = function (x) return seno_velho(x * l) end end A idéia foi manter a versão antiga numa variável privada. O único caminho e acesso é através da nova versão. A partir do conceito de redefinição de funções, é possível utilizar a mesma característica para criar ambientes seguros, também conhecidos como sandboxes ("caixas de areia"). Segurança é essencial quando há execução de código não confiável, como um trecho de programa recebido através da Internet por um servidor. Por exemplo, para restringir acesso aos arquivos por um programa "desconhecido", a função open, da biblioteca io, pode ser redefinida utilizando closures: do local velho_open = io.open io.open = function (nome_arq, modo) if access_OK(nome_arq, modo) then return velho_open(nome_arq, modo) else return nil, "acesso negado" end end end O que torna esse exemplo interessante é que, após a redefinição, não há mais um modo do programa invocar o open irrestrito, exceto através da nova e restrita versão. O resultado foi manter a versão insegura como uma variável privada numa closure, inacessível ao exterior. Com essa facilidade, é possível construir sandboxes até mesmo em Lua, aproveitando o benefício usual: flexibilidade. Dessa forma, ao invés de soluções únicas gerais, Lua oferece meta­mecanismos para que possa adequar o ambiente para necessidades específicas de segurança.
8.2. Funções não­globais.
Uma evidente conseqüência das funções de primeira classe é que é possível armazenar funções não somente em variáveis globais, mas também em tables e em variáveis locais. Quando uma função é armazenada numa variável local, a função será local, ou seja, restrita a um dado escopo. Tal definição é particularmente útil para pacotes: Lua manipula cada chunk4 como uma função, uma chunk pode declarar funções locais, as quais são somente visíveis no interior de um chunk. O escopo léxico garante que outras funções no pacote podem usar essas funções locais: local f = function (...) ... end local g = function (...) ­­ igual a local funcion g(...) ... f() ­­ função externa 'f()' visível aqui ... end Uma questão importante ocorre na definição de recursividade em funções locais. A abordagem primária abaixo não irá funcionar: local fatorial = function (n) if n == 0 then return 1 else return n * fatorial(n­1) ­­ ocorre erro end end Quando Lua compila a chamada fatorial fatorial(n­1), no corpo da função, o fatorial local ainda não foi definido. Portanto, a expressão do código onde ocorre erro invoca a global fatorial, não a local. Para resolver esse problema, é necessário definir a variável local e então criar a função: 4 Cada porção de código em Lua pode ser chamado de chunk, tal como um arquivo ou uma única linha de comando. Mais especificamente, um chunk é simplesmente uma seqüência de declarações. Veja nas referências para saber mais.
local fatorial fatorial = function (n) if n == 0 then return 1 else return n*fatorial(n­1) end end Agora fatorial no interior da função faz referência a variável local. Seu valor não importa quando a função é definida, pois em tempo de execução, fatorial já possui o valor correto. Uma maneira expansiva para funções locais possui uma forma alternativa para uso de recursividade: local function fatorial (n) if n == 0 then return 1 else return n * fatorial(n ­ 1) end end Por outro lado, esse recurso não funciona quando ocorre recursividade de forma indireta. Nesses casos, é preciso usar a declaração explícita:
local f, g ­­ declaração explícita function g () ... f() ... end function f () ... g() ... end 9.
Conclusões
O estudo envolvendo conceitos de linguagens de programação representa o requisito básico para o compreendimento das entidades e elementos de linguagens de forma a diferenciar os diversos própositos da criação de linguagens, visto que não existe uma soluçãoo universalmente utilizada que atenda todos os requisitos no desenvolvimento de sistemas em computação. Em termos de evolução, países desenvolvidos iniciaram a pesquisa na criação de linguagens de programação, regendo técnicas e paradigmas para desenvolvimento de programas. Resultados consagrados na criação de linguagens de programação são fundamentados na capacidade simplificada no uso e expressão dos mecanismos para desenvolvimento de programas, além de ferramentas envolvendo o apoio para correção de erros (debug) e interfaces integradas de desenvolvimento, nos quais são recursos essenciais para qualquer linguagem moderna para programação.
A linguagem de programação Lua demonstrou possuir grandes características reunidas de soluções existentes no desenvolvimento de linguagens e sustena os paradigmas de programação estudados formalmente em Ciência da Computação. Além disso, incorpora funcionalidades próprias, como a alta flexibilidade no desenvolvimento de código, gerência automática de memória e a integração com demais sistemas. Em outras palavras, a linguagem não recria os mecanismos existentes das linguagens consagradas de programação. A aceitação de Lua por diversos programadores ao redor do mundo está na simplificidade de código aliado a magnitude para implementação de programas.
O presente trabalho comprova que pesquisadores e profissionais brasileiros em Ciência da Computação possuem grande capacidade na criação de linguagens e sistemas de forma a contribuir significativamente para facilitar a solução de muitos problemas do mundo moderno, oferecendo meios facilitadores de expressão entre Homem e máquina, além de técnicas que ofereçam desempenho aceitável e clareza no tratamento de erros.
Referências
WoW – World of Warcraft ­ http://www.worldofwarcraft.com/
Psychonauts ­ Double Fine Productions ­ http://www.doublefine.com/
GUPPY ­ genetic sequence visualization . Computational Biology Research Center, AIST Tokyo. http://staff.aist.go.jp/yutaka.ueno/guppy/
Aplicações da linguagem Lua: http://www.lua.org/uses.html
Página oficial da Linguagem Lua: http://www.lua.org/
Livro on­line: http://www.lua.org/pil/
Manual do programador: http://www.lua.org/manual/5.1/
Lua chunks: http://www.lua.org/pil/1.1.html
Artigo que recebeu o primeiro prêmio (categoria tecnologia) no II Prêmio Compaq de Pesquisa e Desenvolvimento em Ciência da Computação em 1997: http://www.lua.org/spe.html
Artigo envolvendo corotinas em Lua:
http://www.inf.puc­rio.br/~roberto/docs/corosblp.pdf
Página geral de papers sobre Lua:
http://www.lua.org/papers.html