2008-10-28 11 views

Odpowiedz

11

Nazwa "Skaner" jest nieco myląca, ponieważ słowo to często jest używane w znaczeniu analizatora leksykalnego, a to nie jest to, do czego służy Skaner. Wszystko to jest substytutem funkcji scanf(), którą można znaleźć w C, Perl, i innych. Podobnie jak StringTokenizer i split(), jest przeznaczony do skanowania z wyprzedzeniem, dopóki nie znajdzie dopasowania dla danego wzorca, a cokolwiek przeskoczyło po drodze, jest zwracane jako token.

Z kolei analizator leksykalny musi badać i klasyfikować każdą postać, nawet jeśli jest to tylko decyzja, czy można ją bezpiecznie zignorować. Oznacza to, że po każdym dopasowaniu może zastosować kilka wzorów, dopóki nie znajdzie takiego, który pasuje do począwszy od tego punktu. W przeciwnym razie może znaleźć sekwencję "//" i pomyśleć, że odnalazł początek komentarza, kiedy jest naprawdę wewnątrz literału i nie zauważył początkowego cudzysłowu.

Oczywiście jest to znacznie bardziej skomplikowane, ale ja po prostu ilustruję, dlaczego wbudowane narzędzia, takie jak StringTokenizer, split() i skaner, nie nadają się do tego rodzaju zadań. Możliwe jest jednak użycie klas regex Java w ograniczonej formie analizy leksykalnej. W rzeczywistości dodanie klasy Scanner znacznie ułatwiło korzystanie z nowego interfejsu API Matchera, który został dodany w celu jego obsługi, tj. Regionów i metody usePattern(). Oto przykład prymitywnego skanera zbudowanego na klasach regex 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); 
    } 
    } 
} 

To, nawiasem mówiąc, jest to jedyny dobry użytek, jaki kiedykolwiek znaleziono metody lookingAt(). : D

+0

Twoja pętla końcowa musi zwiększać liczbę pozycji przed pętlą "dla reguł", na wypadek gdyby reguły nie były prawidłowe?W przeciwnym razie ładny przykład i podziękowania za sugestię lookingAt(). –

+0

Dobry połów. Tak, powinien istnieć "pos ++" zaraz po pętli for. Może to być przykład bez blasku bez sprawdzania błędów, ale przynajmniej powinienem upewnić się, że nie ma żadnych potencjalnych nieskończonych pętli. –

+0

Bardzo podoba mi się to podejście i użyłem tego jako przykładu dla mojego własnego kodu właśnie wczoraj. Zauważyłem jednak, że kolejność list Reguł może wpływać na wyniki. W moim rozwiązaniu próbuję dopasować się do wszystkich reguł, zamiast łamać po pierwszym meczu. Następnie wybieram najdłuższy mecz. –

3

Jeśli dobrze rozumiem twoje pytanie, oto dwie przykładowe metody do tokenizacji ciągu znaków. Nie potrzebujesz nawet klasy Scanner, tylko jeśli chcesz wstępnie rzucić tokeny lub iterować po nich bardziej sofistycznie niż przy użyciu tablicy. Jeśli tablica jest wystarczająca, po prostu użyj String.split() jak podano poniżej.

Podaj więcej wymagań, aby umożliwić bardziej precyzyjne odpowiedzi.

+0

Tak, powinienem był opracować więcej. Jest to pomocne przy dzieleniu ciągu ** wokół ** dopasowań do wyrażenia regularnego, ale nie w celu znalezienia tokenów, które faktycznie pasują do wyrażenia regularnego. – eplawless

2

Jeśli dotyczy to prostego projektu (aby dowiedzieć się, jak to wszystko działa), przejdź do tego, co powiedział Balint Pato.

Jeśli dotyczy to większego projektu, należy zamiast tego użyć generatora skanerów, takiego jak JFlex. Nieco bardziej skomplikowane, ale szybsze i mocniejsze.

+0

Chciałbym również bardzo polecić JFlex do wszystkiego, co nie jest trywialne. Zapisywanie specyfikacji skanera wymaga pewnej praktyki, ale JFlex ma dobre pliki startowe i jest świetną umiejętnością do zdobycia. – Josh

2

Większość odpowiedzi tutaj jest już znakomita, ale byłbym niedbały, gdybym nie wskazał ANTLR. Stworzyłem całe kompilatory wokół tego doskonałego narzędzia. Wersja 3 ma niesamowite funkcje i polecam ją dla każdego projektu, który wymaga od ciebie parsowania danych wejściowych w oparciu o dobrze zdefiniowaną gramatykę.

Powiązane problemy