總結
- 詞法分析就是對字符串進行處理,然後輸出對應的屬性字爲語法分析做準備;
- 如果對某個單詞進行分析後,不屬於任何屬性字,那麼也要按照錯誤處理
- 因此詞法分析需要使用到許多字符串處理函數
- 語法分析就是根據給出的第一個屬性字,然後預測接下來的屬性字,然後讀取下一個屬性字,進行比較,如果和預測相比錯誤,那麼就是語法錯誤
詞法分析
- 彙編器要做的所有事情並不是在一次同時完成的
- 語言處理器通常是分爲不同的階段,而每個階段都關注小的,相當簡單的任務
- 這些階段放在一起構成了一個管道,在它的不同階段源文件都會向他的目標形式前進一步
- 一般而言,翻譯任何語言的第一個階段是詞法分析,詞法分析是把源文件分解成組成它的詞
- 在分離和提取單詞之後,詞法分析器的真正工作是把單詞流轉變成屬性字流(Token stram)
- 把單詞流轉換成屬性字流的過程叫做屬性字識別,因此詞法分析器也叫做屬性字識別器
//字符串流
Mov Sum, X; 執行加法運算
//單詞流
MOV SUM, X
//屬性字流
TOKEN_TYPE_INSTR
TOKEN_TYPE_IDENT
TOKEN_TYPE_COMMA
TOKEN_TYPE_IDENT
語法分析
- 在管道中語法分析器緊跟在詞法分析器和屬性字識別器後,並且有一個非常重要的任務
- 給定一個屬性字流,當把它們作爲整體單元時,語法分析器負責把它們拼湊在一起
- 對於函數聲明的基本語法分析過程
Token CurrToken = GetNextToken();//從屬性字流中讀取下一個屬性字
if (CurrToken == TOKEN_TYPE_FUNC) //是否是一個函數聲明的開始
{
if (GetNextToken() == TOKEN_TYPE_IDENT)
{
string FuncName = GetCurrLexeme(); //當前的單詞是函數名 所以保存他
if (GetNextToken() == TOKEN_TYPE_OPEN_BRACKET) {
//正確的屬性字流
}
}
}
- 在對一個指令進行了語法分析之後,就可以使用指令查找標來驗證它的操作數並把它轉換成機器碼
字符串處理庫
- 空白符 空白符可以存在於任何一個字符串中,它通常被簡單地定義爲不可見的字符比如說空格,製表符,和和換行符
- 區分空白符是否包含換行符是很重要的。對於語句可以跨越多行的C語言中,換行沒有意義,空白可以包含空白符
- 字符串與字符可以通過許多方法進行分組和分類,例如如果一個字符串的每個字符都獨立滿足數字字符條件,那麼這個字符串就可以被看作一個數字字符串
- 你經常需要驗證各種各樣的字符串類型,範圍從標識符到浮點數再到單個的字符,比如說大括號或雙引號
- 這也是決定單詞相應屬性字時的公用功能,字符串處理函數庫應該包含字符串分類函數的擴展集
字符串分類函數
- 彙編器完成的時候,你會發現最頻繁需要的就是字符串分類函數
- 一般來說,當你按照你的方式處理源代碼的時候,你需要了解所給的字符是否是下面幾種中的一種
- 數字字符
- 合法標識符中的字符
- 空白符 (空格或製表符)
- 分隔符(用來分隔元素的符號,如括號,逗號等)
#define TRUE 1;
#define FALSE 0;
//判定一個字符是否是數字字符
int IsCharNumeric(char cChar) {
if (cChar >= '0' && cChar <= '9')
{
return TRUE;
}
else { return FALSE; }
}
//判定一個字符是否是空白符
int IsCharWhitespace(char cChar) {
if (cChar == ' ' || cChar == "\t")
{
return TRUE;
}
else
{
return FALSE;
}
}
//判定一個字符是否是有效標識符的部分
int IsCharIdent(char cChar) {
if ((cChar >= '0' && cChar <= '9') ||
(cChar >= 'A' && cChar <= 'Z') ||
(cChar >= 'a' && cChar <= 'z') ||
cChar == '_')
{
return TRUE;
}
else
{
return FALSE;
}
}
int IsCharDelimiter(char cChar) {
if (cChar == ';' || cChar == ',' || cChar == '"' || cChar == '[' || cChar == ']' || cChar == '{' || cChar == '}' || IsCharWhitespace(cChar))
{
return TRUE;
}
else {
return FALSE;
}
}
int IsStringInt(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
unsigned int iCurrCharIndex;
if (!IsCharNumeric(pstrString[0]) && !pstrString[0] == '-') return FALSE;
for (iCurrCharIndex = 1; iCurrCharIndex < strlen(pstrString); ++iCurrCharIndex)
{
if (!IsCharNumeric(pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
int IsStringFloat(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
unsigned int iCurrCharIndex;
for (iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharNumeric(pstrString[iCurrCharIndex])&&!(pstrString[iCurrCharIndex]=='.')&&!(pstrString[iCurrCharIndex]=='-'))
{
return FALSE;
}
}
//小數點
int iRadixPointFound = FALSE;
for (iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (pstrString[iCurrCharIndex]=='.')
{
if (iRadixPointFound) { return FALSE; }
else
{
iRadixPointFound = TRUE;
}
}
}
for (iCurrCharIndex = 1; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (pstrString[iCurrCharIndex]=='-')
{
return FALSE;
}
}
if (iRadixPointFound) {
return TRUE;
}
else {
return FALSE;
}
}
int IsStringWhitespace(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return TRUE;
for (unsigned int iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharWhitespace( pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
int IsStringIdent(char* pstrString) {
if (!pstrString) return FALSE;
if (strlen(pstrString) == 0) return FALSE;
if (pstrString[0] >= '0' && pstrString[0] <= '9') return FALSE;
for (unsigned int iCurrCharIndex = 0; iCurrCharIndex < strlen(pstrString); iCurrCharIndex++)
{
if (!IsCharIdent(pstrString[iCurrCharIndex]))
{
return FALSE;
}
}
return TRUE;
}
彙編程序
- 彙編程序主要是由管理各種腳本所定義元素如變量,函數和標籤的表組成
- 由於各個腳本之間的這些元素的數量都各不相同,對於他們當中的大部分表,可以使用鏈表來增長到需要的大小
- 我決定在內存中緩存所有的東西,這樣會使處理過程更快速
- 緩衝將要輸出的彙編指令流,
詞法分析器的接口
- int GetNextToken()
- 返回當前節點的屬性字並把當前節點向後移動一個節點,還要填入g_Lexer結構來反映所有的當前屬性字的信息
- 當我們對單詞進行分析的時候,因爲分隔符本身也是屬性字的一種,所以對於Index0,當遍歷到其不是空白符或者製表符的時候就停止,然後讓Index1等於Index0,再讓Index1進行累加遍歷,如果此時Index1遇到分隔符就停止,這樣就可以分離出來分隔符。但是其長度爲0,所以要讓index1加一。
- 不過此時會有一個bug," " "
- char * GetCurrLexeme()
- 返回一個字符指針,它指向包含當前單詞的字符串,什麼是g_Tokenizer
- GetLookAheadChar()
- 向前查看,查看位於當前屬性字之後的那個屬性字的處理過程。雖然他的確讀取了字符,但是它並沒有把屬性字流的當前指針向後移動。
- 如果當前讀取內容不足以讓你正確決定餘下的屬性字應該是什麼,在這些情況下使用向前查看
Var MyVar Var MyVar[256] //不確定性
- void SkipToNextLine()
- 僅僅想忽略掉一整行的屬性字,源代碼在內部存儲的時候被看作一系列單獨的行,這個函數就是增加當前行的計數並且重置屬性字識別器的位置
- ResetLexer()
- 重置一切,詞法分析器要對源代碼執行兩次遍歷,每次執行之前都需要重置,這個函數只會被使用兩次
錯誤處理
- 錯誤處理主要包括三個方面,檢測,重新同步和消息輸出
- 檢測是判斷錯誤是什麼時候發生的,它是什麼類型的錯誤
- 重新同步是使語法分析器回滾的處理過程,讓程序可以標記多重錯誤
- 錯誤消息必須輸出到屏幕或是某種類型的日誌文件
-
語法分析
- 重點在於識別初始的屬性字,並且根據那個初始的屬性字是如何適合語言的規則的,
- 來預知它後面應該跟隨什麼屬性字
- 根據這些初始的屬性字,你可以判斷你正在處理什麼種類的代碼