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 1 Expressões regulares em Java, Parte 2 Expressões regulares em Java, Parte 3 Expressões regulares em Java, Parte 4
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.
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 caracteresc
, 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 PLUS correspondente apenas ao caractere + , o token real não é necessário, pois pode ser determinado [exclusivamente] pelo token. |
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 pacoteca.javajeff.lexan
incluído no código para download deste artigo:
Lexan
: analisador léxico;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.
LexanException
: exceção lançada no construtor da classeLexan;
Lexan(java.lang.Class tokensClass)
cria um novo analisador léxico. Requer um argumento na forma de um objeto de classe java.lang.Class
correspondente à constante de tipo class static Token
. Usando a API Reflection, o construtor lê todas as constantes Token
em uma matriz de valores Token[]
. Se Token
não houver constantes, uma exceção será lançada LexanException
. A classe Lexan
també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 tipoTokLex
. Se for encontrado um caractere que não corresponda a nenhum dos padrões do arrayToken[]
, uma exceção será lançadaLexException
.
LexanException
não possui métodos; ela usa um método herdado para retornar uma mensagem de exceção getMessage()
. Por outro lado, a classe LexException
fornece 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.
Token
substitui 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 TokLex
fornece 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,Lexan
escrevi um aplicativo LexanDemo
. Consiste em classes LexanDemo
, BinTokens
, MathTokens
e 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 Class
e a string a ser analisada. O método lex()
primeiro cria um objeto da classe Lexan
passando o objeto Class
para o construtor da classe Lexan
. E então chama o método lex()
de classe Lexan
nessa string. Se a análise lexical for bem-sucedida, o TokLex
mé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 TokLex
para 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. LexanException
Se 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 MathTokens
descreve 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. A posição da constante Token
na lista de marcadores é importante. As constantes localizadas no topo da lista Token
têm precedência sobre as localizadas abaixo. Por exemplo, ao encontrar sin
, Lexan escolhe o token FUNC
em vez de ID
. Se o marcador ID
tivesse precedido o marcador FUNC
, ele teria sido selecionado.
Compilando e executando o aplicativo LexanDemo
O código para download deste artigo inclui um arquivolexan.zip
contendo todos os arquivos da distribuição Lexan. Descompacte este arquivo e vá para um subdiretório demos
do 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 Неожиданный символ во входном тексте: 20
ocorre como resultado de uma exceção lançada LexanException
devido ao fato da classe BinTokens
não declarar uma constante Token
com um valor 2
como 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 LexException
porque NoTokens
nenhuma constante foi declarada na classe Token
.
Por trás das cenas
Lexan
usa 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.
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 SDKjava.util.regex
para aprender ainda mais sobre expressões regulares e métodos adicionais da API Regex.
GO TO FULL VERSION