C/C++コンパイラがコンパイルを敬遠するGCC拡張とは

# 概要
GCC拡張という普段聞き慣れないものの仕様を調査した結果、他のコンパイラがコンパイルを敬遠するGCC拡張が含まれていることがわかりました。
そういったことを踏まえつつ、[前回の記事](https://qiita.com/HiroInTFLab/items/0b382a35abb6d7def494)で取り上げたパーサーを```C++14 Support in GCC```へと近づけるべく変更を加え、ESP32のソースコードのパース成功率向上につなげました。
これらが何かの一助となれば幸いです。

# GCC拡張とは
gccに関して、少し四方山話的なものが含まれます。
パーサーの改善効果を知りたい方は本記事の末尾を参照してください。
[前回の記事](https://qiita.com/HiroInTFLab/items/0b382a35abb6d7def494)で、初期化指示子を例に出しました。
この初期化指示子を含む、標準の言語仕様として定められていないもののコンパイラが独自に対応したものは、gccでは拡張文法と位置づけられており、それはGCC拡張（WikipediaにはGNU拡張と表現されていますが、ここではGCC拡張という用語で統一して話を進めます）と言われています。

## GCC拡張はどれくらいあるのか
GCC拡張は

https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html

でその一覧を確認することができます。
拡張なので少しかと思いきや、その数65個。
それらのタイトルとコメントから自分なりにざっと分類すると、
+ マクロなどのプリプロセッサに関係するもの
+ 複素数などの特殊な型定義に関するもの
+ 関数や変数に属性を設定する特殊な予約語
+ 組み込み関数に関わるもの
+ gotoに類似のもの

などなど、コンパイラ全般に渡っていることが伺えます。

## コンパイラが敬遠するGCC拡張
個別に見ていくと、例えば[Complex](https://gcc.gnu.org/onlinedocs/gcc/Complex.html#Complex)などはすでにC++14に取り込まれているとありますし、[Case Ranges](https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html#Case-Ranges)は拡張と呼ぶには文法が平易で、数値の範囲を取り扱うことの多い組み込みで使ったら便利なのではと思うものもあります。
そんな中、特に興味深いものは、[Arrays of Variable Length](https://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html#Variable-Length)のうち、構造体内での可変長配列（VLAIS）に対するものでしょうか。
可変長配列自体は馴染みがあるのですが、VLAISとなると事情が変わってくるようで、

http://thelinuxjedi.blogspot.com/2014/02/why-vlais-is-bad.html

には、移植性低下、デバッガが期待した動作をするのか、リーナス・トーバルズのコメントの引用など、VLAISが好まれていない背景が書いてあって興味深いです。移植性低下を嫌ってかわかりませんが、[各コンパイラの動作を確認した方の質問](https://stackoverflow.com/questions/14629504/variable-length-array-in-the-middle-of-struct-why-this-c-code-is-valid-for-gcc)において、gccの代替を狙っているはずのclangでも、```'variable length array in structure' extension will never be supported```と、そのコードを検出する機能を入れてまでエラーを報知するようにしているくらいなので、相当嫌なんだろうということが伝わってきます。

# 再びパス率改善へ
さて、GCC拡張についてはだいたい把握できたので、四方山話はここまでにして、ここからは前回取り上げたパーサーの変更について説明します。

## 環境やバージョン
+ Windows10 Pro
+ antlr4
+ g++ 11.2.0（cygwin経由）

## GCC拡張で対応するもの
初期化指示子以外の、ESP32のソースコードに対してパーサーが報知するエラーには
```
line 32:23 no viable alternative at input 'voidserialEvent(void)__attribute__'
line 61:24 no viable alternative at input 'voidserialEvent1(void)__attribute__'
line 82:24 no viable alternative at input 'voidserialEvent2(void)__attribute__'
```
といったものがあります。
パーサーは末尾の```__attribute__```の文字列に到達した時点でエラーを報知しており、これはパーサーがGCC拡張の[Function Attributes](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function-Attributes)と呼ばれるものに対応していないためです。
エラー内容から対応するGCC拡張が何かという分析過程は長くなるので省略しますが、ESP32のソースコードの構文解析において対応が必要なGCC拡張は次のようになります。
+ Function Attributes
+ Variable Attributes
+ Type Attributes
+ Label Attributes
+ Enumerator Attributes
+ Statement Attributes
+ Typeof

Attributeにかかわるものばかりですね。

## g4ファイルを見直す
上記のGCC拡張に対応するに当たり、Cpp14LexerとCpp14Parserにひとつずつ定義を独自に追加する、でもよかったものの、前回も参考にした[antlr4向けのC言語ルール定義](https://github.com/antlr/grammars-v4/blob/master/c/C.g4)ではすでに対応がなされていると見受けられたので、今回も参考にしました。
多数の予約語が含まれることから、CPP14Parser.g4だけでなく、CPP14Lexer.g4にも変更を加えていきます。
まずは、CPP14Lexer.g4の変更から。

```diff
-
+ Extention_: '__extension__';
+ BuiltinVaArg_: '__builtin_va_arg';
+ BuiltinOffsetOf: '__builtin_offsetof';
+ M128_: '__m128';
+ M128d_: '__m128d';
+ M128i_: '__m128i';
+ Atomic_: '_Atomic';
+ TypeOf: 'typeof';
+ TypeOf_: '__typeof__';
+ Noreturn_: '_Noreturn';
+ Stdcall_: '__stdcall';
+ Declspec_: '__declspec';
+ Alignas_: '_Alignas';
+ Thread_local_: '_Thread_local';
+ Asm_: '__asm';
+ Attribute_: '__attribute__';
+ Inline_: '__inline__';
```

:::note info
```M128_```や```Thread_local_```はESP32のソースコードのパースにおいて不要ではあるものも混じっています。ご注意ください。
:::

次は、CPP14Parser.g4の変更点。

```diff
primaryExpression:
	literal+
	| This
	| LeftParen expression RightParen
+    | Extention_? LeftParen compoundStatement RightParen
+    | BuiltinVaArg_ LeftParen unaryExpression Comma simpleTypeSpecifier RightParen
+    | BuiltinOffsetOf LeftParen simpleTypeSpecifier Comma unaryExpression RightParen
	| idExpression
	| lambdaExpression;

unaryExpression:
	postfixExpression
	| (PlusPlus | MinusMinus | unaryOperator | Sizeof) unaryExpression
	| Sizeof (
		LeftParen theTypeId RightParen
		| Ellipsis LeftParen Identifier RightParen
+		| AndAnd Identifier
	)
	| Alignof LeftParen theTypeId RightParen
	| noExceptExpression
	| newExpression
	| deleteExpression;

jumpStatement:
	(
		Break
		| Continue
		| Return (expression | bracedInitList)?
		| Goto Identifier
+    	| Goto unaryExpression
	) Semi;

declSpecifier:
	storageClassSpecifier
	| typeSpecifier
	| functionSpecifier
+    | alignmentspecifier
	| Friend
	| Typedef
	| Constexpr
;

functionSpecifier:
	Inline | Virtual | Explicit
+	| Noreturn_
+	| Inline_
+	| Stdcall_
+    | gccAttributeSpecifier
+    | Declspec_ LeftParen Identifier RightParen
;

simpleTypeSpecifier:
	nestedNameSpecifier? theTypeName
	| nestedNameSpecifier Template simpleTemplateId
	| simpleTypeSignednessModifier
	| simpleTypeSignednessModifier? simpleTypeLengthModifier+
	| simpleTypeSignednessModifier? Char
	| simpleTypeSignednessModifier? Char16
	| simpleTypeSignednessModifier? Char32
	| simpleTypeSignednessModifier? Wchar
	| Bool
	| simpleTypeSignednessModifier? simpleTypeLengthModifier* Int
	| Float
	| simpleTypeLengthModifier? Double
	| Void
	| Auto
+    | M128_
+    | M128d_
+    | M128i_
+    | Extention_ LeftParen (M128_ | M128d_ | M128i_) RightParen
+    | atomicTypeSpecifier
+    | typeOfOperator LeftParen constantExpression RightParen 
	| decltypeSpecifier;
+
+typeOfOperator:
+	TypeOf | TypeOf_;
+
+ atomicTypeSpecifier
+    : Atomic_ LeftParen simpleTypeSpecifier RightParen
+    ;

declarator:
-　pointerDeclarator
-	| noPointerDeclarator parametersAndQualifiers trailingReturnType;
+	pointerDeclarator gccDeclaratorExtension*
+	| noPointerDeclarator parametersAndQualifiers trailingReturnType gccDeclaratorExtension*;
+
+ gccDeclaratorExtension
+     :   Asm_ LeftParen StringLiteral+ RightParen
+     |   gccAttributeSpecifier
+     ;
+
+ gccAttributeSpecifier
+     :   Attribute_ LeftParen LeftParen gccAttributeList RightParen RightParen
+     ;
+
+ gccAttributeList
+     :   gccAttribute? (Comma gccAttribute?)*
+     ;
+
+ gccAttribute
+     :   ~(Comma | LeftParen | RightParen)
+         (LeftParen expression? RightParen)?
+     ;
```

## （ついでに）キャスト部分でのエラー報知も対応する
ところで、cores\esp32\WString.cppの構文解析において、パーサーは次のようなエラーを出力しています。

```
line 511:39 no viable alternative at input '0-*(unsignedchar*'
line 513:35 no viable alternative at input '*(unsignedchar*'
```
該当するソースコードは

```cpp
508: int String::compareTo(const String &s) const {
509:    if(!buffer() || !s.buffer()) {
510:        if(s.buffer() && s.len() > 0)
511:            return 0 - *(unsigned char *) s.buffer();
512:        if(buffer() && len() > 0)
513:            return *(unsigned char *) buffer();
514:        return 0;
515:    }
516:    return strcmp(buffer(), s.buffer());
517:}
```

となっていて、どうやらポインタ型のキャストが認識できていないようです。
これはCPP14Parser.g4にある不備で、GCC拡張とは関係ない感はあるものの、修正量は多くないのでここで修正します。
修正内容は次のとおりです。

```diff
unaryExpression:
	postfixExpression
-	| (PlusPlus | MinusMinus | unaryOperator | Sizeof) unaryExpression
+	| (PlusPlus | MinusMinus | unaryOperator | Sizeof) castExpression
	| Sizeof (
		LeftParen theTypeId RightParen
		| Ellipsis LeftParen Identifier RightParen
		| AndAnd Identifier
	)
	| Alignof LeftParen theTypeId RightParen
	| noExceptExpression
	| newExpression
	| deleteExpression;

castExpression:
	unaryExpression
-	| LeftParen theTypeId RightParen castExpression
+	| Extention_? LeftParen theTypeId RightParen castExpression
;

```

## 約94%に上昇
[前々回の記事](https://qiita.com/HiroInTFLab/items/8471071fd998b8a62c02)と同様の手順で、パーサーを生成し同じファイルに対して構文解析をかけた結果、パス率は約94%となりました。
前回は84%だったので10ポイントほどの改善となりました。

# 残るはプリプロセッサ
ここまでの過程で、GCC拡張の必要最低限の対応はできました。
```C++14 Support in GCC```にはまだ到達していないですが、だいぶ近づいたと思っています。
残るは、プリプロセッサによる置き換えがなされていないがために生じているエラーです。
次回はプリプロセッサを検討します。