JavaRush /Java Blog /Random-TL /Mga Regular na Ekspresyon sa Java, Bahagi 5

Mga Regular na Ekspresyon sa Java, Bahagi 5

Nai-publish sa grupo
Ipinakita namin sa iyong atensyon ang pagsasalin ng isang maikling gabay sa mga regular na expression sa Java, na isinulat ni Jeff Friesen para sa website ng javaworld. Para sa kadalian ng pagbabasa, hinati namin ang artikulo sa ilang bahagi. Ang bahaging ito ay ang pangwakas. Регулярные выражения в Java, часть 5 - 1Mga Regular na Expression sa Java, Part 1 Regular Expression sa Java, Part 2 Regular Expression sa Java, Part 3 Regular Expression sa Java, Part 4

Paggamit ng mga regular na expression para sa lexical analysis

Ang isang mas kapaki-pakinabang na aplikasyon ng mga regular na expression ay isang library ng reusable code para sa pagsasagawa ng lexical analysis, isang pangunahing bahagi ng anumang compiler o assembler. Sa kasong ito, ang input stream ng mga character ay pinagsama-sama sa isang output stream ng mga token - mga pangalan para sa mga pagkakasunud-sunod ng mga character na may karaniwang kahulugan. Halimbawa, kapag nakatagpo ng pagkakasunud-sunod ng mga character c, o, u, n, t, e, r, sa input stream, ang lexical analyzer ay maaaring mag-output ng token ID(identifier). Ang pagkakasunud-sunod ng mga character na nauugnay sa token ay tinatawag na isang lexeme.
Higit pa tungkol sa mga marker at lexemes
Ang mga token gaya ng ID ay maaaring tumugma sa maraming sequence ng character. Sa kaso ng mga naturang token, ang aktwal na token na tumutugma sa token ay kailangan din ng compiler, assembler, o iba pang utility na nangangailangan ng lexical analysis. Para sa mga token na kumakatawan sa isang partikular na pagkakasunud-sunod ng mga character, tulad ng token PLUSna tumutugma lamang sa character +, ang aktwal na token ay hindi kinakailangan dahil maaari itong [natatanging] tinutukoy ng token.
Ang mga regular na expression ay mas mahusay kaysa sa state-based lexical analyzer, na dapat na nakasulat sa pamamagitan ng kamay at sa pangkalahatan ay hindi na magagamit muli. Ang isang halimbawa ng isang regular na expression-based lexical analyzer ay ang JLex , isang lexical generator para sa wikang Java na gumagamit ng mga regular na expression upang tukuyin ang mga panuntunan para sa paghahati-hati ng input data stream sa mga token. Isa pang halimbawa ay si Lexan.

Kilalanin si Lexan

Ang Lexan ay isang reusable na Java library para sa lexical analysis. Ito ay batay sa code mula sa blog post series na Writing a Parser in Java sa website ng Cogito Learning . Binubuo ang library ng mga sumusunod na klase, na nasa package ca.javajeff.lexanna kasama sa nada-download na code para sa artikulong ito:
  • Lexan: lexical analyzer;
  • LexanException: pagbubukod na itinapon sa tagabuo ng klaseLexan;
  • LexException: exception na itinapon kung ang maling syntax ay nakita sa panahon ng lexical analysis;
  • Token: pangalan na may katangian ng regular na expression;
  • TokLex: pares ng token/token.
Конструктор Lexan(java.lang.Class tokensClass) создает новый лексический анализатор. Для него требуется один аргумент в виде an object класса java.lang.Class, соответствующий классу констант типа static Token. При помощи API Reflection, конструктор читает все константы Token в массив значений Token[]. Если констант Token нет, генерируется исключение LexanException. Регулярные выражения в Java, часть 5 - 2Класс Lexan также предоставляет следующие два метода:
  • Метод List getTokLexes() возвращает список Token этого лексического анализатора;
  • Метод void lex(String str) выполняет лексический анализ входной строки [с помещением результата] в список значений типа TokLex. В случае обнаружения символа, не соответствующего ни одному из шаблонов массива Token[], генерируется исключение LexException.
В классе LexanException методов нет, он использует для возврата messages исключения унаследованный метод getMessage(). В отличие от него, класс LexException предоставляет следующие методы:
  • Метод int getBadCharIndex() возвращает позицию символа, не соответствующего ни одному из шаблонов маркеров.
  • Метод String getText() возвращает анализировавшийся при генерации исключения текст.
Класс Token переопределяет метод toString() для возврата названия маркера. Он также предоставляет метод String getPattern(), возвращающий атрибут регулярного выражения маркера. Класс TokLex предоставляет метод Token getToken(), который возвращает его маркер. Он также предоставляет метод String getLexeme(), который возвращает его лексему.

Демонстрация работы библиотеки Lexan

Для демонстрации работы библиотеки Lexan я написал приложение LexanDemo. Оно состоит из классов LexanDemo, BinTokens, MathTokens и NoTokens. Исходный code applications LexanDemo приведен в листинге 2. Листинг 2. Демонстрация работы библиотеки Lexan
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();
   }
}
Метод main() из листинга 2 вызывает утorту lex() для демонстрации лексического анализа при помощи Lexan. При каждом вызове этому методу передается класс маркеров в an objectе Class и строка, анализ которой необходимо выполнить. Метод lex() сначала создает an object класса Lexan, передавая an object Class конструктору класса Lexan. А затем он вызывает метод lex() класса Lexan для этой строки. В случае успешного выполнения лексического анализа, с целью возврата списка an objectов TokLex вызывается метод getTokLexes() класса Lexan. Для каждого из этих an objectов вызывается его метод getToken() класса TokLex для возврата маркера и его метод getLexeme() для возврата лексемы. Оба значения выводятся в стандартный поток вывода. В случае неудачи лексического анализа генерируется и обрабатывается соответствующим образом одно из исключений LexanException or LexException. Для краткости, рассмотрим, из составляющих это приложение, только класс MathTokens. В листинге 3 показан его исходный code. Листинг 3. Описание набора маркеров для небольшого математического языка
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_]*");
}
Из листинга 3 видно, что класс MathTokens описывает последовательность констант типа Token. Каждой из них присваивается meaning an object Token. Конструктор этого an object получает строку-название маркера, вместе с регулярным выражением, описывающим все строки символов, относящихся к этому маркеру. Для ясности, желательно, чтобы строковое название маркера совпадало с названием константы, но это не обязательно. Регулярные выражения в Java, часть 5 - 3Позиция константы Token в списке маркеров важна. Расположенные выше в списке константы Token имеют приоритет перед расположенными ниже. Например, встретив sin, Lexan выбирает маркер FUNC instead of ID. Если бы маркер ID предшествовал маркеру FUNC, то был бы выбран он.

Компиляция и запуск applications LexanDemo

Загружаемый code для данной статьи включает архив lexan.zip, содержащий все файлы дистрибутива Lexan. Распакуйте этот архив и перейдите в подкаталог demos корневого каталога lexan. Если вы используете Windows, выполните следующую команду для компиляции файлов исходного codeа демо-applications:
javac -cp ..\library\lexan.jar *.java
В случае успешной компиляции выполните следующую команду для запуска демо-applications:
java -cp ..\library\lexan.jar;. LexanDemo
Вы должны увидеть следующие результаты:
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
Сообщение Неожиданный символ во входном тексте: 20 возникает в результате генерации исключения LexanException, вызванного тем, что в классе BinTokens не описана константа Token со meaningм 2 в качестве регулярного выражения. Обратите внимание на то, что обработчик исключений вывел полученную в результате лексического анализа текста позицию неподходящего символа. Сообщение маркеры отсутствуют получается в результате генерации исключения LexException, вызванного тем, что в классе NoTokens не описано ниHowих констант Token.

За кулисами

Lexan использует в качестве своего "движка" класс Lexan. Взгляните на реализацию этого класса в листинге 4 и отметьте вклад регулярных выражений в обеспечение возможности повторного использования "движка". Листинг 4. Creation архитектуры лексического анализатора на основе регулярных выражений
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

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

public final class Lexan
{
   private List tokLexes;

   private Token[] values;

   /**
    *  Инициализируем лексический анализатор набором an objectов Token.
    *
    *  @параметры tokensClass – an object Class класса, содержащего
    *       набор an objectов Token
    *
    *  @генерирует исключение LexanException в случае невозможности
    *       формирования an object Lexan, возможно, из-за отсутствия an objectов
    *       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);
      }
   }
}
Код метода lex() основан на codeе, приведенном в сообщении блога "Пишем синтаксический анализатор на языке Java: Генератор маркеров" веб-сайта Cogito Learning. Прочитайте это сообщение, чтобы узнать больше о том, How Lexan использует API Regex для компиляции codeа. Регулярные выражения в Java, часть 5 - 4

Заключение

Регулярные выражения – полезный инструмент, который может пригодиться любому разработчику. API Regex языка программирования Java упрощает их использование в applicationsх и библиотеках. Теперь, когда у вас уже есть базовое представление о регулярных выражениях и этом API, загляните в documentацию SDK java.util.regex, чтобы узнать еще больше о регулярных выражениях и дополнительных методах API Regex.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION