JavaRush /Blog Java /Random-VI /Biểu thức chính quy trong Java, Phần 5

Biểu thức chính quy trong Java, Phần 5

Xuất bản trong nhóm
Chúng tôi xin gửi đến bạn bản dịch hướng dẫn ngắn gọn về các biểu thức chính quy trong Java, do Jeff Friesen viết cho trang web javaworld. Để dễ đọc, chúng tôi chia bài viết thành nhiều phần. Phần này là phần cuối cùng. Biểu thức chính quy trong Java, Phần 5 - 1Biểu thức chính quy trong Java, Phần 1 Biểu thức chính quy trong Java, Phần 2 Biểu thức chính quy trong Java, Phần 3 Biểu thức chính quy trong Java, Phần 4

Sử dụng biểu thức chính quy để phân tích từ vựng

Một ứng dụng hữu ích hơn của biểu thức chính quy là một thư viện mã có thể tái sử dụng để thực hiện phân tích từ vựng, một thành phần chính của bất kỳ trình biên dịch hoặc trình biên dịch nào. Trong trường hợp này, luồng ký tự đầu vào được nhóm thành luồng đầu ra gồm các mã thông báo - tên của các chuỗi ký tự có ý nghĩa chung. Ví dụ: khi tình cờ gặp chuỗi ký tự c, o, u, n, t, e, rtrong luồng đầu vào, bộ phân tích từ vựng có thể xuất ra một mã thông báo ID(mã định danh). Chuỗi ký tự tương ứng với mã thông báo được gọi là từ vị.
Tìm hiểu thêm về dấu hiệu và từ vựng
Các mã thông báo như ID có thể khớp với nhiều chuỗi ký tự. Trong trường hợp các mã thông báo như vậy, mã thông báo thực tế tương ứng với mã thông báo cũng cần thiết cho trình biên dịch, trình biên dịch mã hoặc tiện ích khác yêu cầu phân tích từ vựng. Đối với các mã thông báo đại diện cho một chuỗi ký tự cụ thể, chẳng hạn như mã thông báo PLUSchỉ tương ứng với ký tự đó +, mã thông báo thực tế là không bắt buộc vì nó có thể được xác định [duy nhất] bởi mã thông báo.
Biểu thức chính quy hiệu quả hơn nhiều so với các máy phân tích từ vựng dựa trên trạng thái, vốn phải được viết bằng tay và thường không thể sử dụng lại. Một ví dụ về trình phân tích từ vựng dựa trên biểu thức chính quy là JLex , một trình tạo từ vựng cho ngôn ngữ Java sử dụng các biểu thức chính quy để xác định quy tắc chia luồng dữ liệu đầu vào thành các mã thông báo. Một ví dụ khác là Lexan.

Làm quen với Lexan

Lexan là một thư viện Java có thể tái sử dụng để phân tích từ vựng. Nó dựa trên mã từ loạt bài đăng trên blog Viết trình phân tích cú pháp bằng Java trên trang web Cogito Learning . Thư viện bao gồm các lớp sau, nằm trong gói ca.javajeff.lexancó trong mã có thể tải xuống cho bài viết này:
  • Lexan: máy phân tích từ vựng;
  • LexanException: ngoại lệ được ném vào hàm tạo của lớpLexan;
  • LexException: ném ngoại lệ nếu phát hiện cú pháp sai trong quá trình phân tích từ vựng;
  • Token: tên có thuộc tính biểu thức chính quy;
  • TokLex: cặp mã thông báo/mã thông báo.
Hàm Lexan(java.lang.Class tokensClass)tạo tạo ra một bộ phân tích từ vựng mới. Nó yêu cầu một đối số ở dạng đối tượng lớp java.lang.Classtương ứng với kiểu hằng số class static Token. Bằng cách sử dụng API Reflection, hàm tạo sẽ đọc tất cả các hằng số Tokenthành một mảng giá trị Token[]. Nếu Tokenkhông có hằng số, một ngoại lệ sẽ được đưa ra LexanException. Biểu thức chính quy trong Java, Phần 5 - 2Lớp này Lexancũng cung cấp hai phương thức sau:
  • Phương thức trả về danh sách từ vựng này;List getTokLexes() Token
  • Метод void lex(String str)thực hiện phân tích từ vựng của chuỗi đầu vào [với kết quả được đặt] vào danh sách các giá trị thuộc loại TokLex. Nếu gặp một ký tự không khớp với bất kỳ mẫu mảng nào Token[], một ngoại lệ sẽ được đưa ra LexException.
Lớp này LexanExceptionkhông có phương thức; nó sử dụng một phương thức kế thừa để trả về một thông báo ngoại lệ getMessage(). Ngược lại, lớp LexExceptioncung cấp các phương thức sau:
  • Phương thức int getBadCharIndex()trả về vị trí của một ký tự không khớp với bất kỳ mẫu đánh dấu nào.
  • Phương thức String getText()trả về văn bản đã được phân tích khi ngoại lệ được tạo.
Lớp Tokenghi đè phương thức này toString()để trả về tên của điểm đánh dấu. Nó cũng cung cấp một phương thức String getPattern()trả về thuộc tính biểu thức chính quy của mã thông báo. Lớp này TokLexcung cấp một phương thức Token getToken()trả về mã thông báo của nó. Nó cũng cung cấp một phương thức String getLexeme()trả về mã thông báo của nó.

Trình diễn thư viện Lexan

Để chứng minh cách hoạt động của thư viện, Lexantôi đã viết một ứng dụng LexanDemo. Nó bao gồm các lớp LexanDemo, BinTokens, MathTokensNoTokens. Mã nguồn của ứng dụng LexanDemođược hiển thị trong Liệt kê 2. Liệt kê 2. Minh họa hoạt động của thư viện 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();
   }
}
Phương thức main()trong Liệt kê 2 gọi một tiện ích lex()để thể hiện khả năng phân tích từ vựng bằng Lexan. Mỗi lệnh gọi phương thức này được chuyển qua lớp mã thông báo trong đối tượng Classvà chuỗi để phân tích cú pháp. Đầu tiên , phương thức này lex()tạo một đối tượng của lớp Lexanbằng cách chuyển đối tượng đó Classtới hàm tạo của lớp Lexan. Và sau đó nó gọi phương thức lex()lớp Lexantrên chuỗi đó. Nếu phân tích từ vựng thành công, phương thức lớp TokLexsẽ được gọi để trả về danh sách các đối tượng . Đối với mỗi đối tượng này, phương thức lớp của nó được gọi để trả về mã thông báo và phương thức lớp của nó để trả về mã thông báo. Cả hai giá trị đều được in ra đầu ra tiêu chuẩn. Nếu phân tích từ vựng không thành công, một trong các trường hợp ngoại lệ hoặc sẽ bị loại bỏ và xử lý tương ứng . Để ngắn gọn, chúng ta chỉ xem xét lớp tạo nên ứng dụng này . Liệt kê 3 cho thấy mã nguồn của nó. Liệt kê 3. Mô tả một bộ mã thông báo cho một ngôn ngữ toán học nhỏgetTokLexes()LexangetToken()TokLexgetLexeme()LexanExceptionLexExceptionMathTokens
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_]*");
}
Liệt kê 3 cho thấy lớp này MathTokensmô tả một chuỗi các hằng số loại Token. Mỗi người trong số họ được gán giá trị của một đối tượng Token. Hàm tạo cho đối tượng này nhận một chuỗi là tên của điểm đánh dấu, cùng với một biểu thức chính quy mô tả tất cả các chuỗi ký tự được liên kết với điểm đánh dấu đó. Để rõ ràng, điều mong muốn là tên chuỗi của điểm đánh dấu giống với tên của hằng số, nhưng điều này không bắt buộc. Biểu thức chính quy trong Java, Phần 5 - 3Vị trí của hằng số Tokentrong danh sách các điểm đánh dấu là quan trọng. Các hằng số nằm ở vị trí cao hơn trong danh sách Tokensẽ được ưu tiên hơn các hằng số nằm ở bên dưới. Ví dụ: khi gặp sin, Lexan chọn token FUNCthay vì ID. Nếu điểm đánh dấu IDđứng trước điểm đánh dấu FUNCthì nó sẽ được chọn.

Biên dịch và chạy ứng dụng LexanDemo

Mã có thể tải xuống cho bài viết này bao gồm một kho lưu trữ lexan.zipchứa tất cả các tệp của bản phân phối Lexan. Giải nén kho lưu trữ này và đi tới thư mục con demoscủa thư mục gốc lexan. Nếu bạn đang sử dụng Windows, hãy chạy lệnh sau để biên dịch tệp mã nguồn ứng dụng demo:
javac -cp ..\library\lexan.jar *.java
Nếu quá trình biên dịch thành công, hãy chạy lệnh sau để chạy ứng dụng demo:
java -cp ..\library\lexan.jar;. LexanDemo
Bạn sẽ thấy kết quả sau:
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
Thông báo Неожиданный символ во входном тексте: 20xảy ra do một ngoại lệ được đưa ra LexanExceptiondo lớp BinTokenskhông khai báo một hằng số Tokencó giá trị 2là biểu thức chính quy. Lưu ý rằng trình xử lý ngoại lệ xuất ra vị trí của ký tự không phù hợp thu được từ phân tích từ vựng của văn bản. Thông báo thiếu mã thông báo là kết quả của một ngoại lệ được đưa ra LexExceptiondo NoTokenskhông có hằng số nào được khai báo trong lớp Token.

Đằng sau hậu trường

Lexansử dụng lớp Lexan làm động cơ của nó. Hãy xem cách triển khai của lớp này trong Liệt kê 4 và lưu ý sự đóng góp của các biểu thức chính quy trong việc làm cho công cụ có thể tái sử dụng được. Liệt kê 4. Tạo một kiến ​​trúc bộ phân tích từ vựng dựa trên các biểu thức chính quy
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);
      }
   }
}
Mã phương thức lex()dựa trên mã được cung cấp trong bài đăng trên blog "Viết trình phân tích cú pháp bằng Java: Trình tạo mã thông báo" trên trang web Cogito Learning. Đọc bài đăng này để tìm hiểu thêm về cách Lexan sử dụng API Regex để biên dịch mã. Biểu thức chính quy trong Java, Phần 5 - 4

Phần kết luận

Biểu thức chính quy là một công cụ hữu ích có thể hữu ích cho bất kỳ nhà phát triển nào. API Regex của ngôn ngữ lập trình Java giúp chúng dễ sử dụng trong các ứng dụng và thư viện. Bây giờ bạn đã có hiểu biết cơ bản về biểu thức chính quy và API này, hãy xem tài liệu SDK java.util.regexđể tìm hiểu thêm về biểu thức chính quy và các phương thức API Regex bổ sung.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION