Comment je tokenize l'entrée en utilisant la classe Scanner de Java et les expressions régulières?

StackOverflow https://stackoverflow.com/questions/244115

Question

Juste pour mes propres besoins, j'essaie de créer un tokenizer en Java, dans lequel je peux définir une grammaire normale et la faire passer en entrée basée sur celle-ci. La classe StringTokenizer est obsolète et j'ai trouvé quelques fonctions dans Scanner qui indiquent ce que je veux faire, mais pas de chance pour le moment. Quelqu'un sait-il comment s'y prendre?

Était-ce utile?

La solution

Le nom "Scanner". est un peu trompeur, car le mot est souvent utilisé pour désigner un analyseur lexical, et ce n’est pas à cela que sert Scanner. Tout ce qu’elle est est un substitut de la fonction scanf () que vous trouvez dans C, Perl, et autres . Comme StringTokenizer et split () , il est conçu pour analyser le contenu jusqu'à ce qu'il trouve une correspondance pour un modèle donné et que tout ce qui a été ignoré soit retourné sous forme de jeton.

Un analyseur lexical, en revanche, doit examiner et classifier chaque caractère, même si ce n’est que pour décider s’il peut ou non les ignorer. Cela signifie que, après chaque correspondance, il peut appliquer plusieurs modèles jusqu'à en trouver un qui corresponde à à partir de ce point . Sinon, il peut trouver la séquence "//". et pense que c'est le début d'un commentaire qui se trouve, alors que c'est vraiment à l'intérieur d'un littéral de chaîne et que le guillemet ouvrant n'a pas été remarqué.

C'est en réalité bien plus compliqué que cela, bien sûr, mais j'explique simplement pourquoi les outils intégrés tels que StringTokenizer, split () et Scanner ne conviennent pas à ce type de tâche. . Il est toutefois possible d'utiliser les classes regex de Java pour une forme limitée d'analyse lexicale. En fait, l’ajout de la classe Scanner a grandement facilité la tâche, en raison de la nouvelle API Matcher ajoutée pour la prendre en charge, c’est-à-dire les régions et la méthode usePattern () . Voici un exemple de scanner rudimentaire construit sur les 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);
    }
  }
}

C’est la seule bonne utilisation que j’ai jamais trouvée pour la méthode lookingAt () . : D

Autres conseils

Si je comprends bien votre question, voici deux exemples de méthodes pour tokeniser une chaîne. Vous n'avez même pas besoin de la classe Scanner, uniquement si vous voulez pré-transtyper les jetons, ou les parcourir plus facilement que d'utiliser un tableau. Si un tableau est suffisant, utilisez simplement String.split () comme indiqué ci-dessous.

Veuillez indiquer davantage d'exigences pour permettre des réponses plus précises.

 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);
        }
    }

}

S'il s'agit d'un projet simple (pour apprendre comment les choses fonctionnent), alors suivez ce que dit Balint Pato.

Si cela s’applique à un projet plus volumineux, envisagez plutôt d’utiliser un générateur de scanneurs, tel que JFlex . Un peu plus compliqué, mais plus rapide et plus puissant.

La plupart des réponses ici sont déjà excellentes, mais je m'en voudrais de ne pas signaler ANTLR . J'ai créé des compilateurs entiers autour de cet excellent outil. La version 3 présente des fonctionnalités étonnantes et je le recommande pour tout projet nécessitant l'analyse syntaxique des entrées en fonction d'une grammaire bien définie.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top