【正文】
ALL MODULEDEF TESTCASE TC_VB_ND_001 CONFIGSPEC RUNONSPEC COMPONENTTYPE NetworkNode STATEBLOCK TIMERINS SINTIMERINS RetransTimer STARTTIMERSTATEMENT RetransTimer OPTTIMERVAL 按照語法樹的層次結(jié)構(gòu),輸出了每個節(jié)點的名稱,在我們的編譯程序中它和語法規(guī)則左邊的非終結(jié)符名稱一致。 語法分析的錯誤處理我們沿用YACC默認對語法分析的錯誤處理,就是當發(fā)現(xiàn)語法錯誤就停下來,并指出錯誤的位置。 語義分析 語義分析簡介語義分析是編譯過程的第三個階段。語義就是一組說明詞法和語法結(jié)構(gòu)含義的規(guī)則,通過它可以定義一個程序的意義。程序的語義確定程序的運行,但是大多數(shù)的程序設計語言都具有在執(zhí)行之前被確定的特征,這些特征被稱作靜態(tài)語義( Static Semantic)[38]。語義的闡明要比語法的闡明難得多,至今還沒有比較公認的形式系統(tǒng)可以用來構(gòu)造適用的語義分析器。語義分析審查每個語法結(jié)構(gòu)的靜態(tài)語義(以下簡稱為語義),即驗證語法結(jié)構(gòu)合法的程序是否真正有意義。 在YACC中實現(xiàn)語義分析語義分析的工作包括類型檢查、控制流檢查、一致性檢查、相關(guān)名字檢查等[36]。在本編譯器中,它與TTCN3的類型、聲明、作用域規(guī)則等密切相關(guān)[16]。本編譯器采用兩遍掃描的方式,語義分析與詞法和語法分析都在一遍之內(nèi)完成,也是通過YACC的嵌入式動作來實現(xiàn)的。,每個節(jié)點都有op,name,type等屬性值。在識別出語法結(jié)構(gòu)后進行即時處理的嵌入式代碼中,在生成新節(jié)點的同時,根據(jù)語義規(guī)則和屬性值對新節(jié)點與其子節(jié)點在語義上能否構(gòu)成一棵樹進行檢查。事實上,每當YACC識別出一個語法結(jié)構(gòu),就是完成了一條語法規(guī)則的歸約。歸約是自底向上的,只要將每次歸約并成功通過語義檢查的節(jié)點屬性記錄下來,再向上繼續(xù)歸約時就可以利用這些屬性,而無需再遞歸地去逐級向下檢查直到葉節(jié)點。 符號表在編譯過程中,編譯程序需要不斷匯集和反復查證出現(xiàn)在源程序中各種名字的屬性和特征等有關(guān)信息。這些信息通常記錄在符號表[36]中。符號表的每一項都包含兩部分:一部分是名字,另一部分是此名字的有關(guān)信息。這些信息將用于語義檢查、代碼生成等不同階段。編譯過程中每當遇到一個名字,都要查找符號表,看其是否存在。如果存在則根據(jù)需要讀取或更新其信息;如果不存在,則添加入口,將此名字及相關(guān)信息登記在符號表中。當某個名字不再有效,還要對其進行刪除。對符號表的操作非常頻繁,其效率直接影響到編譯程序的效率,因此符號表的組織非常重要。與大多數(shù)編譯程序一樣,本編譯器的符號表采用的數(shù)據(jù)結(jié)構(gòu)是散列表 [36]。散列函數(shù)是:static unsigned int hash_function(char *lexeme) { unsigned int h = 0。 while (*lexeme) h = (h2) + (*lexeme++)。 return h % MAX_TAB_SIZE。}散列表的長度MAX_TAB_SIZE設為1000。散列表的每一項都是一個單獨的鏈表SEntryList,SEntryList的每個節(jié)點包括指向表項Entry的指針sentry,和指向下一個節(jié)點的指針next。具體的數(shù)據(jù)結(jié)構(gòu)如下:typedef struct node { struct Entry * sentry。 struct node * next。} *SEntryList。其中結(jié)構(gòu)Entry是符號表項的數(shù)據(jù)結(jié)構(gòu),包括符號的名字及屬性。數(shù)據(jù)結(jié)構(gòu)如下:typedef struct Entry { char *lexeme。 /* 記錄標識符的名字屬性 */ enum SEntry_kind use。 /* 標明該符號表表項是哪類聲明 */ enum Types type。 /* 標明標識符的類型屬性 */ union /* 聯(lián)合了八類聲明的屬性 */ { struct { /* 該結(jié)構(gòu)體定義了常量聲明的屬性 */ int offset。 /* 偏移量,標明存儲位置 */ enum scopes scope。 /* 常量聲明的作用域?qū)傩?*/ } const。 struct { /* 該結(jié)構(gòu)體定義了變量聲明的屬性 */ int offset。 /* 由于TTCN3不可以聲明全局變量,所以變量聲明中沒有作用域?qū)傩?*/ } var。 …………………………………… struct { /* 該結(jié)構(gòu)體定義了測試例聲明的屬性 */ int offset。 SEntryLIST formals。 / *為參數(shù)而新建的鏈表 */ SEntryLIST locals。 /* 為本地聲明新建的鏈表 */ } testcase。 } unionOfval。} *SEntry。 該結(jié)構(gòu)體中定義的成員包括:標識符名,標識符的類型,以及其它的相應于該類標識符的屬性。TTCN3主要有六大類聲明[16],且各類聲明都有各自的屬性特點,因此要在結(jié)構(gòu)體中分別定義這幾類聲明的屬性特點。本編譯器中每類聲明的屬性都被定義為一個結(jié)構(gòu)體,由于每個表項的標識符只可能屬于一類聲明,所以各類聲明的結(jié)構(gòu)體統(tǒng)一用一個union結(jié)構(gòu)來表示。標識符很重要的一個屬性就是作用域。C語言等由于要遵循最近嵌套作用域規(guī)則,所以在設計相關(guān)作用域的屬性時比較復雜,通常要定義實現(xiàn)嵌套功能的屬性。由于TTCN3具有名字唯一性,且不允許全局變量定義等特點[16],使得本編譯器的作用域?qū)傩员容^簡單,僅包括全局、本地和參數(shù)三種作用域。 語義分析的輸出語義分析的結(jié)果是通過檢查,或者發(fā)現(xiàn)語義錯誤。例如以下一小段TTCN3程序,注釋是標明語義出錯的地方。module MyModule { function MyBehaviourA(integer a,integer b) { var integer a。 // 重復聲明 c:=a+b。 // 使用的變量c沒聲明過 if(a) // 運算語句() { a:=10。} } type port IPPortType mixed { inout integer。 } type ponent NetworkNode { port IPPortType NSAP。 } testcase case() runs on NetworkNode { MyBehaviourA(2,)。 // 的類型與聲明的形參// 的類型不一致 timer RetransTimer。 (1)。 // timer操作中要求timer值為浮點類型。 (9)。 // 只有port才可以進行send操作,// 類型錯 }}利用嵌入語義分析子程序的語法分析器對其進行語義分析,輸出結(jié)果如下,提示Semantic error的地方,表明該行語義有錯誤。...Semantic error at line 5: name clash, multiple declarations...Semantic error at line 6: no declarations for identifier c...Semantic error at line 7: type mismatch, cannot INTEGER_TYPE FLOAT_TYPE...Semantic error at line 18: the type of the actual parameter does not match the declared formal parameter...Semantic error at line 20: the type of the expression is not correct...Semantic error at line 21: the type of NetworkNode is not correct如果順利通過了語義檢查,則提供了完整的符號表供后續(xù)編譯階段使用。 語義分析的錯誤處理由于語義分析是在YACC中嵌入動作實現(xiàn),并不影響YACC對詞法和語法的分析,因此當發(fā)現(xiàn)語義錯誤時可以不停下來,而是指明錯誤的位置和類型等信息,繼續(xù)往下編譯。如果某個節(jié)點因為語義分析未通過而導致其屬性不確定,則會引起其上面的系列節(jié)點語義錯誤。第4章 代碼生成 代碼生成簡介上一章我們討論了在第一遍掃描過程中,如何通過詞法、語法和語義分析生成語法樹和符號表,檢查語義是否正確,并輸出以上過程中發(fā)現(xiàn)的錯誤信息,以便測試控制數(shù)據(jù)定義或描述人員檢查或改正相關(guān)的語句。經(jīng)過反復修改和重復第一遍掃描過程,獲得詞法、語法和語義都正確的用TTCN3描述的測試控制數(shù)據(jù),這就為第二遍掃描生成C代碼奠定了基礎。代碼生成以詞法和語法分析所構(gòu)造的語法樹為主要輸入,結(jié)合符號表來生成本編譯程序的目標代碼,即C語言代碼。通常以匯編語言或機器碼為目標代碼的編譯程序需要某種形式的中間代碼,這是因為大多數(shù)機器的指令集都具有運算符加1至2個運算碼的規(guī)則形式,用中間代碼可以方便目標代碼的生成[36]。常用的中間代碼有三地址碼和P代碼[36]。而本編譯器的源語言(TTCN3)和目標語言(C語言)都屬于或接近高級語言,不具有規(guī)則形式,因此本編譯器沒有選擇或設計中間代碼,而直接從語法樹和符號表生成C代碼。 目標代碼的形式本編譯器的目標代碼是C++的源代碼,它與編譯程序自身的代碼和工作環(huán)境無關(guān)。之所以采用C++而不是標準C,是為了簡化代碼生成的算法,增強目標代碼的結(jié)構(gòu)性和可讀性,而且現(xiàn)在絕大多數(shù)的C編譯和集成開發(fā)工具都能支持C++編譯。同樣基于結(jié)構(gòu)化和可讀性方面的考慮,我們生成的目標C++代碼不是全部包含在一個唯一的C++模塊中,而是按TTCN3測試集的結(jié)構(gòu)分為多個模塊,每個模塊包含對TTCN3的Module或單個TestCase的目標代碼。以一個典型的TTCN3 Module為例,其編譯結(jié)果可能包含以下文件: // 模塊定義部分,數(shù)據(jù)類型和常量等定義 // 模塊控制部分,控制測試例的執(zhí)行,// 包含main()函數(shù) // 測試例1的定義 // 測試例1的實現(xiàn) // 測試例2的定義 // 測試例2的實現(xiàn)…除此以外,還將包含兩個文件:,它是編譯器提供的公用文件,包含以上文件所需的類型定義、全局變量和庫函數(shù)等。將編譯的目標代碼文件放在一個目錄下,用C的編譯工具將其單獨或與其它程序聯(lián)合進行編譯、鏈接,就可得到可執(zhí)行程序。 代碼生成的基本思路代碼生成的基本思路是:采用通用的對語法樹進行遍歷的方式生成目標代碼[36]?;舅惴捎靡韵逻f歸過程描述:procedure genCode( T: treenode ) 。beginif T is not nil thenbegingenerate code to implement the action of T。genCode (1st child of T ) 。genCode (2nd child of T ) ?!璯enCode (last child of T ) 。generate code to implement the action of T。 end。end。在實際代碼生成時,由于源語言和目標語言的構(gòu)造差別,因此對一個樹節(jié)點T的所有子節(jié)點的遞歸調(diào)用不一定完全按照從左到右的順序進行;同樣對節(jié)點T本身動作的實現(xiàn)也可能穿插在對各個子節(jié)點代碼生成之間。在遞歸過程中具體的實現(xiàn)代碼和子節(jié)點調(diào)用方面的特別處理由genCode函數(shù)根據(jù)節(jié)點T的屬性進行區(qū)分。代碼生成的實現(xiàn)主要考慮以下幾個方面:一是對某些不能生成實際C代碼的語法結(jié)構(gòu)或元素的處理。基本原則是在代碼生成中忽略,而在語義分析中檢查。比如下面的端口類型定義:type port PortType1 mixed{inout MessageType1,MessageType2。}。其中MessageTypeMessageType2是PortType1類端口允許發(fā)送和接收的數(shù)據(jù)類型,它不能被轉(zhuǎn)換成端口的收發(fā)函數(shù)的形式參數(shù)類型,只