さて、前回BNFで拡張構文の規則を作りました。今回からは、その規則に対するJavaの処理を作っていきます。
構文解析の流れ
Javaの処理を作る前に、CDTがどのような手順で構文を解析しているかについて、ザックリと解説します。
- IScannerインターフェースを実装した字句解析器(CPreprocessor)でテキストをITokenに分解
- 字句解析器が生成したITokenを、構文解析器で使えるように変換(今回の記事の主内容)
- 変換したITokenを構文解析器に渡す
- 構文解析器の規則に従ってITokenからIASTNodeを作成する
IScannerの実装であるCPreprocessorは、名前からわかるようにプリプロセッサの処理もしてくれます。このプリプロセッサの処理があるためか、字句解析器を自作することはできないようです。
さて、CPreprocessorが作ってくれたITokenを構文解析したいのですが、実はこのままでは構文解析器は動きません。例えば、SDCCでは終端記号として「__sfr」などを定義しました。これは終端記号であって、終端文字列ではありません。記号には、IToken.getType()で得られる整数値を用います。同じ終端文字列であっても、CPreprocessorが作るITokenのtypeと、我々が作った構文解析器が受け付けるITokenのtypeが異なっているため、我々が実装した構文解析器は動きません。この差を吸収するためのコンバータが必要になります。
最終ステップで、構文解析器は定義されている規則にマッチすると、登録されているJavaの処理を起動してくれます。ここでマッチした規則や、ITokenの内容からIASTNodeを作成します。
だいたいこんな感じです。ここで作られたIASTNodeから関数名のリストを作ったり、色々しているみたいですが、その辺は詳しく知りません。また、IScannerに渡すテキストも、実は全部のテキストじゃなくて、ブロック単位で管理されていたりと、色々処理が入っています。
ITokenを変換するIDOMTokenMapの実装
先にも説明したように、CPreprocessorのIToken.getType()の値を、我々が扱える数値に変換する必要があります。この変換機能を提供するインターフェースがIDOMTokenMapです。IDOMTokenMapのインスタンスは、前回作ったBNFをコンパイルすることで生成されるParserクラスのコンストラクタに渡す必要があります。
我々が扱える数値は、grammarファイルをコンパイル(grammerディレクトリでantを実行)すると生成される~~Parsersymインターフェースに、「TK_終端記号名」という名前で定義されています。(参考:SDCCParsersym.java)ちなみに、この~~Parsersymインターフェースで文法エラーが発生してる場合は、grammarファイルが既にバグっています。
一方、CPreprocessorはITokenに定義されている定数「t~~~」を扱います。
Keywordの定義
特にやらなくても良いですが、IDOMTokenMapを実装する前に、拡張定義したKeywordを定義するenumを作成しておくと、見通しが良くなります。C99で定義されているものまで、定義する必要はありません。
package nodamushi.cdt.parser.sdcc; //Parsersymの定数をstatic import import static nodamushi.internal.cdt.parser.sdcc.SDCCParsersym.*; public enum SDCCKeyword{ __data(TK___data), __near(TK___near), __xdata(TK___xdata), ………略……… // わざわざ構文解析器で解析することはやめた追加キーワード __asm__(-1) ; //対応する数値を保持しておく private final int tokenKind; private SDCCKeyword(int tokenKind){ this.tokenKind = tokenKind; }
で、ITokenから対応するenumのtokenKindを、素早く検索する為にMapにしておきます。MapはMap<IToken,SDCCKeyword>やMap<String,SDCCKeyword>ではなく、Map<char[],int>で持つ方が良いみたいです。そのためのMapにCharArrayIntMapというのがあったので、これを使いました。UPCの実装ではCharArrayMap<V>が使われていました。
private static final int EMPTY_NUMBER = Integer.MIN_VALUE; private static final CharArrayIntMap tokenMap; static{ final SDCCKeyword[] values = values(); final int size = values.length; tokenMap = new CharArrayIntMap(size, EMPTY_NUMBER); sdccKeywords = new String[size]; for(int i=0;i<size;i++){ final SDCCKeyword v = values[i]; final String name = v.name(); if(v.tokenKind != -1) // 構文解析器と無関係なものは無視 tokenMap.put(name.toCharArray(), v.tokenKind); } } // OptionalIntを使ってるのはただの趣味 public static OptionalInt getTokenKind(char[] image){ if(image == null){ return OptionalInt.empty(); }else{ int v = tokenMap.get(image); return v == EMPTY_NUMBER? OptionalInt.empty():OptionalInt.of(v); } }
IDOMTokenMapの実装
実はIDOMTokenMapの実装のほとんどを、先のKeywordでやっちゃっいました。なので、基本的には以下のコードをSDCCやpackage名だけ直せば、そのままで問題ありません。私もUPCからほとんどコピっただけです。
我々がASTNode変換処理するときに使うITokenは、このIDOMTokenMapのmapKindを通じて得られたtypeに設定されたITokenになります。
package nodamushi.cdt.parser.sdcc; import org.eclipse.cdt.core.dom.lrparser.IDOMTokenMap; import org.eclipse.cdt.core.parser.IToken; import static org.eclipse.cdt.core.parser.IToken.*; import static nodamushi.internal.cdt.parser.sdcc.SDCCParsersym.*; public class DOMToSDCCTokenMap implements IDOMTokenMap{ @Override public int getEOFTokenKind(){ return TK_EOF_TOKEN; } @Override public int getEOCTokenKind(){ return TK_EndOfCompletion; } @Override public int mapKind(IToken token){ switch(token.getType()) { case tIDENTIFIER : return SDCCKeyword.getTokenKind(token.getCharImage()).orElse(TK_identifier); //以下はC99の変換(誰が作っても固定だと思う) case tINTEGER : return TK_integer; case tCOLON : return TK_Colon; case tSEMI : return TK_SemiColon; case tCOMMA : return TK_Comma; case tQUESTION : return TK_Question; case tLPAREN : return TK_LeftParen; case tRPAREN : return TK_RightParen; case tLBRACKET : return TK_LeftBracket; case tRBRACKET : return TK_RightBracket; case tLBRACE : return TK_LeftBrace; case tRBRACE : return TK_RightBrace; case tPLUSASSIGN : return TK_PlusAssign; case tINCR : return TK_PlusPlus; case tPLUS : return TK_Plus; case tMINUSASSIGN : return TK_MinusAssign; case tDECR : return TK_MinusMinus; case tARROW : return TK_Arrow; case tMINUS : return TK_Minus; case tSTARASSIGN : return TK_StarAssign; case tSTAR : return TK_Star; case tMODASSIGN : return TK_PercentAssign; case tMOD : return TK_Percent; case tXORASSIGN : return TK_CaretAssign; case tXOR : return TK_Caret; case tAMPERASSIGN : return TK_AndAssign; case tAND : return TK_AndAnd; case tAMPER : return TK_And; case tBITORASSIGN : return TK_OrAssign; case tOR : return TK_OrOr; case tBITOR : return TK_Or; case tBITCOMPLEMENT: return TK_Tilde; case tNOTEQUAL : return TK_NE; case tNOT : return TK_Bang; case tEQUAL : return TK_EQ; case tASSIGN : return TK_Assign; case tUNKNOWN_CHAR : return TK_Invalid; case tSHIFTL : return TK_LeftShift; case tLTEQUAL : return TK_LE; case tLT : return TK_LT; case tSHIFTRASSIGN : return TK_RightShiftAssign; case tSHIFTR : return TK_RightShift; case tGTEQUAL : return TK_GE; case tGT : return TK_GT; case tSHIFTLASSIGN : return TK_LeftShiftAssign; case tELLIPSIS : return TK_DotDotDot; case tDOT : return TK_Dot; case tDIVASSIGN : return TK_SlashAssign; case tDIV : return TK_Slash; case t_auto : return TK_auto; case t_break : return TK_break; case t_case : return TK_case; case t_char : return TK_char; case t_const : return TK_const; case t_continue : return TK_continue; case t_default : return TK_default; case t_do : return TK_do; case t_double : return TK_double; case t_else : return TK_else; case t_enum : return TK_enum; case t_extern : return TK_extern; case t_float : return TK_float; case t_for : return TK_for; case t_goto : return TK_goto; case t_if : return TK_if; case t_inline : return TK_inline; case t_int : return TK_int; case t_long : return TK_long; case t_register : return TK_register; case t_return : return TK_return; case t_short : return TK_short; case t_sizeof : return TK_sizeof; case t_static : return TK_static; case t_signed : return TK_signed; case t_struct : return TK_struct; case t_switch : return TK_switch; case t_typedef : return TK_typedef; case t_union : return TK_union; case t_unsigned : return TK_unsigned; case t_void : return TK_void; case t_volatile : return TK_volatile; case t_while : return TK_while; case tFLOATINGPT : return TK_floating; case tSTRING : return TK_stringlit; case tLSTRING : return TK_stringlit; case tUTF16STRING : return TK_stringlit; case tUTF32STRING : return TK_stringlit; case tCHAR : return TK_charconst; case tLCHAR : return TK_charconst; case tUTF16CHAR : return TK_charconst; case tUTF32CHAR : return TK_charconst; case t__Bool : return TK__Bool; case t__Complex : return TK__Complex; case t__Imaginary : return TK__Imaginary; case t_restrict : return TK_restrict; case tCOMPLETION : return TK_Completion; case tEOC : return TK_EndOfCompletion; case tEND_OF_INPUT : return TK_EOF_TOKEN; default: assert false : "token not recognized : " + token.getType(); return TK_Invalid; } } }
じゃ、次回ついにIASTNodeに変換するぜよ。たぶん。