Atenção: esse artigo tem uma palestra complementar. Clique e assista!
Metaprogramação pode ter vários significados ou focos. Neste artigo estudaremos alguns componentes para interpretar código pascal. A ideia é criar uma maneira de disponibilizar ao usuário uma interface onde ele possa “programar” suas próprias rotinas e que estas possam até interagir com elementos do nosso software.
Para que serve
Na perspectiva do desenvolvedor, a metaprogramação serviria para economizar trabalho e linhas de código, tornar o sistema maleável ou mutável e, caso se use a metaprogramação para gerar código, este seria menos sujeito a erros porque foi gerado automaticamente. Do ponto de vista do usuário a metaprogramação significa uma maneira de economizar dinheiro, pois dependendo da alteração que ele precise no sistema ele mesmo pode fazê-la.
Em que situação o tema é útil
Este tema é útil em situações onde o custo para se alterar um software especificamente para uma empresa é alto demais tanto para o desenvolvedor como para a empresa, e ainda tornaria o sistema específico e invendável para uma empresa de ramo diferente. Podemos usar esse conhecimento em softwares que vão ser vendidos para uma gama muito diversificada de empresas, e essas possuem regras muito diferentes para cálculos de custos, insumos, impostos e comissões. Também é útil para o administrador do sistema, poder criar Plug-ins ou Workflows específicos para o seu caso.
Resumo do DevMan
Vamos explicar basicamente o que é metaprogramação e as diferentes abordagens que esse assunto pode ter. Citaremos alguns exemplos onde o leitor já usa metaprogramação e talvez não saiba. Vamos também criar ou simular situações onde seria necessário um interpretador de código, e criaremos oportunidades para o usuário do sistema programar.
Metaprogramação é uma palavra abrangente que pode ter diversos significados. Pode ser, por exemplo, a geração de código em uma linguagem qualquer. Se você tem um programa que gere código fonte para ser compilado por outro programa, isso é um caso de metaprogramação.
Um exemplo bastante conhecido por nós são os Templates e Wizards do Delphi que nos ajudam a “digitar” certas porções de código. Um exemplo mais avançado seriam ferramentas como o Model Maker e o ECO ou qualquer outra ferramenta que gere código para nós.
Um outro exemplo de metaprogramação são os próprios compiladores, de qualquer linguagem, porque eles têm como objetivo gerar código de máquina a partir de um código fonte. O código binário resultante reflete as intenções do código fonte, portanto a criação de um compilador é um caso avançado de metaprogramação.
Alguns conceitos abordados aqui precisarão de explicação. Programa fonte é o nome que damos ao código fonte propriamente dito. Programa objeto seria o programa compilado. Linguagem de programação é a linguagem que usamos para criar o código fonte de um programa.
Quando estamos criando um compilador de uma linguagem qualquer o programa objeto é um compilador, ele passa a ser chamado de metaprograma, e a linguagem usada para criá-lo é chamada de metalinguagem. Chamamos de “linguagem objeto” a linguagem com a qual este compilador vai trabalhar.
Quando a linguagem objeto e a metalinguagem são a mesma, ou seja, uma linguagem pode ser sua própria metalinguagem, damos o nome de reflexão. Um ótimo exemplo é a nossa ferramenta preferida, o Delphi, que foi feito em Delphi, ou qualquer outro compilador de uma linguagem que foi criado nessa mesma linguagem.
A seguir explicaremos um pouco sobre duas outras formas em que a metaprogramação é usada. Nós nos concentraremos na segunda situação, mas observe que no nosso cotidiano usamos exemplos da primeira sem perceber.
Um programa com funções de compilador
Um compilador, ou uma biblioteca usada por ele, pode disponibilizar no programa compilado funções dele mesmo, para serem usadas no programa objeto.
Alguns compiladores disponibilizam uma API ou Interface para que programas compilados por ele possam usar funções do próprio compilador. Para ilustrar, imagine que você use a linguagem C++ para criar um outro compilador de C++. Posteriormente você transforma esse seu outro compilador de C++ de sua autoria em uma biblioteca, digamos uma DLL, para que possa ser incorporada e usada por qualquer outro programa feito por você.
Com isso você poderia criar programas num compilador de C++ normal, que usassem essa DLL para compilar outros programas de C++. Você poderia incorporar isso em um ERP para que administradores de sistemas criassem suas próprias rotinas.
No Delphi podemos ver esse exemplo no uso de genéricos. Genéricos usam funções do próprio compilador na metalinguagem Delphi. Além disso, RTTI e Genéricos são bons exemplos de reflexão, pois usam recursos do compilador para descobrir ou modificar informações do programa compilado.
Uma função que executa um comando
Sempre que uma biblioteca qualquer, em uma linguagem qualquer, possuir funções que aceitem como parâmetros expressões de texto ou Strings que contenham comandos, e interpretar ou executar esses comandos em tempo de execução, estamos lidando com um caso de metaprogramação.
Um exemplo bastante claro é um Browser. Consideremos o Mozilla Firefox. O Firefox foi escrito, em sua maior parte, em C/C++. Dentre as muitas funções de um Browser podemos destacar a habilidade de interpretar JavaScript.
JavaScript é uma linguagem de alto nível, muito poderosa e geralmente interpretada por um Browser. Programas podem ser escritos em JavaScript, principalmente para validação ou animação de formulários Web, mas não se limitando a isso.
Nesse caso especial do Browser podemos dizer que a metalinguagem foi C++ e que o metaprograma é o Browser. A linguagem alvo, ou mais precisamente linguagem objeto, é o JavaScript. É este o caso que vamos estudar.
Quem já programou em FoxPro, Visual Basic, Clipper ou JavaScript já conhece essa forma de metaprogramação. Essas linguagens têm funções ou recursos para executar comandos na própria linguagem. Esses comandos podem vir de arquivos de configuração, bancos de dados ou podem ser digitados pelo próprio usuário, e podem interagir com elementos já presentes no programa e fornecer uma ferramenta muito poderosa para alteração e customização dos programas. Um exemplo prático é a função eval do JavaScript, que pode ser vista na Listagem 1.
Listagem 1. Função Eval do Javascript
<script>
eval("alert('Hello World');");
</script>
O JavaScript é uma linguagem que tem uma função especial, chamada eval que recebe como argumento uma String. Esta String pode ser uma fórmula matemática, assim eval retornaria o resultado, ou pode ser um código também em JavaScript.
O código da Listagem 1 fará com que apareça a caixa de mensagem com o texto Hello World no Browser. Mas nós não usamos a função alert para fazer isso, nós usamos a função eval passando o comando alert em forma de String, como parâmetro. O que aconteceu foi que o interpretador do JavaScript, ao executar a função eval, usou recursos do próprio interpretador para executar também o conteúdo passado à eval. Com um input do tipo Text, ou uma TextArea, seria possível permitir ao usuário executar Scripts arbitrários para interagir com elementos da página. O que nós vamos fazer aqui é criar um programa que pode interpretar e executar comandos dados pelo usuário.
Cenários
Imagine que você tem um software para vender para empresas de ramos diferentes, com necessidades diferentes. Seria impossível atender a todas as demandas por alterações no sistema. Caso tentássemos fazer isso nosso software ficaria com um número muito grande de configurações para fazer antes da implantação e um número muito grande de dependências e funcionalidades que seriam utilizadas por menos de 10% de nossos clientes.
Se nós mantemos apenas um único sistema, sem trabalhar com “específicos” é mais fácil garantir que nossos clientes estarão sempre com a versão mais atual do sistema, todos ao mesmo tempo, e uma correção ou melhoria no sistema pode ser atualizada e propagada rapidamente para todos os clientes.
De vez em quando surge um cliente com uma necessidade especial, a qual chamaremos de xxx. Se parecer uma boa ideia podemos nos sentir tentados a incorporar essa nova funcionalidade no sistema, mas sempre que fizermos isso temos de nos preocupar se essa funcionalidade não vai atrapalhar os demais clientes ou tornar o sistema invendável para clientes de outros ramos.
...