我們向您展示 Java 正規表示式簡短指南的翻譯,該指南由 Jeff Friesen 為 javaworld 網站編寫。為了方便閱讀,我們將文章分成幾個部分。這部分是最後一部分。 Java 中的正規表示式,第 1 部分 Java 中的正規表示式,第 2 部分 Java 中的正規表示式,第 3 部分 Java 中的正規表示式,第 4 部分
正規表示式比基於狀態的詞法分析器效率高得多,基於狀態的詞法分析器必須手動編寫,並且通常無法重複使用。基於正規表示式的詞法分析器的一個範例是JLex,它是 Java 語言的詞法產生器,它使用正規表示式來定義將輸入資料流分解為標記的規則。另一個例子是萊克桑。
使用正規表示式進行詞法分析
正規表示式的一個更有用的應用是用於執行詞法分析的可重複使用程式碼庫,它是任何編譯器或彙編器的關鍵元件。在這種情況下,字元輸入流被分組為令牌輸出流 - 具有共同意義的字元序列的名稱。例如,在輸入流中偶然發現字元序列c
, o
, u
, n
, t
, e
, r
, 時,詞法分析器可以輸出一個標記ID
(標識符)。與標記相對應的字元序列稱為詞位。
有關標記和詞位的更多信息 |
---|
ID 等標記可以符合許多字元序列。在這種標記的情況下,編譯器、彙編器或其他需要詞法分析的實用程式也需要與該標記相對應的實際標記。對於表示一個特定字元序列的標記,例如PLUS 僅對應於字元 的標記+ ,不需要實際標記,因為它可以由標記[唯一]決定。 |
了解萊克森
Lexan 是一個用於詞法分析的可重複使用 Java 函式庫。它是基於Cogito Learning網站上的部落格文章系列「用 Java 編寫解析器」中的程式碼。該庫由以下類別組成,這些類別位於本文的可下載程式碼中包含的套件中:ca.javajeff.lexan
Lexan
:詞法分析器;LexException
:詞法分析過程中如果偵測到錯誤語法則拋出異常;Token
:帶有正規表示式屬性的名稱;TokLex
:令牌/令牌對。
LexanException
: 類別建構子中拋出異常Lexan;
Lexan(java.lang.Class tokensClass)
建立一個新的詞法分析器。java.lang.Class
它需要一個與類型常數 class 相對應的類別物件形式的參數static Token
。使用 Reflection API,建構函數將所有常數讀取Token
到值數組中Token[]
。如果Token
沒有常數,則會拋出異常LexanException
。 該類別Lexan
還提供了以下兩個方法:
- 此方法傳回此詞法分析器的清單;
List
getTokLexes() Token
Метод void lex(String str)
對輸入字串進行詞法分析[並將結果放入]類型為 的值清單中TokLex
。如果遇到與任何數組模式都不匹配的字符Token[]
,則會引發異常LexException
。
LexanException
沒有方法;它使用繼承的方法傳回異常訊息getMessage()
。相比之下,該類別LexException
提供了以下方法:
- 此方法
int getBadCharIndex()
傳回與任何標記模式都不匹配的字元的位置。 - 此方法
String getText()
傳回產生異常時分析的文字。
Token
重寫該方法toString()
以傳回標記的名稱。它還提供了一個String getPattern()
傳回標記的正規表示式屬性的方法。該類別提供了一個傳回其標記的TokLex
方法。Token getToken()
它還提供了一個String getLexeme()
返回其令牌的方法。
Lexan庫的演示
為了演示該庫的工作原理,Lexan
我編寫了一個應用程式LexanDemo
。它由類別LexanDemo
、BinTokens
和組成MathTokens
。NoTokens
該應用程式的原始程式碼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 中的 方法呼叫一個實用程式lex()
來示範使用 Lexan 的詞法分析。每次呼叫此方法時都會傳遞物件中標記的類別Class
和要解析的字串。該方法首先透過將物件傳遞給類別建構函式來 lex()
建立該類別的物件。然後它呼叫該字串的類別方法。如果詞法分析成功,則呼叫類別方法傳回物件清單。對於每個對象,呼叫其類別方法以返回令牌,並呼叫其類別方法以返回令牌。這兩個值都列印到標準輸出。如果詞法分析失敗,則拋出例外之一並進行相應處理。為了簡潔起見,我們只考慮組成該應用程式的類別。清單 3 顯示了其原始程式碼。 清單 3. 小型數學語言的一組標記的描述Lexan
Class
Lexan
lex()
Lexan
TokLex
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_]*");
}
清單 3 顯示該類別MathTokens
描述了類型為 的常數序列Token
。他們每個人都被分配了一個物件的值Token
。該物件的建構函式接收一個作為標記名稱的字串,以及一個描述與該標記關聯的所有字串的正規表示式。為了清楚起見,標記的字串名稱最好與常數的名稱相同,但這不是必需的。 常量在標記清單中的位置很重要。Token
位於清單上方的常數Token
優先於位於下方的常數。例如,當遇到 時sin
,Lexan 選擇標記FUNC
而不是ID
。如果該標記ID
位於 標記 之前FUNC
,則該標記將被選取。
編譯並運行 LexanDemo 應用程式
本文的可下載程式碼包括一個lexan.zip
包含 Lexan 發行版的所有檔案的存檔。解壓縮該存檔並轉到demos
根目錄的子目錄lexan
。 如果您使用的是Windows,請執行以下命令來編譯示範應用程式原始碼檔案:
javac -cp ..\library\lexan.jar *.java
如果編譯成功,請執行以下命令來執行演示應用程式:
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
2
LexException
NoTokens
Token
幕後花絮
Lexan
使用 Lexan 類別作為其引擎。請參閱清單 4 中此類的實現,並注意正規表示式對使引擎可重用的貢獻。 清單 4. 建立基於正規表示式的詞法分析器架構
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()
是基於Cogito Learning 網站上的部落格文章「用 Java 編寫解析器:令牌產生器」中提供的程式碼。閱讀這篇文章,以了解有關 Lexan 如何使用 Regex API 編譯程式碼的更多資訊。
結論
正規表示式是對任何開發人員都有用的有用工具。Java 程式語言的 Regex API 使它們易於在應用程式和程式庫中使用。現在您已經對正規表示式和此 API 有了基本的了解,請查看 SDK 文件java.util.regex
以了解有關正規表示式和其他 Regex API 方法的更多資訊。
GO TO FULL VERSION