Pergunta

Apenas para minhas próprias finalidades, estou tentando construir um tokenizer em Java onde eu possa definir uma gramática regular e tê-lo tokenize de entrada com base nisso. A classe StringTokenizer é obsoleto, e eu encontrei um par de funções no Scanner essa dica para o que eu quero fazer, mas sem sorte ainda. Alguém sabe uma boa maneira de ir sobre isso?

Foi útil?

Solução

O nome "Scanner" é um pouco enganador, porque a palavra é muitas vezes usado para significar um analisador léxico, e não é isso que Scanner é para. Tudo o que é um substituto para a função scanf() você encontra em C, Perl, et al . Como StringTokenizer e split(), ele é projetado para fazer a varredura em frente até encontrar uma correspondência para um determinado padrão, e seja o que pulou no caminho é retornado como um token.

A lexical analisador, por outro lado, tem de examinar e classificar cada personagem, mesmo que seja apenas para decidir se pode ignorá-los com segurança. Isso significa que, depois de cada jogo, pode aplicar vários padrões até encontrar um que corresponde a a partir de que ponto . Caso contrário, pode encontrar a seqüência "//" e acho que é encontrado no início de um comentário, quando é realmente dentro de um literal de cadeia e ele simplesmente deixou de notar as aspas de abertura.

Na verdade, é muito mais complicado do que isso, é claro, mas eu estou apenas ilustrando porque o built-in ferramentas como StringTokenizer, split() e Scanner não são adequados para este tipo de tarefa. É, no entanto, possível usar classes regex de Java para uma forma limitada de análise lexical. Na verdade, a adição da classe Scanner tornou muito mais fácil, por causa da nova API Matcher que foi adicionado para apoiá-lo, ou seja, regiões e o método usePattern(). Aqui está um exemplo de um scanner rudimentar construído em cima das classes regex 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);
    }
  }
}

Esta, aliás, é o único uso bom que eu já encontrei para o método lookingAt(). : D

Outras dicas

Se entendi sua pergunta bem, então aqui estão dois métodos de exemplo para tokenize uma corda. Você não precisa mesmo da classe Scanner, só se você quiser pré-moldado as fichas, ou iterate através deles mais sofistically do que usando uma matriz. Se uma matriz é suficiente apenas usar String.split () como dado abaixo.

Por favor, dar mais requisitos para permitir respostas mais 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);
        }
    }

}

Se isto é para um projeto simples (para aprender como as coisas funcionam), em seguida, ir com o que Balint Pato disse.

Se isto é para um projeto maior, considere o uso de um gerador de scanner como JFlex . Um pouco mais complicado, mas mais rápido e mais poderoso.

A maioria das respostas aqui já são excelentes, mas eu seria negligente se eu não apontar ANTLR . Criei compiladores inteiras em torno deste excelente ferramenta. Versão 3 tem algumas características surpreendentes e eu recomendo-o para qualquer projeto que exigia que você para analisar entrada com base em uma gramática bem definido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top