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 1 Expresiones regulares en Java, Parte 2 Expresiones regulares en Java, Parte 3 Expresiones regulares en Java, Parte 4
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.
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 caracteresc
, 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 PLUS que corresponde únicamente al carácter + , el token real no es necesario ya que puede ser determinado [exclusivamente] por el token. |
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 paqueteca.javajeff.lexan
incluido en el código descargable de este artículo:
Lexan
: analizador léxico;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.
LexanException
: excepción lanzada en el constructor de la claseLexan;
Lexan(java.lang.Class tokensClass)
crea un nuevo analizador léxico. Requiere un argumento en forma de objeto de clase java.lang.Class
correspondiente al tipo constante clase static Token
. Usando la API de Reflection, el constructor lee todas las constantes Token
en una matriz de valores Token[]
. Si Token
no hay constantes, se lanza una excepción LexanException
. La clase Lexan
tambié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 tipoTokLex
. Si se encuentra un carácter que no coincide con ninguno de los patrones de la matrizToken[]
, se genera una excepciónLexException
.
LexanException
no tiene métodos; utiliza un método heredado para devolver un mensaje de excepción getMessage()
. Por el contrario, la clase LexException
proporciona 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.
Token
anula 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 TokLex
proporciona 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,Lexan
escribí una aplicación LexanDemo
. Consta de clases LexanDemo
, BinTokens
, MathTokens
y NoTokens
. El código fuente de la aplicación LexanDemo
se 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 Class
y la cadena a analizar. El método lex()
primero crea un objeto de la clase Lexan
pasándolo Class
al constructor de la clase Lexan
. Y luego llama al método lex()
de clase Lexan
en esa cadena. Si el análisis léxico tiene éxito, TokLex
se 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 TokLex
para 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 LexanException
o 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 MathTokens
describe 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. La posición de la constante Token
en la lista de marcadores es importante. Las constantes ubicadas más arriba en la lista Token
tienen prioridad sobre las ubicadas debajo. Por ejemplo, cuando se encuentra con sin
, Lexan elige la ficha FUNC
en lugar de ID
. Si el marcador ID
hubiera 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 archivolexan.zip
que contiene todos los archivos de la distribución Lexan. Desempaquete este archivo y vaya a un subdirectorio demos
del 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 Неожиданный символ во входном тексте: 20
ocurre como resultado de una excepción lanzada LexanException
debido al hecho de que la clase BinTokens
no declara una constante Token
con un valor 2
como 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 LexException
porque NoTokens
no se declaran constantes en la clase Token
.
Entre bastidores
Lexan
utiliza 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.
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 SDKjava.util.regex
para obtener aún más información sobre las expresiones regulares y los métodos adicionales de la API Regex.
GO TO FULL VERSION