You are on page 1of 8

¿Cómo crear un

intérprete de
expresiones en
.NET?
Alberto Población
¿Cómo crear un intérprete de expresiones en .NET?
Nivel: Intermedio-Avanzado
por Alberto Población

En un artículo anterior dábamos respuesta a las preguntas de este tipo:

Deseo introducir en un TextBox una exresión tal como “5*x-3*(x+1)” y que mi


programa calcule el resultado al pasarle el valor de x.

Y comentábamos que una forma de resolverlo, entre las distintas opciones


disponibles, consistía en escribir un intérprete o compilador basado en un
analizador sintáctico hecho por nosotros mismos. En este artículo vamos a dar unas
indicaciones generales acerca de cómo se escribe un programa de este tipo para
interpretar expresiones sencillas.

El primer paso consiste en elaborar un diagrama sintáctico que sirva para


representar las expresiones que queremos evaluar. La Figura 1 representa un
diagrama muy sencillo, que define la sintaxis de las fórmulas matemáticas que
aceptan números paréntesis, las cuatro operaciones matemáticas, y la variable “x”.

Figura 1.- Diagrama sintáctico sencillo.


En este diagrama, se han encerrado en un círculo los elementos “finales”, que se
leen “tal cual” en la expresión tecleada. Normalmente se escribe un fragmento de
programa que en inglés recibe el nombre de parser (“analizador”), que va
devolviendo cada uno de los símbolos que componen la expresión. En el listado 1
que se acompaña más abajo, esto está implementado en la subrutina
ObtenerSiguienteSímbolo.

Las cajas rectangulares sirven para enlazar entre sí las distintas partes del diagrama,
y normalmente se traduce cada una de ellas en un método de nuestro programa
(en el listado 1 llevan el mismo nombre que figura escrito dentro de cada caja).

Las flechas indican la estructura del lenguaje (qué elemento puede venir detrás de
qué otro), y a la hora de programar se convierten en una secuencia de instrucciones
llamando a otro método (cuando las flechas tropiezan con una caja) o llamando al
parser para que obtenga el siguiente símbolo (cuando tropiezan con un círculo).

Cuando una flecha se bifurca en dos, el programa fuente debe contener


instrucciones para leer el siguiente símbolo, y una instrucción condicional (if o
switch) para decidir por cuál de los dos caminos hay que seguir, examinando
símbolo obtenido. Por supuesto, no todos los diagramas sintácticos cumplen el
requisito de que en cada bifurcación se pueda decidir el camino a seguir
examinando únicamente el símbolo siguiente. En los casos en que esto sí que se
cumple (como el diagrama de la figura 1), podremos construir un compilador de
“un solo paso sin marcha atrás”, que es el tipo más simple y el único que vamos a
ver aquí.

Finalmente, nuestro código fuente contendrá las instrucciones necesarias para


hacer la operación indicada por el diagrama (suma, resta, multiplicación, etc.) cada
vez que pase por uno de los puntos en que se ha reunido la información necesaria.
Si la operación se realiza inmediatamente al pasar por ese punto, tenemos un
intérprete; si lo que se hace es generar código ejecutable que más tarde al ser
ejecutado realice la operación deseada, entonces lo que tenemos es un
compilador.

El programa del listado 1 contiene un intérprete de este tipo, realizado conforme


con el esquema de la figura 1. Por razones de claridad y simplicidad, no contiene
apenas nada de código para el control de errores, que lógicamente habría que
añadir en un programa destinado a ser puesto en producción.

Este diagrama es sumamente sencillo, y ha sido fácil dibujarlo en un pequeño


gráfico. Cuando la sintaxis se complica más, y el diagrama empieza a crecer, se
suele recurrir a otros tipos de notación más formales, tales como BNF (Bakus-Naur
Form). Aunque resulta menos claro a simple vista, se trata de un metalenguaje que
define la sintaxis exactamente igual que nuestro gráfico, y sirve para escribir
nuestro intérprete o compilador de la misma manera.
Listado 1:
public class Intérprete
{
private string expresion;
private int posicion;

private Simbolo últimoSimbolo;


private double últimaConstante;

private Stack<double> pila;

private double valorDeX;

public Intérprete(string expresion)


{
this.expresion = expresion;
}

public double Evaluar(double x)


{
this.valorDeX = x;
this.posicion = 0;
this.pila = new Stack<double>();
últimoSimbolo = ObtenerSiguienteSímbolo();
Expresion();
if (pila.Count<1)
throw new Exception("Error al leer de la pila");
return pila.Pop();
}

private Simbolo ObtenerSiguienteSímbolo()


{
char c;
do
{
if (posicion>=expresion.Length)
return Simbolo.FinDeLaExpresión;
c = expresion[posicion++];
}
while (c==' '); //Despreciamos los espacios en blanco
switch (c)
{
case 'x': case 'X': return Simbolo.Variable;
case '(': return Simbolo.AbrirParéntesis;
case ')': return Simbolo.CerrarParéntesis;
case '+': return Simbolo.Suma;
case '-': return Simbolo.Resta;
case '*': return
Simbolo.Multiplicación;
case '/': return Simbolo.División;
}
Regex re = new Regex(@"^\d+([,\.]\d+)?");
string exp = expresion.Substring(posicion-1);
if (re.IsMatch(exp))
{
Match m = re.Match(exp);
string s = m.Value;
posicion += m.Length-1;
últimaConstante =
double.Parse(s.Replace(".",","));
return Simbolo.Constante;
}
throw new Exception(
"Simbolo no reconocido en la posición " + posicion);
}
Continúa...
private void Expresion()
{
Termino();
while (true)
{
switch (últimoSimbolo)
{
case Simbolo.Suma:
últimoSimbolo =
ObtenerSiguienteSímbolo();
Termino();
OperacionSuma();
break;
case Simbolo.Resta:
últimoSimbolo =
ObtenerSiguienteSímbolo();
Termino();
OperacionResta();
break;
default: return;
}
}
}

private void Termino()


{
Factor();
while (true)
{
switch (últimoSimbolo)
{
case Simbolo.Multiplicación:
últimoSimbolo =
ObtenerSiguienteSímbolo();
Factor();
OperacionMultiplicacion();
break;
case Simbolo.División:
últimoSimbolo =
ObtenerSiguienteSímbolo();
Factor();
OperacionDivision();
break;
default: return;
}
}
}

private void Factor()


{
if (últimoSimbolo==Simbolo.AbrirParéntesis)
{
últimoSimbolo = ObtenerSiguienteSímbolo();
Expresion();
if (últimoSimbolo!=Simbolo.CerrarParéntesis)
throw new Exception("Falta ')'");
últimoSimbolo = ObtenerSiguienteSímbolo();
}
else if (últimoSimbolo==Simbolo.Constante)
{
OperacionConstante();
últimoSimbolo = ObtenerSiguienteSímbolo();
}
Continúa...
else if (últimoSimbolo==Simbolo.Variable)
{
OperacionVariable();
últimoSimbolo = ObtenerSiguienteSímbolo();
}
else
throw new Exception("Factor");
}

private void OperacionConstante()


{
pila.Push(últimaConstante);
}
private void OperacionVariable()
{
pila.Push(valorDeX);
}
private void OperacionSuma()
{
pila.Push(pila.Pop()+pila.Pop());
}
private void OperacionResta()
{
double op2 = pila.Pop();
double op1 = pila.Pop();
pila.Push(op1-op2);
}
private void OperacionMultiplicacion()
{
pila.Push(pila.Pop()*pila.Pop());
}
private void OperacionDivision()
{
double op2 = pila.Pop();
double op1 = pila.Pop();
pila.Push(op1/op2);
}
}

enum Simbolo
{
Ninguno,
Suma, Resta, Multiplicación, División,
AbrirParéntesis, CerrarParéntesis,
Constante, Variable,
FinDeLaExpresión

Este listado contiene una clase Intérprete, que recibe la expresión a interpretar a
través de su constructor. Para evaluar la expresión se llama al método Evaluar,
que recibe el valor para la variable x que hemos previsto admitir en nuestras
expresiones. Mediante este mecanismo, se puede ir llamando repetidamente al
intérprete para que evalúe la expresión con distintos valores de x (por ejemplo,
para dibujar una gráfica).

Aplicando procedimientos similares a los que hemos visto aquí, se pueden procesar
expresiones tan complejas como deseemos, pudiendo llegar incluso a crear un
lenguaje de programación completo.
Acerca del autor
Alberto Población lleva 27 años desarrollando software. Ha sido reconocido por Microsoft
como MVP (Most Valuable Professional) de C#. Cuenta, entre otras, con las certificaciones
MCT, MCSE, MCDBA, MCITP, MCSD y MCPD en sus tres variantes (Desarrollador Web,
Desarrollador Windows y Desarrollador de Aplicaciones Empresariales). En la actualidad se
dedica principalmente a la formación, asesoramiento y desarrollo de aplicaciones. Es tutor
de campusMVP.

Acerca de campusMVP
CampusMVP te ofrece la mejor formación en tecnología Microsoft a través de nuestros
cursos online y nuestros libros especializados, impartidos y escritos por conocidos MVP de
Microsoft. Visita nuestra página y prueba nuestros cursos y libros gratuitamente. www-
campusmvp.com

Reconocimiento - NoComercial - CompartirIgual (by-nc-sa):


No se permite un uso comercial de este documento ni de las posibles obras
derivadas, la distribución de las cuales se debe hacer con una licencia igual a la que
regula esta obra original. Se debe citar la fuente.

You might also like