Domanda

Solo per i miei scopi, sto provando a costruire un tokenizer in Java dove posso definire una grammatica regolare e far sì che tokenize input basato su quello. La classe StringTokenizer è obsoleta e ho trovato un paio di funzioni in Scanner che suggeriscono ciò che voglio fare, ma non ho ancora avuto fortuna. Qualcuno sa un buon modo di procedere?

È stato utile?

Soluzione

Il nome " Scanner " è un po 'fuorviante, perché la parola viene spesso usata per indicare un analizzatore lessicale, e non è questo lo scopo di Scanner. Tutto ciò che è è un sostituto della funzione scanf () che trovi in ??C, Perl, et al . Come StringTokenizer e split () , è progettato per eseguire la scansione in avanti fino a quando non trova una corrispondenza per un determinato modello e tutto ciò che è stato ignorato sulla strada viene restituito come token.

Un analizzatore lessicale, d'altra parte, deve esaminare e classificare ogni personaggio, anche se è solo per decidere se può tranquillamente ignorarli. Ciò significa che, dopo ogni corrispondenza, può applicare diversi schemi fino a quando non trova quello corrispondente a a partire da quel punto . Altrimenti, potrebbe trovare la sequenza " // " e penso che abbia trovato l'inizio di un commento, quando è davvero all'interno di una stringa letterale e non è riuscito a notare le virgolette iniziali.

Ovviamente è molto più complicato di così, ovviamente, ma sto solo illustrando perché gli strumenti integrati come StringTokenizer, split () e Scanner non sono adatti per questo tipo di attività . Tuttavia, è possibile utilizzare le classi regex di Java per una forma limitata di analisi lessicale. In effetti, l'aggiunta della classe Scanner ha reso molto più semplice, grazie alla nuova API Matcher che è stata aggiunta per supportarla, ovvero le regioni e il metodo usePattern () . Ecco un esempio di uno scanner rudimentale basato sulle classi regex di 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);
    }
  }
}

Questo, a proposito, è l'unico buon uso che abbia mai trovato per il metodo lookingAt () . : D

Altri suggerimenti

Se capisco bene la tua domanda, ecco due metodi di esempio per tokenizzare una stringa. Non è nemmeno necessaria la classe Scanner, solo se si desidera eseguire il pre-cast dei token o iterarli più sofisticamente rispetto all'utilizzo di un array. Se un array è sufficiente, usa String.split () come indicato di seguito.

Fornisci più requisiti per consentire risposte più precise.

 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 questo è per un semplice progetto (per imparare come funzionano le cose), allora vai con quello che ha detto Balint Pato.

Se questo è per un progetto più ampio, considera invece l'uso di un generatore di scanner come JFlex . Un po 'più complicato, ma più veloce e più potente.

La maggior parte delle risposte qui sono già eccellenti, ma sarei remissivo se non indicassi ANTLR . Ho creato interi compilatori attorno a questo eccellente strumento. La versione 3 ha alcune caratteristiche sorprendenti e lo consiglierei a qualsiasi progetto che richiedesse di analizzare l'input in base a una grammatica ben definita.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top