En programación de computadoras , el patrón de intérprete es un patrón de diseño que especifica cómo evaluar oraciones en un lenguaje. La idea básica es tener una clase para cada símbolo ( terminal o no terminal ) en un lenguaje informático especializado . El árbol de sintaxis de una oración en el lenguaje es una instancia del patrón compuesto y se usa para evaluar (interpretar) la oración para un cliente. [1] : 243 Consulte también Patrón compuesto .
Descripción general
El patrón de diseño Interpreter [2] es uno de los veintitrés patrones de diseño GoF bien conocidos que describen cómo resolver problemas de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar.
¿Qué problemas puede resolver el patrón de diseño del intérprete? [3]
- Se debe definir una gramática para un lenguaje simple.
- para que se puedan interpretar oraciones en el idioma.
Cuando un problema ocurre con mucha frecuencia, se podría considerar representarlo como una oración en un lenguaje simple ( Lenguajes específicos del dominio ) para que un intérprete pueda resolver el problema interpretando la oración.
Por ejemplo, cuando se deben especificar muchas expresiones de búsqueda diferentes o complejas. Implementarlas (cablearlas) directamente en una clase es inflexible porque compromete la clase a expresiones particulares y hace que sea imposible especificar nuevas expresiones o cambiar las existentes independientemente de (sin tener que cambiar) la clase.
¿Qué solución describe el patrón de diseño del intérprete?
- Defina una gramática para un lenguaje simple definiendo una
Expression
jerarquía de clases e implementando unainterpret()
operación. - Representar una oración en el lenguaje mediante un árbol de sintaxis abstracta (AST) formado por
Expression
instancias. - Interprete una oración llamando
interpret()
al AST.
Los objetos de expresión se componen de forma recursiva en una estructura compuesta / de árbol que se denomina árbol de sintaxis abstracta (consulte Patrón compuesto ).
El patrón de intérprete no describe cómo construir un árbol de sintaxis abstracto. Esto se puede hacer manualmente por un cliente o automáticamente por un analizador .
Consulte también el diagrama de objetos y clases UML a continuación.
Usos
- Lenguajes de consulta de bases de datos especializados como SQL .
- Lenguajes informáticos especializados que se utilizan a menudo para describir protocolos de comunicación.
- La mayoría de los lenguajes informáticos de propósito general incorporan en realidad varios lenguajes especializados.
Estructura
Diagrama de objetos y clases UML
En el diagrama de clases de UML anterior , la Client
clase se refiere a la AbstractExpression
interfaz común para interpretar una expresión interpret(context)
.
La TerminalExpression
clase no tiene hijos e interpreta una expresión directamente.
La NonTerminalExpression
clase mantiene un contenedor de expresiones secundarias ( expressions
) y reenvía las solicitudes de interpretación a estas expressions
.
El diagrama de colaboración de objetos muestra las interacciones en tiempo de ejecución: el Client
objeto envía una solicitud de interpretación al árbol de sintaxis abstracta. La solicitud se reenvía a (se realiza en) todos los objetos hacia abajo en la estructura del árbol.
Los NonTerminalExpression
objetos ( ntExpr1,ntExpr2
) envían la solicitud a sus expresiones secundarias.
Los TerminalExpression
objetos ( tExpr1,tExpr2,…
) realizan la interpretación directamente.
Diagrama de clases UML
Ejemplos de
BNF
El siguiente ejemplo de formulario Backus – Naur ilustra el patrón de intérprete. La gramática
expresión :: = más | menos | variable | númeromás :: = expresión expresión '+'menos :: = expresión expresión '-'variable :: = 'a' | 'b' | 'c' | ... | 'z'dígito = '0' | '1' | ... | '9'número :: = dígito | dígito
define un idioma que contiene expresiones de notación polaca inversa como:
ab +abc + -ab + ca - -
C#
Este código estructural demuestra los patrones del intérprete, que utilizando una gramática definida, proporciona el intérprete que procesa las declaraciones analizadas.
usando el sistema ; utilizando System.Collections.Generic ;espacio de nombres OOP { class Program { static void Main () { var context = new Context (); var input = new MyExpression (); var expression = new OrExpression { Left = new EqualsExpression { Left = input , Right = new MyExpression { Value = "4" } }, Right = new EqualsExpression { Left = input , Right = new MyExpression { Value = "cuatro" } } } ; entrada . Valor = "cuatro" ; expresión . Interpretar ( contexto ); // Salida: Consola "verdadera" . WriteLine ( contexto . Resultado . Pop ()); entrada . Valor = "44" ; expresión . Interpretar ( contexto ); // Salida: Consola "falsa" . WriteLine ( contexto . Resultado . Pop ()); } } clase Contexto { Pila pública < cadena > Resultado = nueva Pila < cadena > (); } Expresión de interfaz { void Interpret ( contexto de contexto ); } clase abstracta OperatorExpression : Expression { public Expression Left { private get ; establecer ; } derecho de expresión público { obtención privada ; establecer ; } public void Interpretar ( contexto de contexto ) { Izquierda . Interpretar ( contexto ); string leftValue = contexto . Resultado . Pop (); Correcto . Interpretar ( contexto ); string rightValue = contexto . Resultado . Pop (); DoInterpret ( contexto , leftValue , rightValue ); } vacío abstracto protegido DoInterpret ( contexto de contexto , cadena leftValue , cadena rightValue ); } class EqualsExpression : OperatorExpression { anulación protegida void DoInterpret ( Context context , string leftValue , string rightValue ) { context . Resultado . Push ( leftValue == rightValue ? "True" : "false" ); } } class OrExpression : OperatorExpression { anulación protegida void DoInterpret ( Context context , string leftValue , string rightValue ) { context . Resultado . Push ( leftValue == "true" || rightValue == "true" ? "True" : "false" ); } } class MyExpression : Expression { public string Value { private get ; establecer ; } public void Interpretar ( contexto de contexto ) { contexto . Resultado . Empuje ( valor ); } } }
Java
Siguiendo el patrón del intérprete, necesitamos implementar la interfaz Expr con una lambda (puede ser una clase) para cada regla gramatical.
Intérprete de clase pública { @FunctionalInterface public interface Expr { int interpret ( Map < String , Integer > context ); estática Expr número ( int numero ) { retorno contexto -> número ; } static Expr plus ( Expr izquierda , Expr derecha ) { contexto de retorno -> izquierda . interpretar ( contexto ) + derecha . interpretar ( contexto ); } static Expr menos ( Expr izquierda , Expr derecha ) { contexto de retorno -> izquierda . interpretar ( contexto ) - correcto . interpretar ( contexto ); } Variable Expr estática ( nombre de cadena ) { contexto de retorno -> contexto . getOrDefault ( nombre , 0 ); } }
Si bien el patrón de intérprete no se ocupa del análisis sintáctico, [1] : 247 se proporciona un analizador sintáctico para completar.
Private static Expr parseToken ( String token , ArrayDeque < Expr > pila ) { Expr izquierda , derecha ; switch ( token ) { case "+" : // Es necesario eliminar primero el operando derecho de la pila right = stack . pop (); // ... y luego el de la izquierda left = stack . pop (); volver Expr . más ( izquierda , derecha ); caso "-" : derecha = pila . pop (); izquierda = pila . pop (); volver Expr . menos ( izquierda , derecha ); predeterminado : devuelve Expr . variable ( token ); } } análisis de Expr estático público ( expresión de cadena ) { ArrayDeque < Expr > stack = new ArrayDeque < Expr > (); para ( Token de cadena : expresión . split ( "" )) { pila . empujar ( parseToken ( token , pila )); } pila de retorno . pop (); }
Finalmente evaluando la expresión "wxz - +" con w = 5, x = 10 yz = 42.
public static void main ( final String [] args ) { Expr expr = parse ( "wxz - +" ); Map < String , Integer > context = Map . de ( "w" , 5 , "x" , 10 , "z" , 42 ); int resultado = expr . interpretar ( contexto ); Sistema . fuera . println ( resultado ); // -27 } }
PHP (Ejemplo 1)
/ ** * AbstractExpression * / interface Expression { interpretación de función pública ( matriz $ contexto ) : int ; }
/ ** * TerminalExpression * / class TerminalExpression implementa Expression { / ** @var string * / private $ name ; función pública __construct ( cadena $ nombre ) { $ esto -> nombre = $ nombre ; } función pública interpretar ( matriz $ contexto ) : int { retorno intval ( $ contexto [ $ esto -> nombre ]); } }
/ ** * NonTerminalExpression * / clase abstracta NonTerminalExpression implementa Expression { / ** @var Expression $ left * / protected $ left ; / ** @var? Expresión $ derecha * / protegida $ derecha ; pública función __construct ( Expresión $ abandonó , ? Expresión $ derecha ) { $ this -> izquierda = $ izquierda ; $ esto -> derecha = $ derecha ; } interpretación de función pública abstracta ( matriz $ contexto ) : int ; función pública getRight () { return $ this -> right ; } función pública setRight ( $ right ) : void { $ this -> right = $ right ; } }
/ ** * NonTerminalExpression - PlusExpression * / class PlusExpression extiende NonTerminalExpression { interpretación de función pública ( matriz $ contexto ) : int { retorno intval ( $ esto -> izquierda -> interpretar ( $ contexto ) + $ esto -> derecha -> interpretar ( $ contexto )); } }
/ ** * NonTerminalExpression - MinusExpression * / class MinusExpression extiende NonTerminalExpression { interpretación de función pública ( matriz $ contexto ) : int { retorno intval ( $ esto -> izquierda -> interpretar ( $ contexto ) - $ esto -> derecha -> interpretar ( $ contexto )); } }
/ ** * Cliente * / class InterpreterClient { función protegida parseList ( matriz & $ pila , matriz $ lista , int & $ índice ) { / ** @var cadena $ token * / $ token = $ lista [ $ índice ]; switch ( $ token ) { caso '-' : lista ( $ izquierda , $ derecha ) = $ esto -> fetchArguments ( $ pila , $ lista , $ índice ); return new MinusExpression ( $ izquierda , $ derecha ); case '+' : lista ( $ izquierda , $ derecha ) = $ esto -> fetchArguments ( $ pila , $ lista , $ índice ); return new PlusExpression ( $ izquierda , $ derecha ); predeterminado : devolver nueva TerminalExpression ( $ token ); } } función protegida fetchArguments ( matriz & $ pila , matriz $ lista , int & $ índice ) : matriz { / ** @var Expresión $ izquierda * / $ izquierda = matriz_pop ( $ pila ); / ** @var Expresión $ derecha * / $ derecha = array_pop ( $ pila ); if ( $ derecha === nulo ) { ++ $ índice ; $ esto -> parseListAndPush ( $ pila , $ lista , $ índice ); $ derecha = array_pop ( $ pila ); } matriz de retorno ( $ izquierda , $ derecha ); } función protegida parseListAndPush ( matriz & $ pila , matriz $ lista , int & $ índice ) { matriz_push ( $ pila , $ esto -> parseList ( $ pila , $ lista , $ índice )); } análisis de función protegida ( cadena $ datos ) : Expresión { $ pila = []; $ lista = explotar ( '' , $ datos ); for ( $ índice = 0 ; $ índice < recuento ( $ lista ); $ índice ++ ) { $ esto -> parseListAndPush ( $ pila , $ lista , $ índice ); } return array_pop ( $ pila ); } función pública main () { $ datos = "u + v - w + z" ; $ expr = $ esto -> parse ( $ datos ); $ contexto = [ 'u' => 3 , 'v' => 7 , 'w' => 35 , 'z' => 9 ]; $ res = $ expr -> interpretar ( $ contexto ); echo "resultado: $ res " . PHP_EOL ; } }
// prueba.phpfunction loadClass ( $ className ) { require_once __DIR__ . "/ $ className .php" ; }spl_autoload_register ( 'loadClass' );( new InterpreterClient ()) -> main (); // resultado: -16
PHP (ejemplo 2)
Basado en el ejemplo anterior con otra realización del cliente.
/ ** * Cliente * / class InterpreterClient { public function parseToken ( string $ token , array & $ stack ) : Expression { switch ( $ token ) { case '-' : / ** @var Expression $ left * / $ left = array_pop ( $ pila ); / ** @var Expresión $ derecha * / $ derecha = array_pop ( $ pila ); return new MinusExpression ( $ izquierda , $ derecha ); case '+' : / ** @var Expresión $ izquierda * / $ izquierda = array_pop ( $ pila ); / ** @var Expresión $ derecha * / $ derecha = array_pop ( $ pila ); return new PlusExpression ( $ izquierda , $ derecha ); predeterminado : devolver nueva TerminalExpression ( $ token ); } } análisis de función pública ( cadena $ datos ) : Expresión { $ unfinishedData = null ; $ pila = []; $ lista = explotar ( '' , $ datos ); foreach ( $ lista como $ token ) { $ datos = $ esto -> parseToken ( $ token , $ pila ); if ( ( $ instancia de datos inacabados de NonTerminalExpression ) && ( $ instancia de datos de TerminalExpression ) ) { $ datos inacabados -> setRight ( $ datos ); array_push ( $ pila , $ datos inacabados ); $ datos inacabados = nulo ; continuar ; } if ( $ instancia de datos de NonTerminalExpression ) { if ( $ datos -> getRight () === null ) { $ datos inacabados = $ datos ; continuar ; } } array_push ( $ pila , $ datos ); } return array_pop ( $ pila ); } función pública main () { $ datos = "u + v - w + z" ; $ expr = $ esto -> parse ( $ datos ); $ contexto = [ 'u' => 3 , 'v' => 7 , 'w' => 35 , 'z' => 9 ]; $ res = $ expr -> interpretar ( $ contexto ); echo "resultado: $ res " . PHP_EOL ; } }
JavaScript
Como JavaScript no es compatible con la orientación a objetos real, no implementamos una interfaz.
// expresión Noterminal clase Plus { a ; b ; constructor ( a , b ) { esto . a = a ; esto . b = b ; } interpretar ( contexto ) { devolver esto . a . interpretar ( contexto ) + esto . b . interpretar ( contexto ); } } // Noterminal expresión de clase Minus { a ; b ; constructor ( a , b ) { esto . a = a ; esto . b = b ; } interpretar ( contexto ) { devolver esto . a . interpretar ( contexto ) - esto . b . interpretar ( contexto ); } } // expresión Noterminal clase Tiempos { a ; b ; constructor ( a , b ) { esto . a = a ; esto . b = b ; } interpretar ( contexto ) { devolver esto . a . interpretar ( contexto ) * esto . b . interpretar ( contexto ); } } // expresión Noterminal clase Divide { a ; b ; constructor ( a , b ) { esto . a = a ; esto . b = b ; } interpretar ( contexto ) { devolver esto . a . interpretar ( contexto ) / esto . b . interpretar ( contexto ); } } // Número de clase de expresión terminal { a ; constructor ( a , b ) { esto . a = a ; } interpretar ( contexto ) { devolver esto . a ; } } // Clase de expresión terminal Variable { a ; constructor ( a ) { esto . a = a ; } interpretar ( contexto ) { devolver contexto [ esto . a ] || 0 ; } } // Clase de cliente Parse { context ; constructor ( contexto ) { esto . contexto = contexto ; } parse ( expresión ) { dejar tokens = expresión . dividir ( "" ); dejar cola = []; para ( dejar token de tokens ) { switch ( token ) { caso "+" : var b = cola . pop (); var a = cola . pop (); var exp = nuevo Plus ( a , b ); cola . empujar ( exp ); romper ; caso "/" : var b = cola . pop (); var a = cola . pop (); var exp = nueva división ( a , b ); cola . empujar ( exp ); romper ; caso "*" : var b = cola . pop (); var a = cola . pop (); var exp = nuevos tiempos ( a , b ); cola . empujar ( exp ); romper ; caso "-" : var b = cola . pop (); var a = cola . pop (); var exp = nuevo Menos ( a , b ); cola . empujar ( exp ); romper ; predeterminado : if ( isNaN ( token )) { var exp = nueva Variable ( token ); cola . empujar ( exp ); } else { var number = parseInt ( token ); var exp = nuevo Número ( número ); cola . empujar ( exp ); } romper ; } } deje main = cola . pop (); volver principal . interpretar ( este . contexto ); } } var res = new Parse ({ v : 45 }). analizar ( "16 v * 76 22 - -" ); consola . log ( res ) // 666
Ver también
Referencias
- ^ a b Gamma, Erich ; Helm, Richard ; Johnson, Ralph; Vlissides, John (1994). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison-Wesley. ISBN 0-201-63361-2.
- ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison Wesley. págs. 243ss . ISBN 0-201-63361-2.CS1 maint: varios nombres: lista de autores ( enlace )
- ^ "El patrón de diseño del intérprete: problema, solución y aplicabilidad" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ "El patrón de diseño del intérprete - Estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .