實現一個簡單的詞法分析器
詞法分析
詞法分析的工作是將一個長長的字符串識別出一個個的單詞,這一個個單詞就是 Token。而且詞法分析的工作是一邊讀取一邊識別字符串的,不是把字符串都讀到內存再識別
詞法單元:
-
單詞的內部表示是詞法單元 token
-
編程語言中最小的語法單元
-
詞法單元的表示
目標程序語句:
- age >= 45
解析age >= 45
過程圖:
標識符、比較操作符和數字字面量這三種 Token 的詞法規則:
-
標識符:第一個字符必須是字母,後面的字符可以是字母或數字
-
比較操作符:> 和 >=(其他比較操作符暫時忽略)
-
數字字面量:全部由數字構成(像帶小數點的浮點數,暫時不管它)
有限自動機:
-
- 初始狀態:剛開始啓動詞法分析的時候,程序所處的狀態
-
- 標識符狀態:在初始狀態時,當第一個字符是字母的時候,遷移到狀態 2。當後續字符是字母和數字時,保留在狀態 2。如果不是,就離開狀態 2,寫下該 Token,回到初始狀態
-
- 大於操作符(GT):在初始狀態時,當第一個字符是 > 時,進入這個狀態。它是比較操作符的一種情況
-
- 大於等於操作符(GE):如果狀態 3 的下一個字符是 =,就進入狀態 4,變成 >=。它也是比較操作符的一種情況
-
- 數字字面量:在初始狀態時,下一個字符是數字,進入這個狀態。如果後續仍是數字,就保持在狀態 5
圓圈有單線的也有雙線的。雙線的意思是這個狀態已經是一個合法的 Token 了,單線的意思是這個狀態還是臨時狀態。
標識符和關鍵字規則的衝突
解析“int age = 40”這個語句,以這個語句爲例研究一下詞法分析中會遇到的問題:多個規則之間的衝突。
問題:
- int 這個關鍵字,與標識符很相似,都是以字母開頭,後面跟着其他字母
換句話說,int 這個字符串,既符合標識符的規則,又符合 int 這個關鍵字的規則,這兩個規則發生了重疊。這樣就起衝突了,我們掃描字符串的時候,到底該用哪個規則呢?
當然int 這個關鍵字的規則,比標識符的規則優先級高。普通的標識符是不允許跟這些關鍵字重名的。
關鍵字是語言設計中作爲語法要素的詞彙,例如表示數據類型的 int、char,表示程序結構的 while、if,表述特殊數據取值的 null、NAN 等。
在詞法分析器中,如何把關鍵字和保留字跟標識符區分開
修改自動機爲下面的形式:
當第一個字符是 i 的時候,我們讓它進入一個特殊的狀態。接下來,如果它遇到 n 和 t,就進入狀態 4。但這還沒有結束,如果後續的字符還有其他的字母和數字,它又變成了普通的標識符。比如,我們可以聲明一個 intA(int 和 A 是連着的)這樣的變量,而不會跟 int 關鍵字衝突。
代碼實現
以 int x = 45爲分析目標
定義Token:
自定義的Token:
public interface Token {
// 獲取類型
TokenType getTokenType();
// 獲取文本值
String getText();
}
TokenType:Token類型
是標識符,比較操作符還是數字字面量?
public enum TokenType {
// 先定義了少量的類型 主要了解 整個過程 有需要可以後續再添加
Assignment,// =
Identifier, //標識符
IntLiteral, //整型字面量
StringLiteral, //字符串字面量
GE, // >=
Int
}
在主類中定義一個私有類:SimpleToken
圖示:
private final class SimpleToken implements Token {
private TokenType tokenType = null;
private String text = null;
@Override
public TokenType getTokenType() {
return tokenType;
}
@Override
public String getText() {
return text;
}
}
使用枚舉類來表示有限狀態機的各種狀態
private enum DfaState {
Initial,
If, Id_if1, Id_if2, Else, Id_else1, Id_else2, Id_else3, Id_else4, Int, Id_int1, Id_int2, Id_int3, Id, GT, GE,
Assignment,
Plus, Minus, Star, Slash,
SemiColon,
LeftParen,
RightParen,
IntLiteral
}
現在考慮數據的處理:
圖示:
// 開始解析 進入初始化狀態
// 解析完一個Token 也進入初始化狀態
private DfaState initToken(char ch) {
if (tokenText.length() > 0) {
current_token.text = tokenText.toString();
tokens.add(current_token);
tokenText = new StringBuilder();
current_token = new SimpleToken();
}
//初始狀態
DfaState dfaState = DfaState.Initial;
if (isAlpha(ch)) { //第一個字符是字母
// 出現了字符 'i'
if (ch == 'i') {
dfaState = DfaState.Id_int1;
} else {
dfaState = DfaState.Id; //進入Id狀態
}
current_token.tokenType = TokenType.Identifier;
tokenText.append(ch);
} else if (isDigit(ch)) { //第一個字符是數字
dfaState = DfaState.IntLiteral;
current_token.tokenType = TokenType.IntLiteral;
tokenText.append(ch);
} else if (ch == '=') {
dfaState = DfaState.Assignment;
current_token.tokenType = TokenType.Assignment;
tokenText.append(ch);
} else {
dfaState = DfaState.Initial;
}
return dfaState;
}
public SimpleTokens lexicalanalyz(String resource) throws IOException {
tokens = new ArrayList<>();
CharArrayReader reader = new CharArrayReader(resource.toCharArray());
tokenText = new StringBuilder();
current_token = new SimpleToken();
int int_for_char = 0;
char char_for_int = 0;
DfaState dfaState = DfaState.Initial;
while ( (int_for_char = reader.read()) != -1 ) {
char_for_int = (char) int_for_char;
switch (dfaState) {
case Initial:
dfaState = initToken(char_for_int);
break;
case Id:
if (isDigit(char_for_int) || isAlpha(char_for_int)) {
tokenText.append(char_for_int);
} else {
dfaState = initToken(char_for_int);
}
break;
case GT:
if (char_for_int == '=') {
current_token.tokenType = TokenType.GE;
dfaState = DfaState.GE;
tokenText.append(char_for_int);
} else {
dfaState = initToken(char_for_int);
}
break;
case GE:
case Assignment:
case IntLiteral:
if (isDigit(char_for_int)) {
tokenText.append(char_for_int);
} else {
dfaState = initToken(char_for_int);
}
break;
case Id_int1:
if (char_for_int == 'n') {
dfaState = DfaState.Id_int2;
tokenText.append(char_for_int);
} else if (isDigit(char_for_int) || isAlpha(char_for_int)) {
dfaState = DfaState.Id;
tokenText.append(char_for_int);
} else {
dfaState = initToken(char_for_int);
}
break;
case Id_int2:
if (char_for_int == 't') {
dfaState = DfaState.Id_int3;
tokenText.append(char_for_int);
}
else if (isDigit(char_for_int) || isAlpha(char_for_int)){
dfaState = DfaState.Id; //切換回id狀態
tokenText.append(char_for_int);
}
else {
dfaState = initToken(char_for_int);
}
break;
case Id_int3:
if (isBlank(char_for_int)) {
current_token.tokenType = TokenType.Int;
dfaState = initToken(char_for_int);
}
else{
dfaState = DfaState.Id; //切換回Id狀態
tokenText.append(char_for_int);
}
break;
default:
}
}
if (tokenText.length() > 0) {
initToken(char_for_int);
}
return new SimpleTokens(tokens);
}