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.variável ::= { SALDO | EMFABRICACAO | PEDIDOS }
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.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;
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:
var APos: integer;
var ATipoTokenEsperado:TWTipoToken;
AExpr: TWExpressaoAbstrata)
: TWExpressaoAbstrata;
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.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;
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.
