JavaRush /Blog Java /Random-PL /Wyrażenia regularne w Javie, część 5

Wyrażenia regularne w Javie, część 5

Opublikowano w grupie Random-PL
Przedstawiamy Państwu tłumaczenie krótkiego przewodnika po wyrażeniach regularnych w Javie, napisanego przez Jeffa Friesena dla serwisu javaworld. Dla ułatwienia czytania artykuł podzieliliśmy na kilka części. Ta część jest ostateczna. Wyrażenia regularne w Javie, część 5 - 1Wyrażenia regularne w Javie, część 1 Wyrażenia regularne w Javie, część 2 Wyrażenia regularne w Javie, część 3 Wyrażenia regularne w Javie, część 4

Wykorzystanie wyrażeń regularnych do analizy leksykalnej

Jeszcze bardziej użytecznym zastosowaniem wyrażeń regularnych jest biblioteka kodu wielokrotnego użytku do przeprowadzania analizy leksykalnej, kluczowego elementu każdego kompilatora lub asemblera. W tym przypadku wejściowy strumień znaków grupowany jest w wyjściowy strumień tokenów – nazw ciągów znaków mających wspólne znaczenie. Na przykład, po natknięciu się na sekwencję znaków c, o, u, n, t, e, r, w strumieniu wejściowym, analizator leksykalny może wyprowadzić token ID(identyfikator). Ciąg znaków odpowiadający tokenowi nazywany jest leksemem.
Więcej o markerach i leksemach
Tokeny takie jak ID mogą pasować do wielu sekwencji znaków. W przypadku takich tokenów rzeczywisty token odpowiadający tokenowi jest również potrzebny kompilatorowi, asemblerowi lub innemu narzędziu wymagającemu analizy leksykalnej. W przypadku tokenów, które reprezentują jedną konkretną sekwencję znaków, np. tokenu PLUSodpowiadającego tylko znakowi +, rzeczywisty token nie jest wymagany, ponieważ może być [jednoznacznie] określony przez token.
Wyrażenia regularne są znacznie wydajniejsze niż analizatory leksykalne oparte na stanach, które należy pisać ręcznie i zazwyczaj nie można ich ponownie wykorzystać. Przykładem analizatora leksykalnego opartego na wyrażeniach regularnych jest JLex , generator leksykalny dla języka Java, który używa wyrażeń regularnych do definiowania reguł dzielenia wejściowego strumienia danych na tokeny. Innym przykładem jest Lexan.

Poznajemy Lexana

Lexan to biblioteka Java wielokrotnego użytku do analizy leksykalnej. Opiera się na kodzie z serii wpisów na blogu Pisanie parsera w Javie na stronie Cogito Learning . Biblioteka składa się z następujących klas, które znajdują się w pakiecie ca.javajeff.lexanzawartym w kodzie do pobrania dla tego artykułu:
  • Lexan: analizator leksykalny;
  • LexanException: wyjątek zgłoszony w konstruktorze klasyLexan;
  • LexException: wyjątek zgłoszony w przypadku wykrycia nieprawidłowej składni podczas analizy leksykalnej;
  • Token: nazwa z atrybutem wyrażenia regularnego;
  • TokLex: token/para tokenów.
Konstruktor Lexan(java.lang.Class tokensClass)tworzy nowy analizator leksykalny. Wymaga jednego argumentu w postaci obiektu klasy java.lang.Classodpowiadającego typowi stałej class static Token. Korzystając z interfejsu API Reflection, konstruktor wczytuje wszystkie stałe Tokendo tablicy wartości Token[]. Jeśli Tokennie ma stałych, zgłaszany jest wyjątek LexanException. Wyrażenia regularne w Javie, część 5 - 2Klasa Lexanudostępnia także dwie następujące metody:
  • Metoda zwraca listę tego leksykonu;List getTokLexes() Token
  • Метод void lex(String str)przeprowadza analizę leksykalną ciągu wejściowego [z wynikiem umieszczonym] na liście wartości typu TokLex. Jeśli napotkany zostanie znak, który nie pasuje do żadnego ze wzorców tablicy Token[], zgłaszany jest wyjątek LexException.
Klasa LexanExceptionnie ma metod; używa metody dziedziczonej w celu zwrócenia komunikatu o wyjątku getMessage(). Natomiast klasa LexExceptionudostępnia następujące metody:
  • Metoda int getBadCharIndex()zwraca pozycję znaku, który nie pasuje do żadnego ze wzorców znaczników.
  • Metoda String getText()zwraca tekst, który był analizowany podczas generowania wyjątku.
Klasa Tokenzastępuje metodę toString(), aby zwrócić nazwę znacznika. Udostępnia również metodę String getPattern()zwracającą atrybut wyrażenia regularnego tokenu. Klasa TokLexudostępnia metodę Token getToken()zwracającą swój token. Zapewnia również metodę String getLexeme()zwracającą swój token.

Demonstracja biblioteki Lexan

Aby zademonstrować działanie biblioteki, Lexannapisałem aplikację LexanDemo. Składa się z klas LexanDemo, BinTokensi MathTokens. NoTokensKod źródłowy aplikacji LexanDemopokazano na Listingu 2. Listing 2. Demonstracja biblioteki Lexan w akcji
import ca.javajeff.lexan.Lexan;
import ca.javajeff.lexan.LexanException;
import ca.javajeff.lexan.LexException;
import ca.javajeff.lexan.TokLex;

public final class LexanDemo
{
   public static void main(String[] args)
   {
      lex(MathTokens.class, " sin(x) * (1 + var_12) ");
      lex(BinTokens.class, " 1 0 1 0 1");
      lex(BinTokens.class, "110");
      lex(BinTokens.class, "1 20");
      lex(NoTokens.class, "");
   }

   private static void lex(Class tokensClass, String text)
   {
      try
      {
         Lexan lexan = new Lexan(tokensClass);
         lexan.lex(text);
         for (TokLex tokLex: lexan.getTokLexes())
            System.out.printf("%s: %s%n", tokLex.getToken(),
                              tokLex.getLexeme());
      }
      catch (LexanException le)
      {
         System.err.println(le.getMessage());
      }
      catch (LexException le)
      {
         System.err.println(le.getText());
         for (int i = 0; i < le.getBadCharIndex(); i++)
            System.err.print("-");
         System.err.println("^");
         System.err.println(le.getMessage());
      }
      System.out.println();
   }
}
Metoda main()z Listingu 2 wywołuje narzędzie lex()demonstrujące analizę leksykalną przy użyciu Lexana. Do każdego wywołania tej metody przekazywana jest klasa tokenów w obiekcie Classi ciąg znaków do przeanalizowania. Metoda lex()najpierw tworzy obiekt klasy, Lexanprzekazując obiekt Classdo konstruktora klasy Lexan. A następnie wywołuje metodę lex()klasy Lexanna tym ciągu. Jeśli analiza leksykalna zakończy się pomyślnie, TokLexwywoływana jest metoda getTokLexes()klasowa w celu zwrócenia listy obiektów Lexan. Dla każdego z tych obiektów wywoływana jest jego metoda getToken()klasowa TokLexw celu zwrócenia tokenu oraz metoda klasowa getLexeme()w celu zwrócenia tokenu. Obie wartości są wypisywane na standardowe wyjście. Jeśli analiza leksykalna zakończy się niepowodzeniem, zostanie zgłoszony jeden z wyjątków LexanExceptionlub i odpowiednio obsłużony LexException. Dla zwięzłości rozważmy tylko klasę tworzącą tę aplikację MathTokens. Listing 3 pokazuje jego kod źródłowy. Listing 3. Opis zestawu tokenów dla małego języka matematycznego
import ca.javajeff.lexan.Token;

public final class MathTokens
{
   public final static Token FUNC = new Token("FUNC", "sin|cos|exp|ln|sqrt");
   public final static Token LPAREN = new Token("LPAREN", "\\(");
   public final static Token RPAREN = new Token("RPAREN", "\\)");
   public final static Token PLUSMIN = new Token("PLUSMIN", "[+-]");
   public final static Token TIMESDIV = new Token("TIMESDIV", "[*/]");
   public final static Token CARET = new Token("CARET", "\\^");
   public final static Token INTEGER = new Token("INTEGER", "[0-9]+");
   public final static Token ID = new Token("ID", "[a-zA-Z][a-zA-Z0-9_]*");
}
Listing 3 pokazuje, że klasa MathTokensopisuje sekwencję stałych typu Token. Każdemu z nich przypisana jest wartość obiektu Token. Konstruktor tego obiektu otrzymuje ciąg znaków będący nazwą znacznika wraz z wyrażeniem regularnym opisującym wszystkie ciągi znaków powiązane z tym znacznikiem. Dla przejrzystości pożądane jest, aby nazwa ciągu znacznika była taka sama jak nazwa stałej, ale nie jest to wymagane. Wyrażenia regularne w Javie, część 5 - 3Ważna jest pozycja stałej Tokenna liście znaczników. Stałe znajdujące się wyżej na liście Tokenmają pierwszeństwo przed stałymi znajdującymi się poniżej. Na przykład, podczas spotkania sinLexan wybiera żeton FUNCzamiast ID. Gdyby znacznik IDznajdował się przed znacznikiem FUNC, zostałby wybrany.

Kompilowanie i uruchamianie aplikacji LexanDemo

Kod tego artykułu do pobrania zawiera archiwum lexan.zipzawierające wszystkie pliki dystrybucji Lexan. Rozpakuj to archiwum i przejdź do podkatalogu demoskatalogu głównego lexan. Jeśli używasz systemu Windows, uruchom następujące polecenie, aby skompilować pliki kodu źródłowego aplikacji demonstracyjnej:
javac -cp ..\library\lexan.jar *.java
Jeśli kompilacja zakończy się pomyślnie, uruchom następującą komendę, aby uruchomić aplikację demonstracyjną:
java -cp ..\library\lexan.jar;. LexanDemo
Powinieneś zobaczyć następujące wyniki:
FUNC: sin
LPAREN: (
ID: x
RPAREN: )
TIMESDIV: *
LPAREN: (
INTEGER: 1
PLUSMIN: +
ID: var_12
RPAREN: )
ONE: 1
ZERO: 0
ONE: 1
ZERO: 0
ONE: 1
ONE: 1
ONE: 1
ZERO: 0
1 20
--^
Неожиданный символ во входном тексте: 20
Komunikat Неожиданный символ во входном тексте: 20pojawia się w wyniku zgłoszenia wyjątku LexanExceptionw związku z tym, że klasa BinTokensnie deklaruje stałej Tokenz wartością 2w postaci wyrażenia regularnego. Należy zauważyć, że procedura obsługi wyjątków wyświetla pozycję niewłaściwego znaku uzyskaną z analizy leksykalnej tekstu. Komunikat o braku tokenów jest wynikiem zgłoszenia wyjątku, LexExceptionponieważ w klasie NoTokensnie zadeklarowano żadnych stałych Token.

Za kulisami

Lexanwykorzystuje klasę Lexan jako swój silnik. Przyjrzyj się implementacji tej klasy na Listingu 4 i zwróć uwagę na wkład wyrażeń regularnych w umożliwienie ponownego użycia silnika. Listing 4. Tworzenie architektury analizatora leksykalnego w oparciu o wyrażenia regularne
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

/**
 *  Лексический анализатор. Этот класс можно использовать для
 *  преобразования входного потока символов в выходной поток маркеров.
 *
 *  @Autor Джефф Фризен
 */

public final class Lexan
{
   private List tokLexes;

   private Token[] values;

   /**
    *  Инициализируем лексический анализатор набором obiektов Token.
    *
    *  @параметры tokensClass – obiekt Class класса, содержащего
    *       набор obiektов Token
    *
    *  @генерирует исключение LexanException в случае невозможности
    *       формирования obiektа Lexan, возможно, из-за отсутствия obiektов
    *       Token в классе
    */

   public Lexan(Class tokensClass) throws LexanException
   {
      try
      {
         tokLexes = new ArrayList<>();
         List _values = new ArrayList<>();
         Field[] fields = tokensClass.getDeclaredFields();
         for (Field field: fields)
            if (field.getType().getName().equals("ca.javajeff.lexan.Token"))
               _values.add((Token) field.get(null));
         values = _values.toArray(new Token[0]);
         if (values.length == 0)
            throw new LexanException("маркеры отсутствуют");
      }
      catch (IllegalAccessException iae)
      {
         throw new LexanException(iae.getMessage());
      }

   /**
    * Получаем список TokLex'ов этого лексического анализатора.
    *
    *  @возвращает список TokLex'ов
    */

   public List getTokLexes()
   {
      return tokLexes;
   }

   /** * Выполняет лексический анализ входной строки [с помещением * результата] в список TokLex'ов. * * @параметры str – строка, подвергаемая лексическому анализу * * @генерирует исключение LexException: во входных данных обнаружен * неожиданный символ */

   public void lex(String str) throws LexException
   {
      String s = new String(str).trim(); // удалить ведущие пробелы
      int index = (str.length() - s.length());
      tokLexes.clear();
      while (!s.equals(""))
      {
         boolean match = false;
         for (int i = 0; i < values.length; i++)
         {
            Token token = values[i];
            Matcher m = token.getPattern().matcher(s);
            if (m.find())
            {
               match = true;
               tokLexes.add(new TokLex(token, m.group().trim()));
               String t = s;
               s = m.replaceFirst("").trim(); // удалить ведущие пробелы
               index += (t.length() - s.length());
               break;
            }
         }
         if (!match)
            throw new LexException("Неожиданный символ во входном тексте: "
                                    + s, str, index);
      }
   }
}
Kod metody lex()opiera się na kodzie podanym w poście na blogu „Pisanie parsera w Javie: generator tokenów” na stronie Cogito Learning. Przeczytaj ten post, aby dowiedzieć się więcej o tym, jak Lexan używa API Regex do kompilowania kodu. Wyrażenia regularne w Javie, część 5 - 4

Wniosek

Wyrażenia regularne są użytecznym narzędziem, które może przydać się każdemu programiście. Regex API języka programowania Java ułatwia ich użycie w aplikacjach i bibliotekach. Teraz, gdy masz już podstawową wiedzę na temat wyrażeń regularnych i tego interfejsu API, przejrzyj dokumentację zestawu SDK, java.util.regexaby dowiedzieć się jeszcze więcej o wyrażeniach regularnych i dodatkowych metodach API Regex.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION