El patrón Interpreter
Descripción
El patrón Interpreter proporciona un marco para representar mediante objetos la gramática de un lenguaje con el fin de evaluar, interpretándolas, expresiones escritas en este lenguaje.
Ejemplo
Queremos crear un pequeño motor de búsqueda de vehículos con ayuda de expresiones booleanas según la gramática muy sencilla que se muestra a continuación:
expresión ::= término || palabra-clave || (expresión)
termino ::= factor 'o' factor
factor ::= expresión 'y' expresión
palabra-clave ::= 'a'..'z','A'..'Z' {'a'..'z','A'..'Z'}*
Los símbolos entre comillas son símbolos terminales. Los símbolos no terminales son expresión, término, factor y palabra-clave. El símbolo de partida es expresión.
Vamos a implementar el patrón Interpreter para poder expresar cualquier expresión que responda a esta gramática según un árbol sintáctico constituido por objetos con el objetivo de poder evaluarla e interpretarla.
Tal árbol está constituido únicamente por símbolos terminales. Para simplificar, consideramos que una palabra-clave constituye un símbolo terminal en tanto que es una cadena de caracteres.
La expresión (rojo o gris) y reciente y diesel se representa en el árbol sintáctico de la figura 4-4.1.
Figura 4-4.1 - Árbol sintáctico correspondiente a la expresión (rojo o gris) y reciente y diesel
La evaluación de tal árbol...
Estructura
1. Diagrama de clases
La figura 4-4.3 describe la estructura genérica del patrón.
Este diagrama de clases muestra que existen dos tipos de sub-expresiones, a saber:
-
los elementos terminales que pueden ser nombres de variables, enteros, nombres reales;
-
los operadores que pueden ser binarios como en el ejemplo, unarios (operador « - ») o que tomen más argumentos como las funciones.
Figura 4-4.3 - Estructura del patrón Interpreter
2. Participantes
Los participantes del patrón son los siguientes:
-
Expresión es una clase abstracta que representa cualquier tipo de expresión, es decir cualquier nodo del árbol sintáctico.
-
OperadorAbstracto (OperadorBinario) es también una clase abstracta. Describe cualquier nodo de tipo operador, es decir que posea operandos que son subárboles del árbol sintáctico.
-
OperadorConcreto1 y OperadorConcreto2 (OperadorY, OperadorO) son implementaciones del OperadorAbstracto que describen completamente la semántica del operador y por tanto son capaces de evaluarlo.
-
ElementoTerminal es una clase abstracta que describe cualquier nodo correspondiente a un elemento terminal.
-
ElementoTerminalConcreto1 y ElementoTerminalConcreto2 (PalabraClave) son clases concretas que corresponden con un elemento terminal, capaces de evaluar este elemento.
3. Colaboraciones
El cliente construye una expresión bajo la forma de un árbol sintáctico...
Dominios de aplicación
El patrón se utiliza para interpretar expresiones representadas bajo la forma de árboles sintácticos. Se aplica principalmente en los siguientes casos:
-
La gramática de las expresiones es simple.
-
La evaluación no necesita ser rápida.
Si la gramática es compleja, es preferible utilizar analizadores sintácticos especializados. Si la evaluación debe realizarse rápidamente, puede resultar necesario el uso de un compilador.
Ejemplo en C#
A continuación se muestra el código completo de un ejemplo escrito en C# que no sólo permite evaluar un árbol sintáctico sino que también lo construye.
La construcción del árbol sintáctico, llamado análisis sintáctico, también está repartida en las clases, a saber las de la figura 4-4.2 bajo la forma de métodos de clase (métodos precedidos por la palabra reservada static en C#).
El código fuente de la clase Expresion aparece a continuación. La parte relativa a la evaluación se limita a la declaración de la firma del método evalua.
Los métodos siguientePieza, analiza y parsea están dedicados al análisis sintáctico. El método analiza se utiliza para parsear una expresión entera mientras que parsea está dedicado al análisis bien de una palabra-clave o bien de una expresión escrita entre paréntesis.
using System;
public abstract class Expresion
{
public abstract bool evalua(string descripcion);
// parte análisis sintáctico
protected static string fuente;
protected static int indice;
protected static string pieza;
protected static void siguientePieza()
{
while ((indice < fuente.Length) && (fuente[indice] ==
' '))
indice++;
if (indice == fuente.Length)
pieza = null;
else if ((fuente[indice] == '(') || (fuente[indice] ==
')'))
{
pieza = fuente.Substring(indice, 1);
indice++;
} ...