2008-10-28 8 views
5

Solo para mis propios fines, intento crear un tokenizador en Java donde pueda definir una gramática regular y hacer que la entrada se convierta en tokens en función de eso. La clase StringTokenizer está en desuso, y he encontrado un par de funciones en Scanner que insinúan lo que quiero hacer, pero todavía no tengo suerte. Alguien sabe una buena manera de hacer esto?¿Cómo puedo tokenizar la entrada utilizando la clase de escáner de Java y las expresiones regulares?

Respuesta

11

El nombre de "escáner" es un poco engañoso, porque la palabra se utiliza a menudo para referirse a un analizador léxico, y eso no es lo que es para escáner. Todo lo que es es un sustituto de la función scanf() que se encuentra en C, Perl, y otros. Al igual que StringTokenizer y split(), está diseñado para escanear hacia adelante hasta que encuentre una coincidencia para un patrón dado, y todo lo que omitió en el camino se devuelve como un token.

Un analizador léxico, por el contrario, tiene que examinar y clasificar todos los personajes, incluso si es sólo para decidir si se puede ignorar de forma segura. Eso significa que, después de cada partida, puede aplicar varios patrones hasta que encuentre uno que coincida con comenzando en ese punto. De lo contrario, puede encontrar la secuencia "//" y pensar que se encontró el comienzo de un comentario, cuando está realmente dentro de un literal de cadena y simplemente no notó la comilla de apertura.

En realidad es mucho más complicado que eso, por supuesto, pero solo estoy ilustrando por qué las herramientas integradas como StringTokenizer, split() y Scanner no son adecuadas para este tipo de tareas. Sin embargo, es posible utilizar las clases de expresiones regulares de Java para una forma limitada de análisis léxico. De hecho, la adición de la clase Scanner lo hizo mucho más fácil, debido a la nueva API de Matcher que se agregó para admitirlo, es decir, regiones y el método usePattern(). Aquí hay un ejemplo de un escáner rudimentario construido sobre las clases de expresiones regulares de Java.

import java.util.*; 
import java.util.regex.*; 

public class RETokenizer 
{ 
    static List<Token> tokenize(String source, List<Rule> rules) 
    { 
    List<Token> tokens = new ArrayList<Token>(); 
    int pos = 0; 
    final int end = source.length(); 
    Matcher m = Pattern.compile("dummy").matcher(source); 
    m.useTransparentBounds(true).useAnchoringBounds(false); 
    while (pos < end) 
    { 
     m.region(pos, end); 
     for (Rule r : rules) 
     { 
     if (m.usePattern(r.pattern).lookingAt()) 
     { 
      tokens.add(new Token(r.name, m.start(), m.end())); 
      pos = m.end(); 
      break; 
     } 
     } 
     pos++; // bump-along, in case no rule matched 
    } 
    return tokens; 
    } 

    static class Rule 
    { 
    final String name; 
    final Pattern pattern; 

    Rule(String name, String regex) 
    { 
     this.name = name; 
     pattern = Pattern.compile(regex); 
    } 
    } 

    static class Token 
    { 
    final String name; 
    final int startPos; 
    final int endPos; 

    Token(String name, int startPos, int endPos) 
    { 
     this.name = name; 
     this.startPos = startPos; 
     this.endPos = endPos; 
    } 

    @Override 
    public String toString() 
    { 
     return String.format("Token [%2d, %2d, %s]", startPos, endPos, name); 
    } 
    } 

    public static void main(String[] args) throws Exception 
    { 
    List<Rule> rules = new ArrayList<Rule>(); 
    rules.add(new Rule("WORD", "[A-Za-z]+")); 
    rules.add(new Rule("QUOTED", "\"[^\"]*+\"")); 
    rules.add(new Rule("COMMENT", "//.*")); 
    rules.add(new Rule("WHITESPACE", "\\s+")); 

    String str = "foo //in \"comment\"\nbar \"no //comment\" end"; 
    List<Token> result = RETokenizer.tokenize(str, rules); 
    for (Token t : result) 
    { 
     System.out.println(t); 
    } 
    } 
} 

Esto, por cierto, es el único uso bueno que he encontrado para el método lookingAt(). : D

+0

Su bucle de

+0

Buena captura. Sí, debería haber un 'pos ++' justo después del for-loop. Este puede ser un ejemplo escueto sin verificación de errores, pero al menos debería haberme asegurado de que no tuviera ningún bucle infinito potencial. –

+0

Me gusta mucho este enfoque y lo usé como ejemplo para mi propio código ayer. Sin embargo, me di cuenta de que el orden de la lista de reglas puede afectar los resultados. En mi solución, intento hacer coincidir todas las reglas en lugar de romper después del primer partido. Luego selecciono la coincidencia más larga. –

3

Si entiendo su pregunta bien, entonces aquí hay dos métodos de ejemplo para tokenizar una cadena. Ni siquiera necesita la clase de escáner, solo si desea prefabricar los tokens, o iterar a través de ellos de forma más sofisticada que utilizando una matriz. Si una matriz es suficiente, simplemente use String.split() como se indica a continuación.

Proporcione más requisitos para permitir respuestas más precisas.

import java.util.Scanner; 


    public class Main {  

    public static void main(String[] args) { 

     String textToTokenize = "This is a text that will be tokenized. I will use 1-2 methods."; 
     Scanner scanner = new Scanner(textToTokenize); 
     scanner.useDelimiter("i."); 
     while (scanner.hasNext()){ 
      System.out.println(scanner.next()); 
     } 

     System.out.println(" **************** "); 
     String[] sSplit = textToTokenize.split("i."); 

     for (String token: sSplit){ 
      System.out.println(token); 
     } 
    } 

} 
+0

Sí, debería haber elaborado más. Eso es útil para dividir una cadena ** en ** coincidencias con una expresión regular, pero no para encontrar los tokens que coincidan con la expresión regular. – eplawless

2

Si se trata de un proyecto simple (para aprender cómo funcionan las cosas), entonces vaya con lo que dijo Balint Pato.

Si se trata de un proyecto más amplio, considerar el uso de un generador de escáner como JFlex lugar. Algo más complicado, pero más rápido y más poderoso.

+0

También recomendaría JFlex para cualquier cosa no trivial. Escribir especificaciones de escáner requiere algo de práctica, pero JFlex tiene buenos archivos de inicio y es una gran habilidad para adquirir. – Josh

2

La mayoría de las respuestas aquí son excelentes, pero sería negligente si no señalara ANTLR. Creé compiladores completos en torno a esta excelente herramienta. La versión 3 tiene algunas características increíbles y la recomendaría para cualquier proyecto que requiriera que analizara la entrada en base a una gramática bien definida.

Cuestiones relacionadas