Clang Parser漫步——declaration-specifiers(二)

既上文Clang Parser漫步——external-declaration(一) - XlousZeng的文章 - 知乎專欄說到解析external-declaration之後,本文將會解釋Clang是如何解析declaration-specifiers的。

首先,這裡先給出C99中declaration-specifiers的EBNF語法:

declaration-specifiers:: [C99 6.7]n storage-class-specifier declaration-specifiers[opt]n type-specifier declaration-specifiers[opt]n [C99] function-specifier declaration-specifiers[opt]nnstorage-class-specifier:: [C99 6.7.1]n typedefn externn staticn auton registernnfunction-specifier: [C99 6.7.4]n[C99] inlinenntype-specifier::n charn wchar_tn booln shortn intn longn signedn unsignedn floatn doublen voidn [C99] _Booln [C99] _Complexn [C99] _Imaginary // Removed in TC2?n enum-specifiern struct-specifiern union-specifiern cv-qualifiernncv-qualifier::n constn volatilen restrictn

一、入口

現在可以按照上述的語法對declaration-specifiers進行解析了;入口函數在ParseDecl.cpp:1581行

接下來在一個while循環中使用switch語句去處理上述右遞歸文法。略過處理c++,objective-c語法的邏輯。

上述的DoneWithDeclSpec是解析完成之時的出口,會調用DS對象的finish函數完成一個declaration-specifiers的解析,進行語義檢查,如:

unsigned double是非法的;nunsigned x = 4;  //x的默認類型將會轉換為unsigned int.nunsigned unsigned x = 4; // 錯誤duplicate unsigned declaration specifiern

Clang將declaration-specifiers分為如下幾類,不同的類別是可以同時存在一個聲明中的,但同一組中只能有一個。

  1. TypeSpecifierType: char, wchar_t, _Bool, foat, double, void, enum, union, struct;
  2. TypeSpecifierWidth: short, long, long long;
  3. TypeSpecifierComplex: _Imaginary, _Complex;
  4. TypeSpecifierSign: unsigned, signed;
  5. TypeQualifier: const, restrict, volatile;
  6. StorageClassSpecifier: typedef, extern, static, auto, register;

處理identifier,這個地方會區分普通標識符還是typename:

將其作為typename,並進行名字查找:

處理storage-class-specifier:

處理function-specifiers,這些都是在C99中才加入的,如果你不需要處理C99的語法,可以跳過此類。[注意,virtual和explicit是C++的語法]

處理type-specifiers,[注意:__int64是Clang擴充的整數類型]

其他基本上都是類似的,就不貼圖了,下面處理的是type-qualifiers:

然後就是重點,解析struct/union,enum類型的定義和聲明。在Clang中,struct/union是作為一類來處理的,與C++中的class關鍵字同理。

二、解析Struct/Union-specifier

ParseClassSpecifier函數在ParseDeclCXX.cpp:839行。

產生式為:nstruct-or-union-specifier: [C99 6.7.2.1]n struct-or-union identifier[opt] { struct-contents }n struct-or-union identifiern[GNU] struct-or-union attributes[opt] identifier[opt] { struct-contents attributes[opt]nn[GNU] struct-or-union attributes[opt] identifiernn struct-or-union:n structn unionn

首先進行一個簡單的區分是struct or union or class[c++語法]

之後就是解析各種gnu和microsoft的擴展了,我們這時候可以跳過。

判斷是否有tag名字或者簡單的模板id,不考慮c++模板。因為struct/union有匿名語法。

判斷該struct/union X...是reference ? definition ? declaration。

然後得到如果TUK表示定義,則會轉入解析struct/union body的函數,我們略過對c++語法的解析邏輯。

後面的部分就是Clang的錯誤語法產生式,將某些常見的錯誤語法形式編碼到Parser中,為了更好的進行錯誤提示,可以了解下。

三、解析enum-specifier

解析函數依舊在ParseDecl.cpp中,處於2766行。

產生式:nParseEnumSpecifiern enum-specifier: [C99 6.7.2.2]n enum identifier[opt] { enumerator-list }n[C99/C++]enum identifier[opt] { enumerator-list , }nn enum identifier [類型引用或者聲明]n

對於C++11來說,一個enum定義了一個作用域,但是C語言中沒有。所以此處可以跳過作用域的處理。

判斷是否有tag名字,否則就是一個匿名enum。

與之前解析struct-specifier類似,判斷該語法為何種形式, 定義或聲明或引用

名字處理好了之後,調用語義動作生成一個EnumDecl。

然後就可以解析enum {....}了。

最後進行語義判斷,是否有多個enum聲明,有個話將進行錯誤提示。

三、解析Enum聲明成員

首先聲明一個作用域,用於檢查同名變數,注意對於C語言來說,enum中沒有一個單獨的作用域,enum體內所有的變數的作用域在該enum聲明所在的作用域,比如在頂層聲明一個enum,那麼enum常量所屬的作用域是最頂層作用域。

//對於如下代碼:nenum Xn{n XX = 0,n XZ,n};nnenum Yn{n XX = 0;n};nn// 將會提示redefinition of enumerator XX。n// 如果此處使用c++11 enum class語法,帶來的效果將會如Java中enum聲明一樣。n// 單獨形成一個作用域。nn// 如下代碼在Clang 3.9 中使用-std=c++11編譯通過。nenum class Xn{n XX = 0,n XZ,n};nnenum class Yn{n XX = 0,n};n

好了,回歸正題,在一個死循環中以此解析每個enum-decelarator,將結果保存在一個SmallVector中。

得到一個標識符,判斷是否有賦值表達式,有的話在3031行解析常量表達式。

每個enumerator-constant解析完成之後,調用語義動作生成一個EnumConstantDecl對象,並添加到集合中。

當所有的成員解析完成之後,退出while循環,調用語義動作生成一個EnumDecl對象,對進行語義檢查。

推薦閱讀:

有沒有不適合使用flex/lex作為詞法分析器的語言?
KLEE到底是靜態分析工具還是動態分析工具?
(發散性問題,請隨意加上假設)一門編程語言同時擁有解釋器和編譯器的必要性有多大?
從哪裡可以得到包含了Python3所支持的所有語法的測試用例?
為什麼C++預處理期編譯期的支持比較薄弱?

TAG:Clang | C编程语言 | 编译原理 |