JavaRush /Blog Java /Random-ES /Expresiones regulares en Java, Parte 5

Expresiones regulares en Java, Parte 5

Publicado en el grupo Random-ES
Presentamos a su atención una traducción de una breve guía sobre expresiones regulares en Java, escrita por Jeff Friesen para el sitio web javaworld. Para facilitar la lectura, hemos dividido el artículo en varias partes. Esta parte es la final. Expresiones regulares en Java, Parte 5 - 1Expresiones regulares en Java, Parte 1 Expresiones regulares en Java, Parte 2 Expresiones regulares en Java, Parte 3 Expresiones regulares en Java, Parte 4

Usar expresiones regulares para el análisis léxico

Una aplicación aún más útil de las expresiones regulares es una biblioteca de código reutilizable para realizar análisis léxicos, un componente clave de cualquier compilador o ensamblador. En este caso, el flujo de caracteres de entrada se agrupa en un flujo de tokens de salida: nombres de secuencias de caracteres que tienen un significado común. Por ejemplo, al tropezar con la secuencia de caracteres c, o, u, n, t, e, r, en el flujo de entrada, el analizador léxico puede generar un token ID(identificador). La secuencia de caracteres correspondiente al token se llama lexema.
Más sobre marcadores y lexemas
Los tokens como la identificación pueden coincidir con muchas secuencias de caracteres. En el caso de dichos tokens, el compilador, ensamblador u otra utilidad que requiera análisis léxico también necesita el token real correspondiente al token. Para los tokens que representan una secuencia específica de caracteres, como el token PLUSque corresponde únicamente al carácter +, el token real no es necesario ya que puede ser determinado [exclusivamente] por el token.
Las expresiones regulares son mucho más eficientes que los analizadores léxicos basados ​​en estados, que deben escribirse a mano y, por lo general, no pueden reutilizarse. Un ejemplo de analizador léxico basado en expresiones regulares es JLex , un generador léxico para el lenguaje Java que utiliza expresiones regulares para definir reglas para dividir un flujo de datos de entrada en tokens. Otro ejemplo es Lexan.

Conociendo a Lexan

Lexan es una biblioteca Java reutilizable para análisis léxico. Se basa en el código de la serie de publicaciones del blog Writing a Parser in Java en el sitio web de Cogito Learning . La biblioteca consta de las siguientes clases, que se encuentran en el paquete ca.javajeff.lexanincluido en el código descargable de este artículo:
  • Lexan: analizador léxico;
  • LexanException: excepción lanzada en el constructor de la claseLexan;
  • LexException: se lanza una excepción si se detecta una sintaxis incorrecta durante el análisis léxico;
  • Token: nombre con un atributo de expresión regular;
  • TokLex: par token/token.
El constructor Lexan(java.lang.Class tokensClass)crea un nuevo analizador léxico. Requiere un argumento en forma de objeto de clase java.lang.Classcorrespondiente al tipo constante clase static Token. Usando la API de Reflection, el constructor lee todas las constantes Tokenen una matriz de valores Token[]. Si Tokenno hay constantes, se lanza una excepción LexanException. Expresiones regulares en Java, Parte 5 - 2La clase Lexantambién proporciona los dos métodos siguientes:
  • El método devuelve una lista de este lexer;List getTokLexes() Token
  • Метод void lex(String str)realiza un análisis léxico de la cadena de entrada [con el resultado colocado] en una lista de valores de tipo TokLex. Si se encuentra un carácter que no coincide con ninguno de los patrones de la matriz Token[], se genera una excepción LexException.
La clase LexanExceptionno tiene métodos; utiliza un método heredado para devolver un mensaje de excepción getMessage(). Por el contrario, la clase LexExceptionproporciona los siguientes métodos:
  • El método int getBadCharIndex()devuelve la posición de un carácter que no coincide con ninguno de los patrones de marcador.
  • El método String getText()devuelve el texto que se analizó cuando se generó la excepción.
La clase Tokenanula el método toString()para devolver el nombre del marcador. También proporciona un método String getPattern()que devuelve el atributo de expresión regular del token. La clase TokLexproporciona un método Token getToken()que devuelve su token. También proporciona un método String getLexeme()que devuelve su token.

Demostración de la biblioteca Lexan

Para demostrar cómo funciona la biblioteca, Lexanescribí una aplicación LexanDemo. Consta de clases LexanDemo, BinTokens, MathTokensy NoTokens. El código fuente de la aplicación LexanDemose muestra en el Listado 2. Listado 2. Demostración de la biblioteca Lexan en acción
import ca.javajeff.lexan.Lexan;
import ca.javajeff.lexan.LexanException;
import ca.javajeff.lexan.LexException;
import ca.javajeff.lexan.TokLex;

public final class LexanDemo
{
   public static void main(String[] args)
   {
      lex(MathTokens.class, " sin(x) * (1 + var_12) ");
      lex(BinTokens.class, " 1 0 1 0 1");
      lex(BinTokens.class, "110");
      lex(BinTokens.class, "1 20");
      lex(NoTokens.class, "");
   }

   private static void lex(Class tokensClass, String text)
   {
      try
      {
         Lexan lexan = new Lexan(tokensClass);
         lexan.lex(text);
         for (TokLex tokLex: lexan.getTokLexes())
            System.out.printf("%s: %s%n", tokLex.getToken(),
                              tokLex.getLexeme());
      }
      catch (LexanException le)
      {
         System.err.println(le.getMessage());
      }
      catch (LexException le)
      {
         System.err.println(le.getText());
         for (int i = 0; i < le.getBadCharIndex(); i++)
            System.err.print("-");
         System.err.println("^");
         System.err.println(le.getMessage());
      }
      System.out.println();
   }
}
El método main()del Listado 2 llama a una utilidad lex()para demostrar el análisis léxico utilizando Lexan. A cada llamada a este método se le pasa la clase de los tokens en el objeto Classy la cadena a analizar. El método lex()primero crea un objeto de la clase Lexanpasándolo Classal constructor de la clase Lexan. Y luego llama al método lex()de clase Lexanen esa cadena. Si el análisis léxico tiene éxito, TokLexse llama al método getTokLexes()de clase para devolver una lista de objetos Lexan. Para cada uno de estos objetos, se llama a su método getToken()de clase TokLexpara devolver el token y a su método de clase getLexeme()para devolver el token. Ambos valores se imprimen en la salida estándar. Si el análisis léxico falla, se lanza una de las excepciones LexanExceptiono y se maneja en consecuencia LexException. Por brevedad, consideremos solo la clase que conforma esta aplicación MathTokens. El Listado 3 muestra su código fuente. Listado 3. Descripción de un conjunto de tokens para un pequeño lenguaje matemático
import ca.javajeff.lexan.Token;

public final class MathTokens
{
   public final static Token FUNC = new Token("FUNC", "sin|cos|exp|ln|sqrt");
   public final static Token LPAREN = new Token("LPAREN", "\\(");
   public final static Token RPAREN = new Token("RPAREN", "\\)");
   public final static Token PLUSMIN = new Token("PLUSMIN", "[+-]");
   public final static Token TIMESDIV = new Token("TIMESDIV", "[*/]");
   public final static Token CARET = new Token("CARET", "\\^");
   public final static Token INTEGER = new Token("INTEGER", "[0-9]+");
   public final static Token ID = new Token("ID", "[a-zA-Z][a-zA-Z0-9_]*");
}
El Listado 3 muestra que la clase MathTokensdescribe una secuencia de constantes de tipo Token. A cada uno de ellos se le asigna el valor de un objeto Token. El constructor de este objeto recibe una cadena que es el nombre del marcador, junto con una expresión regular que describe todas las cadenas de caracteres asociadas con ese marcador. Para mayor claridad, es deseable que el nombre de cadena del marcador sea el mismo que el nombre de la constante, pero esto no es obligatorio. Expresiones regulares en Java, Parte 5 - 3La posición de la constante Tokenen la lista de marcadores es importante. Las constantes ubicadas más arriba en la lista Tokentienen prioridad sobre las ubicadas debajo. Por ejemplo, cuando se encuentra con sin, Lexan elige la ficha FUNCen lugar de ID. Si el marcador IDhubiera precedido al marcador FUNC, habría sido seleccionado.

Compilación y ejecución de la aplicación LexanDemo

El código descargable de este artículo incluye un archivo lexan.zipque contiene todos los archivos de la distribución Lexan. Desempaquete este archivo y vaya a un subdirectorio demosdel directorio raíz lexan. Si está utilizando Windows, ejecute el siguiente comando para compilar los archivos de código fuente de la aplicación de demostración:
javac -cp ..\library\lexan.jar *.java
Si la compilación se realiza correctamente, ejecute el siguiente comando para ejecutar la aplicación de demostración:
java -cp ..\library\lexan.jar;. LexanDemo
Deberías ver los siguientes resultados:
FUNC: sin
LPAREN: (
ID: x
RPAREN: )
TIMESDIV: *
LPAREN: (
INTEGER: 1
PLUSMIN: +
ID: var_12
RPAREN: )
ONE: 1
ZERO: 0
ONE: 1
ZERO: 0
ONE: 1
ONE: 1
ONE: 1
ZERO: 0
1 20
--^
Неожиданный символ во входном тексте: 20
El mensaje Неожиданный символ во входном тексте: 20ocurre como resultado de una excepción lanzada LexanExceptiondebido al hecho de que la clase BinTokensno declara una constante Tokencon un valor 2como expresión regular. Tenga en cuenta que el controlador de excepciones genera la posición del carácter inapropiado obtenido del análisis léxico del texto. El mensaje de tokens faltantes es el resultado de que se lanzó una excepción LexExceptionporque NoTokensno se declaran constantes en la clase Token.

Entre bastidores

Lexanutiliza la clase Lexan como motor. Observe la implementación de esta clase en el Listado 4 y observe la contribución de las expresiones regulares para que el motor sea reutilizable. Listado 4. Creación de una arquitectura de analizador léxico basada en expresiones regulares
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

/**
 *  Лексический анализатор. Этот класс можно использовать для
 *  преобразования входного потока символов в выходной поток маркеров.
 *
 *  @Autor Джефф Фризен
 */

public final class Lexan
{
   private List tokLexes;

   private Token[] values;

   /**
    *  Инициализируем лексический анализатор набором un objetoов Token.
    *
    *  @параметры tokensClass – un objeto Class класса, содержащего
    *       набор un objetoов Token
    *
    *  @генерирует исключение LexanException в случае невозможности
    *       формирования un objetoа Lexan, возможно, из-за отсутствия un objetoов
    *       Token в классе
    */

   public Lexan(Class tokensClass) throws LexanException
   {
      try
      {
         tokLexes = new ArrayList<>();
         List _values = new ArrayList<>();
         Field[] fields = tokensClass.getDeclaredFields();
         for (Field field: fields)
            if (field.getType().getName().equals("ca.javajeff.lexan.Token"))
               _values.add((Token) field.get(null));
         values = _values.toArray(new Token[0]);
         if (values.length == 0)
            throw new LexanException("маркеры отсутствуют");
      }
      catch (IllegalAccessException iae)
      {
         throw new LexanException(iae.getMessage());
      }

   /**
    * Получаем список TokLex'ов этого лексического анализатора.
    *
    *  @возвращает список TokLex'ов
    */

   public List getTokLexes()
   {
      return tokLexes;
   }

   /** * Выполняет лексический анализ входной строки [с помещением * результата] в список TokLex'ов. * * @параметры str – строка, подвергаемая лексическому анализу * * @генерирует исключение LexException: во входных данных обнаружен * неожиданный символ */

   public void lex(String str) throws LexException
   {
      String s = new String(str).trim(); // удалить ведущие пробелы
      int index = (str.length() - s.length());
      tokLexes.clear();
      while (!s.equals(""))
      {
         boolean match = false;
         for (int i = 0; i < values.length; i++)
         {
            Token token = values[i];
            Matcher m = token.getPattern().matcher(s);
            if (m.find())
            {
               match = true;
               tokLexes.add(new TokLex(token, m.group().trim()));
               String t = s;
               s = m.replaceFirst("").trim(); // удалить ведущие пробелы
               index += (t.length() - s.length());
               break;
            }
         }
         if (!match)
            throw new LexException("Неожиданный символ во входном тексте: "
                                    + s, str, index);
      }
   }
}
El código del método lex()se basa en el código proporcionado en la publicación del blog "Escribir un analizador en Java: un generador de tokens" en el sitio web de Cogito Learning. Lea esta publicación para obtener más información sobre cómo Lexan usa la API Regex para compilar código. Expresiones regulares en Java, partes 5 - 4

Conclusión

Las expresiones regulares son una herramienta útil que puede resultar útil para cualquier desarrollador. La API Regex del lenguaje de programación Java los hace fáciles de usar en aplicaciones y bibliotecas. Ahora que ya tiene un conocimiento básico de las expresiones regulares y esta API, eche un vistazo a la documentación del SDK java.util.regexpara obtener aún más información sobre las expresiones regulares y los métodos adicionales de la API Regex.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION