? ? ? ? 從這一部分起,我們將逐步地去實現一個簡易C語言編譯器。所謂簡易,就是會依照C語言的標準,忽略部分的語法,適當降低難度,實現包含基本功能的C語言編譯器。我們將把主要的精力放在編譯過程這個環節,匯編和鏈接過程將會使用已有編譯器完成。
? ? ? ? 按照難易程度,我們先跳過前處理過程,直接進入編譯階段的詞法分析。我們先從下面這段簡單的C語言代碼入手:
int main()
{
return (2 + 2) * 2 - 8;
}
? ? ? ? 我并沒有用經典的Hello World!
那段代碼,因為那幾行簡單的代碼,涉及了非常多的知識。現在,我們需要由淺入深,逐步地去理解編譯器的實現方法。到時候再回過頭來分析那段經典代碼。
? ? ? ? 上面這段代碼非常簡單:沒有頭文件,只有一個主函數,函數內部進行一個簡單的四則運算,然后返回結果。結果很容易計算。但別忘了,我們的目的是實現一個編譯器,最終讓計算機CPU芯片的運算單元來求解這個結果。因此,需要從系統層面思考,去解決一類問題,從而處理更多的類似于這樣的計算過程。
? ? ? ? 前面提到,詞法分析是將源代碼分解成一個個的token,比如這里可以將源代碼分解為下面的一些token:
int | main | ( | ) | { | return | ( | 2 | + |
---|---|---|---|---|---|---|---|---|
2 | ) | * | 2 | - | 8 | ; | } |
? ? ? ? 相信大家也看明白了,詞法分析就是將這段代碼一個一個拆開。對于連續的字符,我們將它們組成為一個token,直到遇到空格為止,當然也包括換行符。因此,如果將int main
不小心寫成了intmain
,它也就成為了一個token,而不會被拆分為int
和main
。因為這可能就是定義的變量或者函數名,當然也可能只是程序員不小心沒有用空格將它們分開。
? ? ? ? 是不是很容易?下面,我們用Python來實現這部分功能。當然,喝水不忘挖井人,我將沿用這個博客文章(Let's Build A Simple Interpreter)里面所使用的方法和思路。
? ? ? ? 先定義一直提到的token類。
class Token(object):
def __init__(self, type, value, pos=None):
self.type = type
self.value = value
? ? ? ? 可以簡單地將token理解為一個鍵值對應的Map。其中,上面的C語言代碼會用到的token名字,包括:
# algebra
PLUS = 'PLUS'
MINUS = 'MINUS'
MUL = 'MUL'
# scope
LPAREN = 'LPAREN'
RPAREN = 'RPAREN'
BEGIN = 'BEGIN'
END = 'END'
# symbol
ID = 'ID'
SEMI = 'SEMI'
EOF = 'EOF'
# reserved keywords
INT = 'INT'
RETURN = 'RETURN'
? ? ? ? 用大寫字母表示,是為了區別python中的關鍵字。有了這些準備,我們就可以定義詞法分析的專用類:Lexer
。
class Lexer(object):
def __init__(self, text):
self.text = text
self.pos = 0
self.current_char = self.text[self.pos]
def next(self):
"""obtain the next the `pos` pointer ."""
self.pos += 1
if self.pos > len(self.text) - 1:
self.current_char = None # Indicates end of input
else:
self.current_char = self.text[self.pos]
def get_next_token(self):
"""Lexical analyzer. One token at a time."""
while self.current_char is not None:
if self.current_char.isspace():
self.skip_whitespace()
continue
if self.current_char.isalpha():
return self.identifier()
if self.current_char.isdigit():
return self.number()
if self.current_char == '+':
self.next()
return Token(PLUS, '+')
if self.current_char == '-':
self.next()
return Token(MINUS, '-')
if self.current_char == '*':
self.next()
return Token(MUL, '*')
if self.current_char == '(':
self.next()
return Token(LPAREN, '(')
if self.current_char == ')':
self.next()
return Token(RPAREN, ')')
if self.current_char == '{':
self.next()
return Token(BEGIN, '{')
if self.current_char == '}':
self.next()
return Token(END, '}')
if self.current_char == ';':
self.next()
return Token(SEMI, ';')
return Token(EOF, None)
? ? ? ? 這段代碼中的EOF
(同eof)代表文件結束符,表明一段源代碼已經全部讀取完畢。值得注意的是,Lexer
類中還缺少三個函數,分別處理空格,數字和標識符。一旦定義了這些函數,我們就可以實例化一個Lexer
對象來處理開頭的那段C語言代碼了。通過循環調用get_next_token()
,從而得到拆分出來的一個個token。類似的,處理更加復雜的源代碼時,只需要按照C語言語法的定義,增加token的名字,通過判斷當前字符是否匹配,就可以依樣畫葫蘆地處理得到對應的token了。
? ? ? ? 將那三個成員函數單獨提出來的原因,是因為實現方法決定了我們是假設每一個字符都可能為一個token。但對于空格、數字或者標志符等,可能需要將多個字符組成為一個token,而不能像其它代數符號那樣只去判斷一個字符。因此,需要單獨的函數進行處理。例如,對于數字的處理如下:
def number(self):
result = ''
while self.current_char is not None \
and self.current_char.isdigit():
result += self.current_char
self.next()
return Token(INT, int(result), self.line)
? ? ? ? 值得注意的是,我們的編譯器不支持浮點運算。在后面進行匯編語言生成的過程中,浮點運算可以說是完整的一大塊。從理解編譯器的角度出發,掌握理解了整數型運算,就已經差不多了。后續如果大家有興趣,完全可以擴充方法讓編譯器支持浮點型,原理都是差不多的。
? ? ? ? 同理,處理空白字符也是一樣的:
def skip_whitespace(self):
# isspace contains '', '\n', '\t', etc.
while self.current_char is not None \
and self.current_char.isspace():
self.next()
? ? ? ? 然后是標志符的處理:
def identifier(self):
"""Handle identifiers and reserved keywords, and macro definition replace"""
result = ''
while self.current_char is not None and (self.current_char.isalnum() or self.current_char == '_'):
result += self.current_char
self.next()
token = RESERVED_KEYWORDS.get(result.upper(), Token(ID, result))
return token
這里用到了RESERVED_KEYWORDS
。顧名思義,RESERVED_KEYWORDS
就是預先定義好的保留關鍵字字典:
RESERVED_KEYWORDS = {
'INT': Token('INT', 'int'),
'RETURN': Token('RETURN', 'return'),
}
? ? ? ? 至此,詞法分析的內容就分析完畢。
實現簡易的C語言編譯器(part 0)
實現簡易的C語言編譯器(part 2)
實現簡易的C語言編譯器(part 3)
實現簡易的C語言編譯器(part 4)
實現簡易的C語言編譯器(part 5)
實現簡易的C語言編譯器(part 6)
實現簡易的C語言編譯器(part 7)
實現簡易的C語言編譯器(part 8)
實現簡易的C語言編譯器(part 9)
實現簡易的C語言編譯器(part 10)
實現簡易的C語言編譯器(part 11)