読者です 読者をやめる 読者になる 読者になる

プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

EclipseCDTにC方言を追加するプラグインを作ろう その2

eclipse Eclipse CDT

 さて、前回BNFで拡張構文の規則を作りました。今回からは、その規則に対するJavaの処理を作っていきます。

構文解析の流れ

 Javaの処理を作る前に、CDTがどのような手順で構文を解析しているかについて、ザックリと解説します。

  1. IScannerインターフェースを実装した字句解析器(CPreprocessor)でテキストをITokenに分解
  2. 字句解析器が生成したITokenを、構文解析器で使えるように変換(今回の記事の主内容)
  3. 変換したITokenを構文解析器に渡す
  4. 構文解析器の規則に従って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に変換するぜよ。たぶん。