Avaliando Expressões Matemáticas com C#
Leonel Fraga de Oliveira 05/05/2010 23:40

Antes de mais nada, o que significa “avaliar” (ou em inglês: evaluate) uma expressão? Sendo simples e direto, significa resolvê-la.

Nas linguagens de programação, podemos montar expressões de diversas maneiras dentro do nosso código, seja com valores constantes ou com variáveis, porém, elas estarão de forma hard-coded.

Recentemente tive a necessidade de fazer um sistema em que uma fórmula de cálculo é montada de forma dinâmica dentro da aplicação, ou melhor dizendo, o usuário monta a fórmula em uma interface de cadastro, o programa substitui algumas constantes por valores e enfim faz a conta.

Agora falando mais tecnicamente sobre essa necessidade: preciso que o programa avalie uma expressão matemática passada como string.

Até aí beleza, pois sabia que algumas linguagens, como por exemplo o JavaScript, possuem a função eval(), que recebe como parâmetro uma string com uma expressão válida e retorna o resultado da mesma. Fui pesquisar pelo code-insight do Visual Studio para ver se tinha um método como Math.Eval(), Math.Parse() ou coisa que o valha, porém não tive sucesso nessa empreitada.

Xi… F*deu! E agora?

Lembrei-me dos velhos tempos de faculdade, onde na disciplina de Estrutura de Dados chegamos a implementar um avaliador de expressões utilizando pilhas. Mas eu não me lembrava do algoritmo. Aqui vamos abrir um parêntese: Fuçando no meu computador, achei essa implementação, em linguagem Pascal. Segue o código:

program EXPRESSAO1;
{Resolve expressoes parentetizadas e converte para NPR}
uses crt,pilha;

type maximo = string[30];

{Funcao que simplifica o uso da funcao Pascal Val}
function str2int(valor : maximo) : integer;
var i,code : integer;
begin
     Val(valor, I, Code);
     if code <> 0 then
        Writeln('Error at position: ', Code)
     else
     str2int := I;
end;

{Funcao que simplifica o uso da funcao Str Pascal }
function int2str(I: integer): MAX_DIG;
var
 S: MAX_DIG;
begin
 Str(I, S);
 int2str := S;
end;


{Checa se o caracter e um operador}
function checa_operador(n : char) : boolean;
begin
     if (n = '+') or (n = '-') or (n = '*') or (n = '/') then
        checa_operador := true
     else
         checa_operador := false;
end;

{Avalia (= resolve) a expressao em NPR}
function aval_npr(expressao : maximo) : MAX_DIG;
var
   p2      : tpilha;
   result  : integer;
   cont    : integer;
   op1,op2 : integer;
begin
     cont := 1;
     s_create(p2);
     while cont < (length(expressao)+1) do begin
           if expressao[cont] = ' ' then begin
              cont := cont + 1;
           end
           else begin
               if not checa_operador(expressao[cont]) then
                  push(p2,expressao[cont])
               else begin
                    op1 := str2int(pop(p2));
                    op2 := str2int(pop(p2));
                    if expressao[cont] = '+' then
                       push(p2,int2str(op2 + op1));
                    if expressao[cont] = '-' then
                       push(p2,int2str(op2 - op1));
                    if expressao[cont] = '*' then
                       push(p2,int2str(op2 * op1));
                    if expressao[cont] = '/' then
                       push(p2,int2str(op2 div op1));
               end;
               cont := cont + 1;
           end;
     end;
     aval_npr := pop(p2);
end;

{Modulo Principal}
var
   expr     : maximo;
   expr_npr : maximo;
   aux      : char;
   cont     : integer;
   p1       : tpilha;
   result   : integer;
   op1,op2  : integer;
   tam_expr : integer;
begin
     clrscr;
     write('Digite a expressao paretentizada: ');
     readln(expr);
     writeln('A expressao digitada e: ',expr);

     {Passos para a conversao em NPR}
     s_create(p1);
     cont := 1;
     while cont < (length(expr) + 1) do begin
           if expr[cont] = '(' then begin
              cont := cont + 1;
           end
           else begin
                if checa_operador(expr[cont]) then
                   push(p1,expr[cont])
                else begin
                     if expr[cont] <> ')' then begin
                        expr_npr := expr_npr + expr[cont];
                        expr_npr := expr_npr + ' ';
                     end;
                end;
                if expr[cont] = ')' then begin
                   expr_npr := expr_npr + pop(p1) + ' ';
                end;
           cont := cont + 1;
           end;
     end;
     writeln('A expressao em NPR e: ',expr_npr);
     writeln('O resultado da expressao e: ',aval_npr(expr_npr));
     readln;
end.

Neste código não se encontra a implementação de pilha que fiz na época. E lembre-se: ele é bem antigo!

Fechando o parêntese…

Como isso me foi inviável, comecei a procura por um componente, classe, código, o raio que o parta para implementar tal “parser”. A esmagadora maioria dos resultados é de componentes pagos, porém, vi este código no site Code Project, que cria um assembly em tempo de execução. Mais uma vez, isso também me era inviável.

Googlando mais um pouco, cheguei a um artigo no Linha de Código (fonte no final do post) que tinha duas formas de implementar o parser: uma utilizando o IronRuby e outra utilizando uma classe que faz o trabalho utilizando JavaScript, porém, executando em uma engine à parte. Optei pela segunda opção, e vejam o código da classe que implementei:

public class JScriptEval
{
	private static JS.Vsa.VsaEngine _localEngine = JS.Vsa.VsaEngine.CreateEngine();
	public static T EvalExpressao(String expressao)
	{
		var res = Microsoft.JScript.Eval.JScriptEvaluate(expressao, _localEngine);
		return (T) Convert.ChangeType(res, typeof(T));
	}
}

Adicione as referências aos assemblies Microsoft.Vsa e Microsoft.JScript ao seu projeto. Veja que ele cria o engine de JavaScript e executa o método de avaliação de expressões. Note também que o retorno desta função é dinâmico. Para usá-la, faça o seguinte:

var res2 = JScriptEval.EvalExpressao("2+2");

Implementei esse código no meu programa, ele funcionou perfeitamente, e ficou tudo azul!

[Via: Avaliando Regras de Negócio Dinamicamente em C#, por Paulo Henrique dos Santos Monteiro – Linha de Código]

Um abraço!

Leonel Fraga de Oliveira Leonel Fraga de Oliveira é formado em Processamento de Dados na Faculdade de Tecnologia de São Paulo (FATEC-SP - 2002) e anteriormente em Técnico em Eletrônica, pela ETE Professor Aprígio Gonzaga (lá em 1999).
Atualmente trabalha como Analista de Sistemas na Prefeitura Municipal de São Caetano do Sul - SP
Tem como hobbies DJing (também trabalha como DJ freelancer) e ciclismo, além da manutenção dos sites NeoMatrix Light e NeoMatrix Tech.
Gosta de música eletrônica, tecnologia, cinema (super fã de Jornada nas Estrelas), gastronomia e outras coisas mais.


Compartilhe nas redes sociais

   

Deixe seu comentário

comments powered by Disqus