Está en la página 1de 8

Cmo crear un compilador de expresiones en .

NET
Alberto Poblacin

Cmo crear un compilador de expresiones en .NET


Nivel: Intermedio-Avanzado

por Alberto Poblacin

En un artculo anterior Alberto nos daba explicaciones acerca de cmo escribir un intrprete capaz de tomar en tiempo de ejecucin una expresin del tipo 5*x-3*(x+1) y evaluarla para calcular el resultado. En aquel momento comentbamos que el primer paso consista en elaborar un diagrama sintctico como el de la figura, y luego escribir cdigo para recorrer las distintas ramas del grfico interpretando sobre la marcha las operaciones encontradas:

Una limitacin que tiene esa forma de operar y que ya se comentaba en aquel texto- es que resulta lenta. Si es necesario evaluar la misma funcin miles de veces, por ejemplo, para dibujar una grfica punto por punto, entonces se repite miles de veces el seguimiento del diagrama y la interpretacin de las operaciones.

Una posible solucin consiste en compilar la expresin. Se recorre el diagrama, igual que en el caso de la interpretacin, pero cada vez que hay que realizar una de las operaciones de clculo, lo que se hace, en lugar de operar sobre la marcha, es generar cdigo ejecutable que implemente esa operacin. Terminado el diagrama, y generado todo el cdigo, las sucesivas ejecuciones se realizan mediante llamadas a dicho ejecutable, esta vez a toda velocidad. Para este fin, vamos a utilizar las clases disponibles en el espacio de nombres System.Reflection.Emit. Aunque es posible generar un ensamblado dinmico o salvarlo a disco, para la aplicacin concreta que tenemos entre manos es preferible utilizar lo que se denomina un DynamicMethod, que viene a ser un mtodo generado dinmicamente en memoria y que podemos ejecutar sobre la marcha. La ventaja del mtodo dinmico es que el Garbage Collector es capaz de liberarlo cuando ya no se necesite, mientras que si se genera un ensamblado, no se descarga de memoria mientras no se descargue el dominio de aplicacin completo. Los pasos necesarios para generar el mtodo dinmico son estos: 1. Definir un delegado que sirva para apuntar a un mtodo del tipo que queremos generar. En nuestro ejemplo utilizaremos el tipo DelegadoParaEvaluar que sirve para apuntar a mtodos que reciban como argumento un double y devuelvan como resultado otro double. 2. Crear una instancia de la clase DynamicMethod, pasndole en el constructor el nombre del mtodo y los Type de los argumentos y el resultado. 3. Llamar al mtodo GetILGenerator del DynamicMethod para obtener una instancia del generador de cdigo IL (Intermediate Language). 4. Llamar al mtodo Emit del generador cuantas veces sea necesario para ir escribiendo el cdigo de nuestro mtodo dinmico. Esta es la operacin que se va realizando repetidamente cada vez que en el diagrama sintctico se ve la necesidad de realizar una operacin. Los argumentos de Emit indican cul es la operacin concreta que se va a realizar. 5. Llamar al mtodo CreateDelegate del DynamicMethod para obtener un delegado, que el que finalmente se usa para ejecutar el cdigo generado. En el ejemplo de cdigo que se adjunta como Listado 1 a continuacin, hemos reproducido en su mayor parte el analizador sintctico que ya introdujimos en el artculo sobre el intrprete, pero hemos sustituido las rutinas que ejecutaban las operaciones matemticas (OperacionSuma, OperacionResta, etc.) por otras equivalentes que generan cdigo mediante Reflection.Emit. El Listado 2 muestra la forma de realizar la llamada para evaluar una expresin.

Listado 1
using System.Reflection.Emit; ... public class Compilador { private string expresion; private int posicion; private Simbolos ultimoSimbolo; private double ultimaConstante; public delegate double DelegadoParaEvaluar(double valor); public DelegadoParaEvaluar Evaluar; private ILGenerator il; public Compilador(string expresion) { this.expresion = expresion; this.posicion = 0; Type tipoADevolver = typeof(double); Type[] tiposDeLosParametros = new Type[] { typeof(double) }; DynamicMethod evaluador = new DynamicMethod( "Evaluador", tipoADevolver, tiposDeLosParametros); il = evaluador.GetILGenerator(); ultimoSimbolo = ObtenerSiguienteSmbolo(); Expresion(); il.Emit(OpCodes.Ret); Evaluar = (DelegadoParaEvaluar)evaluador.CreateDelegate( typeof(DelegadoParaEvaluar)); } private Simbolos ObtenerSiguienteSmbolo() { char c; do { if (posicion >= expresion.Length) return Simbolos.FinDeLaExpresin; c = expresion[posicion++]; } while (c == ' '); switch (c) { case '+': return Simbolos.Suma; case '-': return Simbolos.Resta; case '*': return Simbolos.Multiplicacin; case '/': return Simbolos.Divisin; case '(': return Simbolos.AbrirParntesis; case ')': return Simbolos.CerrarParntesis; case 'x': case 'X': return Simbolos.Variable; } 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; ultimaConstante = double.Parse(s.Replace(".", ",")); return Simbolos.Constante; } throw new Exception( "Simbolo no reconocido en la posicin " + posicion); } Contina...

private void Expresion() { Termino(); while (true) { switch (ultimoSimbolo) { case Simbolos.Suma: ultimoSimbolo = ObtenerSiguienteSmbolo(); Termino(); OperacionSuma(); break; case Simbolos.Resta: ultimoSimbolo = ObtenerSiguienteSmbolo(); Termino(); OperacionResta(); break; default: return; } } } private void Termino() { Factor(); while (true) { switch (ultimoSimbolo) { case Simbolos.Multiplicacin: ultimoSimbolo = ObtenerSiguienteSmbolo(); Factor(); OperacionMultiplicacion(); break; case Simbolos.Divisin: ultimoSimbolo = ObtenerSiguienteSmbolo(); Factor(); OperacionDivision(); break; default: return; } } } private void Factor() { if (ultimoSimbolo == Simbolos.AbrirParntesis) { ultimoSimbolo = ObtenerSiguienteSmbolo(); Expresion(); if (ultimoSimbolo != Simbolos.CerrarParntesis) throw new Exception("Falta ')'"); ultimoSimbolo = ObtenerSiguienteSmbolo(); } else if (ultimoSimbolo == Simbolos.Constante) { OperacionConstante(); ultimoSimbolo = ObtenerSiguienteSmbolo(); } else if (ultimoSimbolo == Simbolos.Variable) { OperacionVariable(); ultimoSimbolo = ObtenerSiguienteSmbolo(); } else throw new Exception("Factor"); }

Contina...

private void OperacionConstante() { il.Emit(OpCodes.Ldc_R8, ultimaConstante); } private void OperacionVariable() { il.Emit(OpCodes.Ldarg_0); } private void OperacionSuma() { il.Emit(OpCodes.Add); } private void OperacionResta() { il.Emit(OpCodes.Sub); } private void OperacionMultiplicacion() { il.Emit(OpCodes.Mul); } private void OperacionDivision() { il.Emit(OpCodes.Div); } } enum Simbolos { Ninguno, Suma, Resta, Multiplicacin, Divisin, AbrirParntesis, CerrarParntesis, Constante, Variable, FinDeLaExpresin }

Listado 2
//Se ejecuta una vez, creando el mtodo dinmico string expresion = "5*x-3*(x+1) "; Compilador comp = new Compilador(expresion); //Se ejecuta cuantas veces sea necesario double x = ...; double resultado = comp.Evaluar(x);

Al igual que ya mencionamos cuando hablbamos del intrprete, aplicando procedimientos similares a los que hemos visto aqu se pueden procesar expresiones tan complejas como deseemos, pudiendo llegar incluso a construir un compilador para un lenguaje de programacin completo.

Acerca del autor


Alberto Poblacin lleva 27 aos 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 formacin, asesoramiento y desarrollo de aplicaciones. Es tutor de campusMVP.

Acerca de campusMVP
CampusMVP te ofrece la mejor formacin en tecnologa Microsoft a travs de nuestros cursos online y nuestros libros especializados, impartidos y escritos por conocidos MVP de Microsoft. Visita nuestra pgina 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 distribucin de las cuales se debe hacer con una licencia igual a la que regula esta obra original. Se debe citar la fuente.

También podría gustarte