Pregunta

Solo para mis propios propósitos, estoy tratando de construir un tokenizador en Java donde pueda definir una gramática regular y hacer que sea tokenize la entrada basada en eso. La clase StringTokenizer está en desuso, y he encontrado un par de funciones en el escáner que apuntan a lo que quiero hacer, pero aún no hay suerte. ¿Alguien sabe una buena manera de hacer esto?

¿Fue útil?

Solución

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

Por otro lado, un analizador léxico tiene que examinar y clasificar cada carácter, incluso si es solo para decidir si puede ignorarlos de forma segura. Eso significa que, después de cada coincidencia, puede aplicar varios patrones hasta que encuentre uno que coincida con comenzando en ese punto . De lo contrario, puede encontrar la secuencia " // " y creo que se encuentra al principio de un comentario, cuando está realmente dentro de un literal de cadena y simplemente no se nota 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, las 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);
    }
  }
}

Por cierto, este es el único buen uso que he encontrado para el método lookingAt () . : D

Otros consejos

Si entiendo bien tu pregunta, aquí hay dos métodos de ejemplo para crear una cadena. Ni siquiera necesita la clase de Escáner, solo si desea prefundir los tokens, o iterar a través de ellos de manera más suave que usando una matriz. Si una matriz es suficiente, simplemente use String.split () como se indica a continuación.

Por favor, indique 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);
        }
    }

}

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

Si esto es para un proyecto más grande, considere usar un generador de escáner como JFlex en su lugar. Algo más complicado, pero más rápido y más poderoso.

La mayoría de las respuestas aquí ya son excelentes, pero sería negligente si no señalara ANTLR . He creado compiladores completos alrededor de esta excelente herramienta. La versión 3 tiene algunas características sorprendentes y lo recomendaría para cualquier proyecto que requiera que analices las entradas en función de una gramática bien definida.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top