JavaRush /Blogue Java /Random-PT /Expressões regulares em Java, Parte 5

Expressões regulares em Java, Parte 5

Publicado no grupo Random-PT
Apresentamos a sua atenção a tradução de um breve guia sobre expressões regulares em Java, escrito por Jeff Friesen para o site javaworld. Para facilitar a leitura, dividimos o artigo em várias partes. Esta parte é a última. Expressões regulares em Java, Parte 5 - 1Expressões regulares em Java, Parte 1 Expressões regulares em Java, Parte 2 Expressões regulares em Java, Parte 3 Expressões regulares em Java, Parte 4

Usando expressões regulares para análise lexical

Uma aplicação ainda mais útil de expressões regulares é uma biblioteca de código reutilizável para realizar análises lexicais, um componente chave de qualquer compilador ou montador. Nesse caso, o fluxo de entrada de caracteres é agrupado em um fluxo de saída de tokens - nomes para sequências de caracteres que possuem um significado comum. Por exemplo, tendo encontrado a sequência de caracteres c, o, u, n, t, e, r, no fluxo de entrada, o analisador léxico pode gerar um token ID(identificador). A sequência de caracteres correspondente ao token é chamada de lexema.
Mais sobre marcadores e lexemas
Tokens como ID podem corresponder a muitas sequências de caracteres. No caso de tais tokens, o token real correspondente ao token também é necessário para o compilador, montador ou outro utilitário que exija análise lexical. Para tokens que representam uma sequência específica de caracteres, como o token PLUScorrespondente apenas ao caractere +, o token real não é necessário, pois pode ser determinado [exclusivamente] pelo token.
As expressões regulares são muito mais eficientes que os analisadores lexicais baseados em estado, que devem ser escritos à mão e geralmente não podem ser reutilizados. Um exemplo de analisador léxico baseado em expressões regulares é o JLex , um gerador léxico para a linguagem Java que usa expressões regulares para definir regras para dividir um fluxo de dados de entrada em tokens. Outro exemplo é Lexan.

Conhecendo Lexan

Lexan é uma biblioteca Java reutilizável para análise lexical. Ele é baseado no código da série de postagens do blog Writing a Parser in Java no site Cogito Learning . A biblioteca consiste nas seguintes classes, que estão no pacote ca.javajeff.lexanincluído no código para download deste artigo:
  • Lexan: analisador léxico;
  • LexanException: exceção lançada no construtor da classeLexan;
  • LexException: exceção lançada se sintaxe incorreta for detectada durante a análise lexical;
  • Token: nome com atributo de expressão regular;
  • TokLex: par token/token.
O construtor Lexan(java.lang.Class tokensClass)cria um novo analisador léxico. Requer um argumento na forma de um objeto de classe java.lang.Classcorrespondente à constante de tipo class static Token. Usando a API Reflection, o construtor lê todas as constantes Tokenem uma matriz de valores Token[]. Se Tokennão houver constantes, uma exceção será lançada LexanException. Expressões regulares em Java, Parte 5 - 2A classe Lexantambém fornece os dois métodos a seguir:
  • O método retorna uma lista deste lexer;List getTokLexes() Token
  • Метод void lex(String str)realiza uma análise lexical da string de entrada [com o resultado colocado] em uma lista de valores do tipo TokLex. Se for encontrado um caractere que não corresponda a nenhum dos padrões do array Token[], uma exceção será lançada LexException.
A classe LexanExceptionnão possui métodos; ela usa um método herdado para retornar uma mensagem de exceção getMessage(). Por outro lado, a classe LexExceptionfornece os seguintes métodos:
  • O método int getBadCharIndex()retorna a posição de um caractere que não corresponde a nenhum dos padrões do marcador.
  • O método String getText()retorna o texto que foi analisado quando a exceção foi gerada.
A classe Tokensubstitui o método toString()para retornar o nome do marcador. Ele também fornece um método String getPattern()que retorna o atributo de expressão regular do token. A classe TokLexfornece um método Token getToken()que retorna seu token. Ele também fornece um método String getLexeme()que retorna seu token.

Demonstração da biblioteca Lexan

Para demonstrar como a biblioteca funciona, Lexanescrevi um aplicativo LexanDemo. Consiste em classes LexanDemo, BinTokens, MathTokense NoTokens. O código-fonte do aplicativo LexanDemoé mostrado na Listagem 2. Listagem 2. Demonstração da biblioteca Lexan em ação
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();
   }
}
O método main()na Listagem 2 chama um utilitário lex()para demonstrar a análise lexical usando Lexan. Cada chamada para esse método recebe a classe dos tokens no objeto Classe a string a ser analisada. O método lex()primeiro cria um objeto da classe Lexanpassando o objeto Classpara o construtor da classe Lexan. E então chama o método lex()de classe Lexannessa string. Se a análise lexical for bem-sucedida, o TokLexmétodo getTokLexes()da classe será chamado para retornar uma lista de objetos Lexan. Para cada um desses objetos, seu método getToken()de classe é chamado TokLexpara retornar o token e seu método de classe getLexeme()para retornar o token. Ambos os valores são impressos na saída padrão. LexanExceptionSe a análise lexical falhar, uma das exceções ou será lançada e tratada adequadamente LexException. Para resumir, vamos considerar apenas a classe que compõe esta aplicação MathTokens. A Listagem 3 mostra seu código fonte. Listagem 3. Descrição de um conjunto de tokens para uma pequena linguagem matemática
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_]*");
}
A Listagem 3 mostra que a classe MathTokensdescreve uma sequência de constantes do tipo Token. A cada um deles é atribuído o valor de um objeto Token. O construtor deste objeto recebe uma string que é o nome do marcador, juntamente com uma expressão regular que descreve todas as strings de caracteres associadas a esse marcador. Para maior clareza, é desejável que o nome da string do marcador seja igual ao nome da constante, mas isso não é obrigatório. Expressões regulares em Java, Parte 5 - 3A posição da constante Tokenna lista de marcadores é importante. As constantes localizadas no topo da lista Tokentêm precedência sobre as localizadas abaixo. Por exemplo, ao encontrar sin, Lexan escolhe o token FUNCem vez de ID. Se o marcador IDtivesse precedido o marcador FUNC, ele teria sido selecionado.

Compilando e executando o aplicativo LexanDemo

O código para download deste artigo inclui um arquivo lexan.zipcontendo todos os arquivos da distribuição Lexan. Descompacte este arquivo e vá para um subdiretório demosdo diretório raiz lexan. Se você estiver usando o Windows, execute o seguinte comando para compilar os arquivos de código-fonte do aplicativo de demonstração:
javac -cp ..\library\lexan.jar *.java
Se a compilação for bem-sucedida, execute o seguinte comando para executar o aplicativo de demonstração:
java -cp ..\library\lexan.jar;. LexanDemo
Você deverá ver os seguintes 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
A mensagem Неожиданный символ во входном тексте: 20ocorre como resultado de uma exceção lançada LexanExceptiondevido ao fato da classe BinTokensnão declarar uma constante Tokencom um valor 2como expressão regular. Observe que o manipulador de exceções gera a posição do caractere inadequado obtido na análise lexical do texto. A mensagem de falta de tokens é o resultado de uma exceção lançada LexExceptionporque NoTokensnenhuma constante foi declarada na classe Token.

Por trás das cenas

Lexanusa a classe Lexan como mecanismo. Dê uma olhada na implementação desta classe na Listagem 4 e observe a contribuição das expressões regulares para tornar o mecanismo reutilizável. Listagem 4. Criando uma arquitetura de analisador léxico baseada em expressões regulares
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

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

public final class Lexan
{
   private List tokLexes;

   private Token[] values;

   /**
    *  Инициализируем лексический анализатор набором an objectов Token.
    *
    *  @параметры tokensClass – an object Class класса, содержащего
    *       набор an objectов Token
    *
    *  @генерирует исключение LexanException в случае невозможности
    *       формирования an object Lexan, возможно, из-за отсутствия an objectов
    *       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);
      }
   }
}
O código do método lex()é baseado no código fornecido na postagem do blog "Writing a Parser in Java: A Token Generator" no site Cogito Learning. Leia esta postagem para saber mais sobre como Lexan usa a API Regex para compilar código. Expressões regulares em Java, Parte 5 a 4

Conclusão

Expressões regulares são uma ferramenta útil que pode ser útil para qualquer desenvolvedor. A API Regex da linguagem de programação Java facilita seu uso em aplicativos e bibliotecas. Agora que você já tem um conhecimento básico de expressões regulares e desta API, dê uma olhada na documentação do SDK java.util.regexpara aprender ainda mais sobre expressões regulares e métodos adicionais da API Regex.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION