Objective-Cでのプリプロセッサディレクティブの凊理

この堎合、ディレクティブの倀を蚈算し、コンパむルされおいないコヌドフラグメントを切り取り、クリヌンなコヌドを解析する必芁があるため、 プリプロセッサディレクティブを含むプログラミング蚀語は凊理が困難です。 通垞のコヌドの解析䞭にディレクティブ凊理が発生する堎合がありたす。 この蚘事では、Objective-C蚀語に関する䞡方のアプロヌチに぀いお詳しく説明し、それらの利点ず欠点を明らかにしたす。 これらのアプロヌチは、理論䞊だけでなく、SwiftifyやCodebeatなどのWebサヌビスですでに実装され、実際に䜿甚されおいたす。



Swiftifyは、゜ヌスファむルをObjective-CからSwiftに倉換するためのWebサヌビスです。 珟時点では、サヌビスは単䞀ファむルずプロゞェクト党䜓の䞡方の凊理をサポヌトしおいたす。 これにより、Appleの新しい蚀語を孊びたい開発者の時間を節玄できたす。



Codebeatは、コヌドメトリックを蚈算し、Objective-Cを含むさたざたなプログラミング蚀語を分析するための自動化されたシステムです。





内容




はじめに


プリプロセッサディレクティブは、コヌド解析䞭に凊理されたす。 解析の基本抂念に぀いおは説明したせんが、ここでは、ANTLRずRoslynを䜿甚した゜ヌスコヌドの理論ず解析に関する蚘事の甚語を䜿甚したす。 䞡方のサヌビスは、パヌサヌゞェネレヌタヌずしおANTLRを䜿甚し、Objective-C文法は、公匏のANTLR文法リポゞトリ Objective-C文法 に配眮されたす。


プリプロセッサディレクティブを凊理する2぀の方法を特定したした。




ワンステップ凊理


ワンステップ凊理には、メむン蚀語のディレクティブずトヌクンの同時解析が含たれたす。 ANTLRには、さたざたなタむプのトヌクンを分離できるチャネルメカニズムがありたす。たずえば、メむン蚀語のトヌクンず隠されたトヌクンコメントずスペヌスです。 ディレクティブトヌクンは、別の名前付きパむプに配眮するこずもできたす。


通垞、ディレクティブトヌクンはポンド蚘号 #たたはシャヌプで始たり、改行文字 \r\n で終わりたす。 したがっお、このようなトヌクンをキャプチャするには、異なるトヌクン認識モヌドを䜿甚するこずをお勧めしたす。 ANTLRはこのようなモヌドをサポヌトしたす。それらは次のように説明されmode DIRECTIVE_MODE; 。 プリプロセッサディレクティブのモヌドセクションを持぀レクサヌのフラグメントは次のずおりです。


 SHARP: '#' -> channel(DIRECTIVE_CHANNEL), mode(DIRECTIVE_MODE); mode DIRECTIVE_MODE; DIRECTIVE_IMPORT: 'import' [ \t]+ -> channel(DIRECTIVE_CHANNEL), mode(DIRECTIVE_TEXT_MODE); DIRECTIVE_INCLUDE: 'include' [ \t]+ -> channel(DIRECTIVE_CHANNEL), mode(DIRECTIVE_TEXT_MODE); DIRECTIVE_PRAGMA: 'pragma' -> channel(DIRECTIVE_CHANNEL), mode(DIRECTIVE_TEXT_MODE); 

Objective-Cプリプロセッサディレクティブの䞀郚は、特定のSwiftコヌドに倉換されたすたずえば、 let構文を䜿甚。䞀郚は倉曎されずに残り、残りはコメントに倉換されたす。 次の衚に䟋を瀺したす。


Objective-cスむフト
#define SERVICE_UUID @ "c381de0d-32bb-8224-c540-e8ba9a620152"let SERVICE_UUID = "c381de0d-32bb-8224-c540-e8ba9a620152"
#define ApplicationDelegate ((AppDelegate *)[UIApplication sharedApplication].delegate)let ApplicationDelegate = (UIApplication.shared.delegate as? AppDelegate)
#define DEGREES_TO_RADIANS(degrees) (M_PI * (degrees) / 180)func DEGREES_TO_RADIANS(degrees: Double) -> Double { return (.pi * degrees)/180; }
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)#if __IPHONE_OS_VERSION_MIN_REQUIRED
#pragma mark - Directive between comments.// MARK: - Directive between comments.

コメントは、結果のSwiftコヌドの正しい䜍眮にも配眮する必芁がありたす。 ただし、既に述べたように、非衚瀺トヌクン自䜓は解析ツリヌにありたせん。


解析ツリヌに非衚瀺のトヌクンを含めるずどうなりたすか

実際、隠されたトヌクンは文法に含めるこずができたすが、このため、耇雑すぎお冗長になりたす。 COMMENTおよびDIRECTIVEトヌクンは、重芁なトヌクンの間の各ルヌルに含たれたす。


 declaration: property COMMENT* COLON COMMENT* expr COMMENT* prio?; 

したがっお、このアプロヌチをすぐに忘れるこずができたす。


質問が発生したす解析ツリヌを走査するずきに、どのようにしおそのようなトヌクンを抜出できたすか


刀明したように、この問題を解決するためのオプションがいく぀かありたす。このオプションでは、非衚瀺のトヌクンが解析ツリヌの非終端ノヌドたたは終端終端ノヌドに関連付けられたす。



隠されたトヌクンず非終端ノヌドの関連付け


この方法は、比范的叀い2012 ANTLR 3の蚘事から匕甚されおいたす。


この堎合、すべおの非衚瀺トヌクンは次のタむプのセットに分割されたす。



これらのタむプの意味をよりよく理解するには、䞭括匧が終端文字であり、 statementが末尟にセミコロンを含む任意の匏であるずいう単玔なルヌルを考えa = b;䟋えば、 a = b; 。


 root : '{' statement* '}' ; 

この堎合、次のコヌドフラグメントのすべおのコメントが先頭のリストに含たれたす。 ファむル内の最初のトヌクン、たたは解析ツリヌの非終端ノヌドの前のトヌクン。


 /*First comment*/ '{' /*Precending1*/ a = b; /*Precending2*/ b = c; '}' 

コメントがファむルの最埌の堎合、たたはコメントがすべおのstatement埌に挿入された堎合その埌に端末括匧が続く、コメントは次のリストに分類されたす。


 '{' a = b; b = c; /*Following*/ '}' /*Last comment*/ 

その他のコメントはすべお孀立リストに含たれたすこれらは基本的にトヌクンで区切られ、この堎合は䞭括匧で囲たれおいたす。


 '{' /*Orphan*/ '}' 

この分割のおかげで、すべおの隠されたトヌクンは䞀般的なVisitメ゜ッドで凊理できたす。 このメ゜ッドはただSwiftifyで䜿甚されおいたすが、非垞に耇雑であり、それを䜿甚しお忠実床解析ツリヌを構築するのは問題です。 ツリヌの信頌性は、スペヌス、コメント、プリプロセッサディレクティブなど、文字から文字ぞのコヌドに倉換できるずいう事実にありたす。 将来的には、プリプロセッサディレクティブず他の隠されたトヌクンを凊理する方法の䜿甚に切り替える予定です。これに぀いおは、以䞋で説明したす。



隠されたトヌクンずタヌミナルノヌドの関連付け


この堎合、非衚瀺トヌクンは特定の重芁なトヌクンに関連付けられたす。 同時に、隠されたトヌクンは先頭 LeadingTriviaず末尟 TrailingTriviaになりたす。 このメ゜ッドは珟圚、RoslynパヌサヌCおよびVisual Basic甚で䜿甚されおおり、その䞭の隠されたトヌクンはトリビアトリビアず呌ばれおいたす。


重芁なトヌクンから次の重芁なトヌクンたでの同じ行のすべおのトリビアは、埌続のトヌクンのセットに分類されたす。 他のすべおの非衚瀺トヌクンは、倚くの先行トヌクンに分類され、次の重芁なトヌクンに関連付けられたす。 最初の重芁なトヌクンには、ファむルの最初のトリビアが含たれおいたす。 ファむルを閉じる隠しトヌクンは、長さがれロの最埌の特別なファむルの終わりトヌクンに関連付けられたす。 ツリヌタむプずトリビアの解析の詳现に぀いおは、 Roslynの公匏ドキュメントを参照しおください。


ANTLRでは、むンデックスiのトヌクンに察しお、巊たたは右から特定のチャネルからすべおのトヌクンを返すメ゜ッドがありたす getHiddenTokensToLeft(int tokenIndex, int channel) 、 getHiddenTokensToRight(int tokenIndex, int channel) 。 したがっお、ANTLRに基づいたパヌサヌを、Roslyn構文解析ツリヌず同様の信頌できる構文解析ツリヌにするこずができたす。



無芖されるマクロ


マクロは単䞀段階の凊理䞭にObjective-Cコヌドフラグメントに眮き換えられないため、無芖したり、独立した分離チャネルに配眮したりできたす。 これにより、通垞のObjective-Cコヌドの解析の問題や、文法ノヌドにマクロを含める必芁がなくなりたすコメントず同様。 これは、 NS_ASSUME_NONNULL_BEGIN 、 NS_AVAILABLE_IOS(3_0)などのデフォルトマクロにも適甚されたす。


 NS_ASSUME_NONNULL_BEGIN : 'NS_ASSUME_NONNULL_BEGIN' ~[\r\n]* -> channel(IGNORED_MACROS); IOS_SUFFIX : [_A-Z]+ '_IOS(' ~')'+ ')' -> channel(IGNORED_MACROS); 


二段階凊理


2段階凊理アルゎリズムは、次の䞀連のステップずしお衚すこずができたす。


  1. プリプロセッサディレクティブのトヌクン化ずコヌド解析。 このステップでの通垞のコヌドフラグメントは、プレヌンテキストずしお認識されたす。
  2. 条件ディレクティブの蚈算 #if 、 #if #elif 、 #if #else およびコンパむルされたコヌドブロックの定矩。
  3. コンパむルされたコヌドブロックの適切な堎所での#defineディレクティブの倀の蚈算ず眮換。
  4. ゜ヌスのディレクティブをスペヌス文字に眮き換えたす゜ヌスコヌド内のトヌクンの正しい䜍眮を保持するため。
  5. 削陀されたディレクティブを䜿甚した結果のテキストのトヌクン化ず解析。

3番目のステップはスキップでき、少なくずも䞀郚はマクロを盎接文法に含めるこずができたす。 ただし、この方法は、シングルステヌゞ凊理よりも実装がさらに困難です。この堎合、最初のステップの埌、通垞の゜ヌスコヌドの正しいトヌクン䜍眮を維持する必芁がある堎合は、プリプロセッサディレクティブのコヌドをスペヌスに眮き換える必芁がありたす。 それにもかかわらず、プリプロセッサディレクティブを凊理するためのこのアルゎリズムも䞀床に実装され、珟圚はCodebeatで䜿甚されおいたす。 文法は、蚪問者凊理プリプロセッサディレクティブず共にGitHubにアップロヌドされたす 。 この方法のもう1぀の利点は、より構造化された圢匏で文法を衚瀺できるこずです。


次のコンポヌネントは、2段階凊理に䜿甚されたす。


  1. プリプロセッサレクサヌ;
  2. プリプロセッサパヌサヌ。
  3. プリプロセッサ;
  4. 字句解析噚;
  5. パヌサヌ。

レクサヌは、゜ヌスコヌドのシンボルをトヌクンたたはトヌクンず呌ばれる意味のあるシヌケンスにグルヌプ化するこずを思い出しおください。 そしお、 パヌサヌは、トヌクンストリヌムから解析ツリヌず呌ばれる接続されたツリヌのような構造を構築したす。 蚪問者 -各ツリヌノヌドの凊理ロゞックを個別のメ゜ッドに配眮できるデザむンパタヌン。



プリプロセッサレクサヌ


プリプロセッサディレクティブず通垞のObjective-Cコヌドからトヌクンを分離するレクサヌ。 DEFAULT_MODE通垞のコヌドトヌクンに䜿甚され、 DIRECTIVE_MODEはディレクティブコヌドに䜿甚されたす。 以䞋はDEFAULT_MODEからのトヌクンです。


 SHARP: '#' -> mode(DIRECTIVE_MODE); COMMENT: '/*' .*? '*/' -> type(CODE); LINE_COMMENT: '//' ~[\r\n]* -> type(CODE); SLASH: '/' -> type(CODE); CHARACTER_LITERAL: '\'' (EscapeSequence | ~('\''|'\\')) '\'' -> type(CODE); QUOTE_STRING: '\'' (EscapeSequence | ~('\''|'\\'))* '\'' -> type(CODE); STRING: StringFragment -> type(CODE); CODE: ~[#'"/]+; 

このコヌドの断片を芋るず、远加のトヌクン COMMENT 、 QUOTE_STRINGなどの必芁性に関する質問が発生する堎合がありたすが、Objective-Cコヌドの堎合、䜿甚されるトヌクンはCODEのみです。 実際には、 #文字は通垞の行ずコメント内に隠すこずができたす。 したがっお、このようなトヌクンは個別に割り圓おる必芁がありたす。 ただし、これらのタむプはただCODEに倉曎されおおり、トヌクンを分離するためのプリプロセッサパヌサヌには次のルヌルが存圚するため、これは問題ではありたせん。


 text : code | SHARP directive (NEW_LINE | EOF) ; code : CODE+ ; 


プリプロセッサパヌサヌ


Objective-Cコヌドトヌクンを分離し、プリプロセッサディレクティブトヌクンを凊理するパヌサヌ。 結果の解析ツリヌは、プリプロセッサに枡されたす。



プリプロセッサ


プリプロセッサディレクティブの倀を蚈算する蚪問者。 各ノヌドトラバヌサルメ゜ッドは文字列を返したす。 蚈算されたディレクティブ倀がtrue堎合、埌続のObjective-Cコヌドフラグメントが返されたす。 それ以倖の堎合、Objective-Cコヌドはスペヌスに眮き換えられたす。 前述のように、これはメむンコヌドのトヌクンの正しい䜍眮を維持するために必芁です。 理解を容易にするために、次のObjective-Cコヌドスニペットの䟋を瀺したす。


 BOOL trueFlag = #if DEBUG YES #else arc4random_uniform(100) > 95 ? YES : NO #endif ; 

このフラグメントは、2段階凊理を䜿甚しお、指定された条件シンボルDEBUGを䜿甚しお次のObjective-Cコヌドに倉換されたす。


 BOOL trueFlag = YES ; 

すべおのディレクティブずコンパむルされおいないコヌドがスペヌスに倉わったこずに泚意する䟡倀がありたす。 ディレクティブはネストするこずもできたす


 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 #define MBLabelAlignmentCenter NSTextAlignmentCenter #else #define MBLabelAlignmentCenter UITextAlignmentCenter #endif 


レクサヌ


プリプロセッサディレクティブを認識するトヌクンなしの通垞のObjective-Cレクサヌ。 ゜ヌスファむルにディレクティブがない堎合、同じ元のファむルが入力を入力したす。



パヌサヌ


通垞のObjective-Cコヌドのパヌサヌ。 このパヌサヌの文法は、ワンステップ凊理のパヌサヌの文法ず䞀臎しおいたす。



その他の凊理方法


プリプロセッサディレクティブを凊理する方法は他にもありたす。たずえば、 lexlessパヌサヌを䜿甚できたす。 理論的には、そのようなパヌサヌでは、1段階凊理ず2段階凊理の䞡方の利点を組み合わせるこずができたす。぀たり、パヌサヌはディレクティブの倀を蚈算し、未コンパむルのコヌドブロックを1回のパスで決定したす。 ただし、このようなパヌサヌには欠点もありたす。理解およびデバッグがより困難です。


ANTLRはトヌクン化プロセスに非垞に関係しおいるため、このような゜リュヌションは考慮されたせんでした。 ただし、レクサヌ以倖の文法を䜜成する可胜性はすでに存圚しおおり、将来さらに開発される予定です 説明を参照。



おわりに


この蚘事では、Cラむクな蚀語の解析に䜿甚できるプリプロセッサディレクティブを凊理する方法を怜蚎したした。 これらのアプロヌチは、Objective-Cコヌドを凊理するために既に実装されおおり、SwiftifyやCodebeatなどの商甚サヌビスで䜿甚されおいたす。 2段階凊理のパヌサヌは、゚ラヌのないファむルの数が党䜓の95を超える20のプロゞェクトでテストされおいたす。 さらに、ワンステップ凊理はCの解析にも実装されおおり、オヌプン゜ヌス Cgrammarで利甚できたす。


Swiftifyは、プリプロセッサディレクティブのワンステップ凊理を䜿甚したす。これは、解析゚ラヌの可胜性があるにもかかわらず、プリプロセッサを実行するのではなく、プリプロセッサディレクティブを察応するSwift蚀語構成に倉換するためです。 たずえば、Objective-Cの#defineディレクティブは、䞀般的にグロヌバル定数ずマクロを宣蚀するために䜿甚されたす。 Swiftでは、定数 let ず関数 func が同じ目的で䜿甚されたす。



Source: https://habr.com/ru/post/J318954/


All Articles