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 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
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.
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
, r
trong 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 PLUS chỉ 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. |
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óica.javajeff.lexan
có 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;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.
LexanException
: ngoại lệ được ném vào hàm tạo của lớpLexan;
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.Class
tươ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ố Token
thành một mảng giá trị Token[]
. Nếu Token
không có hằng số, một ngoại lệ sẽ được đưa ra LexanException
. Lớp này Lexan
cũ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ạiTokLex
. Nếu gặp một ký tự không khớp với bất kỳ mẫu mảng nàoToken[]
, một ngoại lệ sẽ được đưa raLexException
.
LexanException
khô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 LexException
cung 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.
Token
ghi đè 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 TokLex
cung 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,Lexan
tôi đã viết một ứng dụng LexanDemo
. Nó bao gồm các lớp LexanDemo
, BinTokens
, MathTokens
và NoTokens
. 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 Class
và 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 Lexan
bằng cách chuyển đối tượng đó Class
tới hàm tạo của lớp Lexan
. Và sau đó nó gọi phương thức lex()
lớp Lexan
trên chuỗi đó. Nếu phân tích từ vựng thành công, phương thức lớp TokLex
sẽ đượ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()
Lexan
getToken()
TokLex
getLexeme()
LexanException
LexException
MathTokens
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 MathTokens
mô 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. Vị trí của hằng số Token
trong 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 Token
sẽ đượ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 FUNC
thay vì ID
. Nếu điểm đánh dấu ID
đứng trước điểm đánh dấu FUNC
thì 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.zip
chứ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 demos
củ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 Неожиданный символ во входном тексте: 20
xảy ra do một ngoại lệ được đưa ra LexanException
do lớp BinTokens
không khai báo một hằng số Token
có giá trị 2
là 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 LexException
do NoTokens
không có hằng số nào được khai báo trong lớp Token
.
Đằng sau hậu trường
Lexan
sử 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ã.
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 SDKjava.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.
GO TO FULL VERSION