31 de agosto de 2010

Design Patterns com Delphi : Interpreter - parte III

Neste post eu concluo o exemplo para o Design Pattern comportamental Interpreter. Conforme eu disse no post anterior, aqui eu falo sobre análise sintática de um texto para podermos trabalhar de fato com o Interpreter.

Embora o conceito estabelecido para o Interpreter nada diga a respeito, é imprescindível construir um analisador sintático para podermos utilizar este Pattern. Independentemente da complexidade da sintaxe válida estabelecida, é o analisador quem extrairá as partes que compõem uma expressão e montará a instância de classe que nos permitirá avaliar tal expressão em um contexto qualquer. Como as sintaxes podem ser muito diferentes entre si - pense num comando SQL comparado à notação polonesa, por exemplo - fica bastante difícil determinar uma fórmula única que sirva de guia para construir todas as possibilidades.

Para o exemplo do saldo de itens de estoque introduzido no post inicial sobre o Interpreter, a sintaxe é bastante simples. Ela consta de alguns poucos símbolos válidos e de apenas duas operações definidas. Formalmente, podemos resumir essa sintaxe como segue:

Expressão ::= { variável [ { + | - } Expressão] }
variável ::= { SALDO | EMFABRICACAO | PEDIDOS }
Ou seja, a expressão pode ser uma única das variáveis permitidas ou uma operação (soma ou subtração) entre a variável e uma outra expressão - isso mostra o nítido caráter recursivo dessa sintaxe. Os nomes de variáveis aceitas na expressão são os listados na segunda linha do quadro acima. O sinal | significa que se deve escolher um dos valores para que a expressão seja válida. O par [ ] indica um termo não obrigatório. Para finalizar a definição da sintaxe, ressalto que ela não é sensível ao caso; isto é, letras maiúsculas e minúsculas não são diferenciadas.

Montar uma instância de classe com base num texto que siga essa sintaxe exigirá preparativos para controlar chamadas recursivas, conforme constatamos anteriormente. Precisamos ter controle sobre qual é o próximo tipo de token esperado (variável ou operação) e também conhecer até que ponto o texto já foi analisado. Veja o código:
function TWMontaExpressao.CreateExprFrom (AStrExpr: String) : TWExpressaoAbstrata;
var lPos, lCompr : integer;
lTipoTokenEsperado : TWTipoToken;
lExpr: TWExpressaoAbstrata;
begin
{ Próximo caractere a ser lido no texto }
lPos := 1;
{ Ainda não há expressões criadas }
lExpr := Nil;
AStrExpr := Trim (AStrExpr);
{ Comprimento do texto analisado }
lCompr := Length (AStrExpr);
{ Inicia aguardando uma variável }
lTipoTokenEsperado := ttkVar;

repeat
try
{ Chama a função recursiva para montar a expressão a partir do texto }
lExpr := CreateProxExpr (AStrExpr, lPos, lTipoTokenEsperado, lExpr);
except
on Exception do begin
{ No caso de erro, devolver a memória usada }
FreeAndNil (lExpr);
Raise;
end;
end;
until (lPos > lCompr);

Result := lExpr;
end;
A função acima apenas prepara o ambiente para que a função CreateProxExpr seja invocada recursivamente. Ela, então, pode percorrer o texto, analisando e interpretando os termos da expressão associada.

Para isso funcionar, a declaração da função recursiva deve ser feita com passagem de parâmetros por referência (palavra chave VAR do Delphi) pois assim o avanço na análise pode ser devolvido em cada chamada à função, garantindo que nada será interpretado duas vezes e que uma chamada prosseguirá a partir do ponto onde a anterior parou. A função também recebe a instância da expressão montada até o momento para que possa utilizá-la na montagem das instâncias seguintes. A definição do cabeçalho da função fica assim:
function CreateProxExpr (var AStrExpr: String;
var APos: integer;
var ATipoTokenEsperado:TWTipoToken;
AExpr: TWExpressaoAbstrata)
: TWExpressaoAbstrata;
A codificação dessa função deve extrair o próximo token do texto e verificar se ele é do tipo esperado - variável ou operação. Se for do tipo variável, basta criar a instância correta de expressão para a variável. No caso de ser uma operação, a própria função será chamada recursivamente para obter o termo seguinte da expressão pois nossas operações sempre exigem dois termos. Com o segundo termo preparado, podemos então criar a instância de classe representando a operação em questão. Fica mais fácil vendo o código:
lToken := ProxToken (AStrExpr, APos, ATipoTokenEsperado);
lTipoToken := CalcTipoToken (lToken);

if (lTipoToken <> ttkVazio) then
begin
if (lTipoToken <> ATipoTokenEsperado) then
raise Exception.CreateFmt('Esperado símbolo do tipo "%s"',
[GetNameFromTipoToken(ATipoTokenEsperado)]);
if (lTipoToken = ttkVar) then
ATipoTokenEsperado := ttkOper { Próximo tem que ser uma operação }
else begin
ATipoTokenEsperado := ttkVar; { Próximo tem que ser uma das variáveis }
lExpr := CreateProxExpr (AStrExpr, APos, ATipoTokenEsperado, AExpr);

if (lExpr = Nil) then
raise Exception.CreateFmt('Fim inesperado da expressão - operador "%s"', [lToken]);
end;

Result := CriaInstancia (lToken, AExpr, lExpr);
end;
Embora não estejam retratadas, o código acima faz uso de outras funções importantes do analisador: a função ProxToken - que apenas percorre o texto a partir da posição atual e extrai dele um token - e a CriaInstancia, que é uma espécie de Factory para criação de instâncias de expressões.

O resultado da chamada à função CreateExprFrom é uma única instância de expressão que pode ser avaliada com qualquer item de estoque do tipo TWItem. Todas as outras instâncias construídas pelo analisador ficam internas a esta instância e é ela a responsável por devolver os recursos de memória quando não forem mais necessários.

O exemplo completo está disponível para download como um projeto Delphi 2005. Para obtê-lo, siga este link.

Mais Informações Design Patterns com Delphi : Interpreter - parte I e parte II, Download do projeto de exemplo em Delphi 2005

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.