El patrón de diseño Interpreter
Descripción
El patrón de diseño Interpreter representa en forma de objetos la gramática de un idioma con el fin de evaluar, interpretándolas, expresiones escritas en este idioma.
Ejemplo
Queremos crear un pequeño motor de búsqueda de vehículos con ayuda de expresiones booleanas según una gramática 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 de diseño Interpreter para poder representar cualquier expresión que responda a esta gramática según un árbol sintáctico constituido por objetos con el objetivo.
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 va a traducir por el árbol sintáctico de la figura 20.1.
Figura 20.1 - Árbol sintáctico correspondiente a la expresión (rojo o gris) y reciente y diesel
La evaluación de tal árbol para la descripción...
Estructura
1. Diagrama de clases
La figura 20.3 describe la estructura genérica del patrón de diseño Interpreter.
Este diagrama de clases muestra que existen dos tipos de sub-expresiones, a saber:
-
Los elementos terminales que pueden ser nombres de variables, valores enteros, números reales.
-
Los operadores que pueden ser binarios como en el ejemplo, unarios (operador « - ») o que reciban más argumentos como las funciones.
Figura 20.3 - Estructura del patrón de diseño Interpreter
2. Participantes
Los participantes del patrón de diseño Interpreter son los siguientes:
-
Expresion (AbstractExpresion)es una clase abstracta que representa cualquier tipo de expresión, es decir cualquier nodo del árbol sintáctico.
-
OperadorAbstracto (AbstractOperadorBinario) 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 se corresponden con un elemento terminal, capaces de evaluar...
Dominios de aplicación
El patrón de diseño Interpreter 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.
Ejemplo en PHP
A continuación se muestra el código completo de un ejemplo escrito en PHP que no solo 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 20.2 bajo la forma de métodos de clase (métodos estáticos precedidos por la palabra reservada static en PHP).
El código fuente de la clase AbstractExpresion 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 siguienteItem, analiza y parsea están dedicados al análisis sintáctico. El método parsea se utiliza para parsear una expresión entera mientras que analiza está dedicado al análisis bien de una palabra-clave o bien de una expresión escrita entre paréntesis.
<?php
declare(strict_types=1);
namespace ENI\DesignPatterns\Interpreter;
abstract class AbstractExpresion
{
protected static string $fuente;
protected static int $indice;
protected static ?string $item;
*
protected static function siguienteItem(): void *
{
while ((self::$indice < strlen(self::$fuente)) && *
self::$fuente[self::$indice] == ' ')) {
self::$indice++;
}
if (self::$indice == strlen(self::$fuente)) {
self::$item = null;
} elseif ((self::$fuente[self::$indice] == '(') ||
(self::$fuente[self::$indice] == ')')) {
self::$item = substr(self::$fuente, self::$indice, 1);
self::$indice++;
} else {
$inicio...