No post sobre máquina de estado eu falei que iria fazer um exemplo mais completo para melhor entendimento dos conceitos.
Neste post vou abordar em como construir uma função que valide a construção de um endereço de e-mail utilizando o conceito de máquina de estado.
Uma das formas de se validar uma sequência de caracteres é utilizando expressão regular, mas nem todos ambientes temos expressões regulares para validação. Por exemplo temos o Delphi, apesar de ser possível adicionar esta funcionalidade.
Uma curiosidade, internamente, a rotina que processo uma expressão regular, possuí uma máquina de estado para processar a expressão passada. Inclusive em ambientes como .NET, a expressão regular é “compilada” gerando código CIL (Common Intermediate Language), um algorítimo na forma de máquina de estado.
O que faremos aqui usando diagramas e máquina de estado, nada mais é o que o algorítimo de expressão regular faz automaticamente.
Premissas para o endereço de e-mail: Para o nosso exemplo adoto as seguintes regras, deve existir apenas uma arroba (@). Deve existir pelo menos 1 caracter antes do arroba, este caracter deve ser de “A” à “Z”, de “a” à “z”, de “0″ à “9″, “-” (hífen), “_” (travessão) ou “.” (ponto). Depois do arroba teriamos uma sequência de pelo menos 2 caracteres válidos, um ponto e pelo menos 2 carcteres válidos, sendo que pode-se repetir o ponto mais caracteres válidos.
Dado estas premissas, eu montei a seguinte expressão regular:
[A-Za-z0-9-_.]+@[A-Za-z0-9-_]{2,}(\.[A-Za-z0-9-_]{2,})+
Para testar, entre neste site e copie e cole a expressão acima.
Por esta expressão regular, o endereço a@bb.cc é considerado válido, mas um @bb.cc não é. Faça testes com vários endereços de e-mail e verifique se todas as premissas foram atendidas.
Agora vou montar um diagrama de máquina de estado que seguirá as premissas passadas:
Para montar este diagrama, foi feito estado por estado (começando do “1″) e tentando seguir as premissas. Para exemplificar: no estado 1 eu vejo a premissa de que é necessário pelo menos 1 caracter válido antes do arroba. E no estado 2 verifico uma seqüência de n caracteres válidos (observe a transição do estado 2 para o próprio estado 2).
Sendo que [A-Za-z0-9-_.]+ é o mesmo que [A-Za-z0-9-_.][A-Za-z0-9-_.]*. Podemos dizer que [A-Za-z0-9-_.] é representado pelo estado 1 e [A-Za-z0-9-_.]* é representado pelo estado 2.
A mesma regra vale para achar os outros estados e transições. Para fixar a idéia, sugiro fazer um funcionamento da máquina de estado mentalmente como dissemos neste post.
Observe que em qualquer estado, se as condições não são válidas, saímos imediatamente. A unica forma de considerar um e-mail válido é percorrer todos os estados e no oitavo satisfazer a condição FIM que executa a ação OK!!!.
Na seqüência mostro a função em Pascal que implementa o diagrama acima:
const
V = [‘a’..‘z’, ‘A’..‘Z’, ‘0′..‘9′, ‘_’, ‘-’];
VPonto = V + [‘.’];
var
email: string;
function Proximo: Char;
begin
if email <> ” then
begin
Result := email[1];
Delete(email, 1, 1);
end
else
Result := #0;
end;
function Estado1(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in VPonto then
Result := 2
else
Result := 9;
end;
function Estado2(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in VPonto then
Result := 2
else if ALetra = ‘@’ then
Result := 3
else
Result := 9;
end;
function Estado3(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 4
else
Result := 9;
end;
function Estado4(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 5
else
Result := 9;
end;
function Estado5(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 5
else if ALetra = ‘.’ then
Result := 6
else
Result := 9;
end;
function Estado6(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 7
else
Result := 9;
end;
function Estado7(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 8
else
Result := 9;
end;
function Estado8(ALetra: Char; var Valido: Boolean): Integer;
begin
if ALetra in V then
Result := 8
else if ALetra = ‘.’ then
Result := 6
else if ALetra = #0 then
begin
Valido := True;
Result := 9;
end
else
Result := 9;
end;
type
TTestaEstado = function(ALetra: Char; var Valido: Boolean): Integer;
var
TestaEstado: array [1..8] of TTestaEstado;
estado: Integer;
begin
TestaEstado[1] := @Estado1;
TestaEstado[2] := @Estado2;
TestaEstado[3] := @Estado3;
TestaEstado[4] := @Estado4;
TestaEstado[5] := @Estado5;
TestaEstado[6] := @Estado6;
TestaEstado[7] := @Estado7;
TestaEstado[8] := @Estado8;
email := AEMail;
Result := False;
estado := 1;
while estado < 9 do
estado := TestaEstado[estado](Proximo, Result);
end;
O código é apenas uma materialização do diagrama, nenhuma otimização foi feita.
Muito bom. Outra coisa legal da máquina de estado é que fica fácil inserir novos estados a qualquer altura, sem precisar mudar muita coisa.
Concordo com você. Alterar o diagrama e trazer isso para o código torna-se trivial. Facilitando a manutenção de código.