文章目錄
- 推薦:Tiny Lexer - 一個極簡的C語言詞法分析器
- 特點
- 核心代碼實現
- 學習價值
- 擴展建議
- 用Java實現一個簡單的詞法分析器
- 完整實現代碼
- 代碼解析
- 示例輸出
- 擴展建議
- 用Go實現極簡詞法分析器
- 完整實現代碼
- 代碼解析
- 示例輸出
- 擴展建議
最近兩天搞一個DSL,不得不重溫了一把antlr4和編譯原理之詞法分析。
推薦:Tiny Lexer - 一個極簡的C語言詞法分析器
推薦一個非常小巧但完整的C語言詞法分析器實現 - Tiny Lexer。它具有以下優點:
特點
- 代碼量極小(約100行核心代碼)
- 純C實現,無外部依賴
- 易于理解和學習
- 包含完整的功能:標識符、數字、運算符識別等
核心代碼實現
#include <stdio.h>
#include <ctype.h>
#include <string.h>typedef enum {TOKEN_EOF,TOKEN_NUMBER,TOKEN_IDENTIFIER,TOKEN_OPERATOR,TOKEN_UNKNOWN
} TokenType;typedef struct {TokenType type;char value[32];
} Token;Token get_next_token(const char** input) {Token token = {TOKEN_UNKNOWN, {0}};// 跳過空白字符while (isspace(**input)) {(*input)++;}// 檢查文件結束if (**input == '\0') {token.type = TOKEN_EOF;return token;}// 處理數字if (isdigit(**input)) {token.type = TOKEN_NUMBER;int i = 0;while (isdigit(**input) && i < sizeof(token.value)-1) {token.value[i++] = *(*input)++;}token.value[i] = '\0';return token;}// 處理標識符(字母開頭)if (isalpha(**input)) {token.type = TOKEN_IDENTIFIER;int i = 0;while ((isalnum(**input) || **input == '_') && i < sizeof(token.value)-1) {token.value[i++] = *(*input)++;}token.value[i] = '\0';return token;}// 處理運算符if (strchr("+-*/=(){};", **input)) {token.type = TOKEN_OPERATOR;token.value[0] = *(*input)++;token.value[1] = '\0';return token;}// 未知字符token.value[0] = *(*input)++;return token;
}int main() {const char* input = "int x = 42 + y;";const char* p = input;while (1) {Token token = get_next_token(&p);if (token.type == TOKEN_EOF) break;const char* type_str;switch (token.type) {case TOKEN_NUMBER: type_str = "NUMBER"; break;case TOKEN_IDENTIFIER: type_str = "IDENTIFIER"; break;case TOKEN_OPERATOR: type_str = "OPERATOR"; break;default: type_str = "UNKNOWN"; break;}printf("Token: %-12s Value: %s\n", type_str, token.value);}return 0;
}
學習價值
- 詞法分析基本原理:展示了如何將輸入流分解為token
- 狀態機概念:通過條件判斷實現了簡單的狀態轉移
- 可擴展性:可以輕松添加更多token類型和規則
- 實用性:雖然簡單,但包含了詞法分析的核心功能
擴展建議
學習這個基本實現后,你可以嘗試:
- 添加更多運算符和關鍵字識別
- 實現更復雜的數字格式(如浮點數)
- 添加錯誤處理機制
- 將其擴展為遞歸下降語法分析器
這個實現去除了所有不必要的復雜性,是學習編譯原理前端技術的理想起點。
用Java實現一個簡單的詞法分析器
下面我將實現一個最簡的詞法分析器(Lexer),它可以識別以下類型的詞法單元(Token):
- 整數(如
123
) - 浮點數(如
3.14
) - 標識符(如
variable
) - 運算符(如
+
,-
,*
,/
) - 分隔符(如
(
,)
,;
)
完整實現代碼
import java.util.ArrayList;
import java.util.List;public class SimpleLexer {// Token類型枚舉public enum TokenType {INTEGER, // 整數FLOAT, // 浮點數IDENTIFIER, // 標識符OPERATOR, // 運算符DELIMITER, // 分隔符EOF // 文件結束}// Token類public static class Token {public final TokenType type;public final String value;public Token(TokenType type, String value) {this.type = type;this.value = value;}@Overridepublic String toString() {return String.format("Token(%s, '%s')", type, value);}}private final String input;private int position = 0;public SimpleLexer(String input) {this.input = input;}// 詞法分析主方法public List<Token> tokenize() {List<Token> tokens = new ArrayList<>();while (position < input.length()) {char current = peek();// 跳過空白字符if (Character.isWhitespace(current)) {consume();continue;}// 處理數字if (Character.isDigit(current)) {tokens.add(readNumber());continue;}// 處理標識符(以字母或下劃線開頭)if (Character.isLetter(current) || current == '_') {tokens.add(readIdentifier());continue;}// 處理運算符if (isOperator(current)) {tokens.add(new Token(TokenType.OPERATOR, Character.toString(consume())));continue;}// 處理分隔符if (isDelimiter(current)) {tokens.add(new Token(TokenType.DELIMITER, Character.toString(consume())));continue;}throw new RuntimeException("Unexpected character: " + current);}tokens.add(new Token(TokenType.EOF, ""));return tokens;}// 讀取數字(整數或浮點數)private Token readNumber() {StringBuilder sb = new StringBuilder();boolean hasDecimal = false;while (position < input.length() && (Character.isDigit(peek()) || (!hasDecimal && peek() == '.'))) {if (peek() == '.') {hasDecimal = true;}sb.append(consume());}return new Token(hasDecimal ? TokenType.FLOAT : TokenType.INTEGER, sb.toString());}// 讀取標識符private Token readIdentifier() {StringBuilder sb = new StringBuilder();while (position < input.length() && (Character.isLetterOrDigit(peek()) || peek() == '_')) {sb.append(consume());}return new Token(TokenType.IDENTIFIER, sb.toString());}// 輔助方法:查看當前字符但不消耗private char peek() {return input.charAt(position);}// 輔助方法:消耗當前字符并返回private char consume() {return input.charAt(position++);}// 判斷是否是運算符private boolean isOperator(char c) {return c == '+' || c == '-' || c == '*' || c == '/';}// 判斷是否是分隔符private boolean isDelimiter(char c) {return c == '(' || c == ')' || c == ';';}// 測試public static void main(String[] args) {String input = "x = 123 + (y * 3.14);";SimpleLexer lexer = new SimpleLexer(input);List<Token> tokens = lexer.tokenize();for (Token token : tokens) {System.out.println(token);}}
}
代碼解析
-
Token類型定義:
- 使用枚舉
TokenType
定義了各種詞法單元類型 Token
類封裝了類型和實際值
- 使用枚舉
-
核心方法:
tokenize()
:主方法,逐個字符分析輸入字符串readNumber()
:讀取整數或浮點數readIdentifier()
:讀取標識符
-
輔助方法:
peek()
:查看當前字符但不移動指針consume()
:讀取當前字符并移動指針isOperator()
和isDelimiter()
:判斷字符類型
-
處理流程:
- 跳過空白字符
- 按優先級識別數字、標識符、運算符和分隔符
- 遇到無法識別的字符拋出異常
示例輸出
對于輸入"x = 123 + (y * 3.14);"
,輸出結果為:
Token(IDENTIFIER, 'x')
Token(OPERATOR, '=')
Token(INTEGER, '123')
Token(OPERATOR, '+')
Token(DELIMITER, '(')
Token(IDENTIFIER, 'y')
Token(OPERATOR, '*')
Token(FLOAT, '3.14')
Token(DELIMITER, ')')
Token(DELIMITER, ';')
Token(EOF, '')
擴展建議
這個簡單詞法分析器可以進一步擴展:
- 支持更多運算符(如
==
,!=
等) - 添加關鍵字識別(如
if
,while
等) - 支持字符串字面量
- 添加注釋處理
- 改進錯誤處理機制
這個實現是編譯器前端的基礎組件,可以作為更復雜詞法分析器的起點。
用Go實現極簡詞法分析器
下面是一個用Go語言實現的極簡詞法分析器,它可以識別以下詞法單元:
- 整數(如
123
) - 標識符(如
variable
) - 運算符(如
+
,-
,*
,/
) - 分隔符(如
(
,)
,;
)
完整實現代碼
package mainimport ("fmt""unicode"
)// TokenType 表示詞法單元類型
type TokenType intconst (EOF TokenType = iota // 文件結束INTEGER // 整數IDENTIFIER // 標識符OPERATOR // 運算符DELIMITER // 分隔符
)// Token 表示一個詞法單元
type Token struct {Type TokenTypeValue string
}// String 實現Stringer接口
func (t Token) String() string {return fmt.Sprintf("Token(%v, '%s')", t.Type, t.Value)
}// Lexer 詞法分析器結構體
type Lexer struct {input stringposition int
}// NewLexer 創建新的詞法分析器
func NewLexer(input string) *Lexer {return &Lexer{input: input}
}// NextToken 獲取下一個詞法單元
func (l *Lexer) NextToken() Token {// 跳過空白字符l.skipWhitespace()// 檢查是否到達輸入末尾if l.position >= len(l.input) {return Token{Type: EOF, Value: ""}}current := l.peek()// 處理數字if unicode.IsDigit(current) {return l.readNumber()}// 處理標識符if unicode.IsLetter(current) || current == '_' {return l.readIdentifier()}// 處理運算符if isOperator(current) {token := Token{Type: OPERATOR, Value: string(l.consume())}return token}// 處理分隔符if isDelimiter(current) {token := Token{Type: DELIMITER, Value: string(l.consume())}return token}panic(fmt.Sprintf("未知字符: %c", current))
}// 讀取數字
func (l *Lexer) readNumber() Token {start := l.positionfor l.position < len(l.input) && unicode.IsDigit(l.peek()) {l.consume()}return Token{Type: INTEGER, Value: l.input[start:l.position]}
}// 讀取標識符
func (l *Lexer) readIdentifier() Token {start := l.positionfor l.position < len(l.input) && (unicode.IsLetter(l.peek()) || unicode.IsDigit(l.peek()) || l.peek() == '_') {l.consume()}return Token{Type: IDENTIFIER, Value: l.input[start:l.position]}
}// 跳過空白字符
func (l *Lexer) skipWhitespace() {for l.position < len(l.input) && unicode.IsSpace(l.peek()) {l.consume()}
}// 查看當前字符但不消耗
func (l *Lexer) peek() rune {if l.position >= len(l.input) {return 0}return rune(l.input[l.position])
}// 消耗當前字符并返回
func (l *Lexer) consume() rune {if l.position >= len(l.input) {return 0}char := rune(l.input[l.position])l.position++return char
}// 判斷是否是運算符
func isOperator(r rune) bool {return r == '+' || r == '-' || r == '*' || r == '/'
}// 判斷是否是分隔符
func isDelimiter(r rune) bool {return r == '(' || r == ')' || r == ';'
}func main() {input := "x = 123 + (y * 456);"lexer := NewLexer(input)for {token := lexer.NextToken()fmt.Println(token)if token.Type == EOF {break}}
}
代碼解析
-
Token類型定義:
- 使用
TokenType
枚舉定義詞法單元類型 Token
結構體包含類型和實際值
- 使用
-
Lexer結構體:
- 保存輸入字符串和當前位置
- 提供
NextToken()
方法逐個生成詞法單元
-
核心方法:
readNumber()
:讀取整數readIdentifier()
:讀取標識符skipWhitespace()
:跳過空白字符
-
輔助方法:
peek()
:查看當前字符但不移動指針consume()
:讀取當前字符并移動指針isOperator()
和isDelimiter()
:判斷字符類型
示例輸出
對于輸入"x = 123 + (y * 456);"
,輸出結果為:
Token(IDENTIFIER, 'x')
Token(OPERATOR, '=')
Token(INTEGER, '123')
Token(OPERATOR, '+')
Token(DELIMITER, '(')
Token(IDENTIFIER, 'y')
Token(OPERATOR, '*')
Token(INTEGER, '456')
Token(DELIMITER, ')')
Token(DELIMITER, ';')
Token(EOF, '')
擴展建議
這個基礎詞法分析器可以進一步擴展:
- 添加浮點數支持
- 支持更多運算符(如
==
,!=
等) - 添加關鍵字識別
- 支持字符串字面量
- 添加注釋處理
- 改進錯誤處理機制
這個實現展示了Go語言簡潔高效的特點,適合作為更復雜詞法分析器的基礎。