本家「@IT」にはない内容をエンジニアライフで技術紹介するコラム。広く議論する場になることを目指します。

第025回_条件付字句解析器1_CharDataLexer_CharDataTokenizer

»

基礎クラスの準備ができたので、今回から字句解析器を検討します。
今回はCharDataについて検討します。
# 8個の字句解析器について1回ずつ実装を紹介します。
# 内容が薄く見えますが、前回までの基礎クラスをコツコツと積み上げた
# 結果なので22回、23回、24回を振り返りながら読んでいただけたらと思います。

CharDataを字句解析するCharDataLexerの検討

CharDataの構文を確認した上で検討に入ります。

CharDataの構文
 [14] CharData ::= [ˆ<&]* − ([ˆ<&]* ']]>' [ˆ<&]*)
とあるので
 CHARDATA_WORD は <と&を除く文字列 かつ "]]>" を含まない文字列
また、[ˆ<&]*とあるので
 文字数は0文字の場合もあり得ます。

1文字目による分岐
前にも述べましたが、この自作XMLパーサの役割分担は
 1文字目による分岐をLexer
 2文字目以降の処理をTokenizer
が担当します。

よってCharDataLexerの考えるべきはバッファから取得した最初の1文字です。
 最初の1文字によって
 トークンをすぐに作成する

 必要なTokenizerへ移動する
に分岐します。

処理をまとめると次のように表にできます。

次の文字処理
―――――――――――――――――――――――――――――――――――――――
&空のCharDataTokenを作成する
<空のCharDataTokenを作成する
]この文字"]"を確定せず、次の文字に移動して、CharDataTokenizer2へ移動する
その他初期化して、この文字を確定する。さらにCharDataTokenizerへ移動する
EOF空のCharDataTokenを作成する

少し小難しいのは、1文字目が"]"の場合です。2文字目が"]"かつ3文字目が">"の場合はこの1文字目の"]"はCharDataTokenには含まず、
空のCharDataTokenになります。そうでない場合、この1文字目の"]"はCharDataTokenの内容に含みます。
よって、仕様は「この文字"]"を確定せず、次の文字に移動して、CharDataTokenizer2へ移動する」となります。

CharDataLexerの実装

CharDataLexerの実装は、MapSwitchLexerを利用して次のようにできます。


public class CharDataLexer extends MapSwitchLexer
{
    //空用のクラス
    private class CharDataTokenMaker implements Functor
    {
        public CharDataTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            m_tokenBuilder.initStart();
            return new CharDataToken();
        }
    }
    
    
    private static final CharDataLexer m_instance = new CharDataLexer();

    private CharDataLexer()
    {
        super();
        
        final CharDataTokenMaker cdtm = new CharDataTokenMaker();
        
        //次の文字    処理
        //-----------------------------------------------------------------------------------------
        //"&"         空のCharDataTokenを作成する
        //"<"         空のCharDataTokenを作成する
        //"]"         今の文字"]"を確定せず、次の文字に移動して、
        //            CharDataTokenizer2へ移動する
        //その他      初期化して、今の文字を確定する。
        //            さらにCharDataTokenizerへ移動する
        //EOF         空のCharDataTokenを作成する
        
        MovePos      charDataTokenizer2
                      = new MovePos(new CharDataTokenizer2());
        Init_MovePos charDataTokenizer
                      = new Init_MovePos(new CharDataTokenizer());
        
        m_map.put('&', cdtm);
        m_map.put('<', cdtm);
        m_map.put(']', charDataTokenizer2);
        m_map.setOutOfRange(charDataTokenizer);
        m_map.setEofFunctor(cdtm);
    }

    public static CharDataLexer getInstance()
    {
        return m_instance;
    }
}

CharDataTokenizerの検討

CharDataLexerで1文字目が"その他"の場合に2文字目以降に対して実施するトークナイザがCharDataTokenizerです。

表Aの処理
CharDataの範囲で1文字取得した場合、どのように処理するかを検討します。

まず、CHARDATA_WORD は "<"と"&"と"]]>"を含まない文字列ですから、
 バッファに文字がない場合
 1文字目に"<"と"&"と"]"を取得した場合
 1文字目が"<"と"&"と"]"でない文字を取得した(その他の)場合
に分岐します。

それぞれの処理をまとめた表Aは
表A
次の文字処理
―――――――――――――――――――――――――――――――――――――――
&この文字を確定せずにCharDataTokenを作成する
<この文字を確定せずにCharDataTokenを作成する
]確定せずに次の文字のために表Bへ移動する
その他今の文字を確定する(次の文字も表Aで処理する)
EOF確定せずにCharDataTokenを作成する
となります。

表Bの処理
表Aにあった表Bについても同じように考えると、

表B (1つ前が]で未確定)
次の文字処理
―――――――――――――――――――――――――――――――――――――――
&1文字前を確定して、この文字を確定せずCharDataTokenを作成する
<1文字前を確定して、この文字を確定せずCharDataTokenを作成する
]確定せずに次の文字のために表Cへ移動する
その他1文字前、今の文字を確定して表Aへ移動する
EOF1文字前を確定して、CharDataTokenを作成する
となります。

表Cの処理
表Bにあった表Cについても同じように考えると、

表C (2つ前が]で未確定、1つ前が]で未確定)
次の文字処理
―――――――――――――――――――――――――――――――――――――――
&2文字前、1文字前を確定して、CharDataTokenを作成する
<2文字前、1文字前を確定して、CharDataTokenを作成する
>確定せずにCharDataTokenを作成する
]2文字前を確定する(次の文字も表Cで処理する)
その他2文字前、1文字前、今の文字を確定して表Aへ移動する
EOF2文字前、1文字前を確定して、CharDataTokenを作成する
となります。

CharDataTokenizerの実装

先ほどの検討とMultipleMapsSwitchTokenizerを使ってCharDataTokenizerを実装します。


public class CharDataTokenizer extends MultipleMapsSwitchTokenizer
{
    private class CharDataTokenMaker implements Functor
    {
        public CharDataTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            return new CharDataToken();
        }
    }
    
    protected final CharDataTokenMaker m_ctm;
    
    public CharDataTokenizer()
    {
        m_ctm  = new CharDataTokenMaker();

        makeMap_A();
        makeMap_B();
        makeMap_C();
    }
    
    private void makeMap_A()
    {
        LexicalMap map = new LexicalMap();
        
        //表A
        //次の文字    処理
        //-----------------------------------------------------------------------------------------
        //"&"         この文字を確定せずにCharDataTokenを作成する
        //"<"         この文字を確定せずにCharDataTokenを作成する
        //"]"         確定せずに次の文字のために表Bへ移動する
        //その他      今の文字を確定する(次の文字も表Aで処理する)
        //EOF         確定せずにCharDataTokenを作成する

        //              proc1 = m_ctm
        MovePos_MoveMap proc2 = new MovePos_MoveMap(1);
        Update_MovePos  proc3 = new Update_MovePos(this);

        map.put('<', m_ctm);
        map.put('&', m_ctm);
        map.put(']', proc2);
        map.setOutOfRange(proc3);
        map.setEofFunctor(m_ctm);
        
        m_mapList.add(map);
    }
    
    private void makeMap_B()
    {
        LexicalMap map = new LexicalMap();
        
 
        //表B (1つ前が]で未確定)
        //次の文字    処理
        //-----------------------------------------------------
        //'&'         1文字前の"]"を確定して、
        //            この文字を確定せずCharDataTokenを作成する
        //'<'         1文字前の"]"を確定して、
        //            この文字を確定せずCharDataTokenを作成する
        //"]"         確定せずに次の文字のために表Cへ移動する
        //その他      1文字前、今の文字を確定して表Aへ移動する
        //EOF         1文字前を確定して、CharDataTokenを作成する
        //
        UpdateRange_MovePos  proc1
                             = new UpdateRange_MovePos(m_ctm,1,1);
        MovePos_MoveMap      proc2
                             = new MovePos_MoveMap(2);
        UpdateRange_MovePos_MoveMap proc3
                             = new UpdateRange_MovePos_MoveMap(0,1,0);
        
        map.put('&', proc1);
        map.put('<', proc1);
        map.put(']', proc2);
        map.setOutOfRange(proc3);
        map.setEofFunctor(proc1);
        
        m_mapList.add(map);
    }

    private void makeMap_C()
    {
        LexicalMap map = new LexicalMap();


        //表C (2つ前が]で未確定、1つ前が]で未確定)
        //次の文字    処理
        //-----------------------------------------------------
        //'&'         2文字前、1文字前を確定して、CharDataTokenを作成する
        //'<'         2文字前、1文字前を確定して、CharDataTokenを作成する
        //'>'         確定せずにCharDataTokenを作成する
        //"]"         2文字前を確定する(次の文字も表Cで処理する)
        //その他      2文字前、1文字前、今の文字を確定して表Aへ移動する
        //EOF         2文字前、1文字前を確定して、CharDataTokenを作成する

        UpdateRange_MovePos proc1 = new UpdateRange_MovePos(m_ctm,2,1);
        //                  proc2 = m_ctm
        UpdateRange_MovePos proc3 = new UpdateRange_MovePos(this,2,2);
        UpdateRange_MovePos_MoveMap
                         proc4 = new UpdateRange_MovePos_MoveMap(0,2,0);

        map.put('&', proc1);
        map.put('<', proc1);
        map.put('>', m_ctm);
        map.put(']', proc3);
        map.setOutOfRange(proc4);
        map.setEofFunctor(proc1);
        
        m_mapList.add(map);
    }
}

CharDataTokenizer2の検討

CharDataLexerで1文字目が"]"の場合に、未初期化で2文字目以降に対して実施するトークナイザがCharDataTokenizer2です。

表Dの処理
表D(未初期化、1つ前が]で未確定)
次の文字処理
―――――――――――――――――――――――――――――――――――――――
&初期化、1文字前を確定してCharDataTokenを作成する
<初期化、1文字前を確定してCharDataTokenを作成する
]確定せずに表Eへ移動する
その他初期化、1文字前、今の文字を確定して表Aへ移動する
EOF初期化、1文字前を確定してCharDataTokenを作成する

表Eの処理
表E(未初期化、2つ前が]、1つ前が]で未確定)
次の文字処理
―――――――――――――――――――――――――――――――――――――――
&初期化、2文字前、1文字前を確定して、CharDataTokenを作成する
<初期化、2文字前、1文字前を確定して、CharDataTokenを作成する
>確定せずにCharDataTokenを作成する
]2文字前を確定して表Cへ移動する
その他2文字前、1文字前、今の文字を確定して表Aへ移動する
EOF2文字前、1文字前を確定して、CharDataTokenを作成する

継承関係
CharDataTokenizer2は表A(表Aの内部では表Bにも移動する)、表Cに移動する可能性があるためCharDataTokenizerを継承します。
また初期の表はDです。

CharDataTokenizer2実装

 実装は次のようになります。

public class CharDataTokenizer2 extends CharDataTokenizer
{
    private class EmptyCharDataTokenMaker implements Functor
    {
        public EmptyCharDataTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            m_tokenBuilder.initStart();
            return new CharDataToken();
        }
    }
    
    private final EmptyCharDataTokenMaker  m_ectm;
    
    public CharDataTokenizer2()
    {
        super();
        
        //Dからスタートする
        m_startindex = 3;
        m_currentIndex = m_startindex;

        m_ectm  = new EmptyCharDataTokenMaker();
        
        makeMap_D();
        makeMap_E();
    }
    
    private void makeMap_D()
    {
        LexicalMap map = new LexicalMap();
        
        //表D(未初期化、1つ前が]で未確定)
        //次の文字    処理
        //-----------------------------------------------------------------------------------------
        //"&"         初期化、1文字前を確定してCharDataTokenを作成する
        //"<"         初期化、1文字前を確定してCharDataTokenを作成する
        //"]"         確定せずに表Eへ移動する
        //その他      初期化、1文字前、今の文字を確定して表Aへ移動する
        //EOF         初期化、1文字前を確定してCharDataTokenを作成する
        //
        InitUpdateRange_MovePos proc1
                         = new InitUpdateRange_MovePos(m_ctm,1,1);
        MovePos_MoveMap  proc2
                         = new MovePos_MoveMap(4);
        InitUpdateRange_MovePos_MoveMap
                    proc3 = new InitUpdateRange_MovePos_MoveMap(0,1,0);
        
        map.put('<', proc1);
        map.put('&', proc1);
        map.put(']', proc2);
        map.setOutOfRange(proc3);
        map.setEofFunctor(proc1);

        m_mapList.add(map);
    }
    
    private void makeMap_E()
    {
        LexicalMap map = new LexicalMap();
        
        //表E(未初期化、2つ前が]、1つ前が]で未確定)
        //次の文字    処理
        //-----------------------------------------------------------------------------------------
        //'&'         初期化、2文字前、1文字前を確定して
        //              CharDataTokenを作成する
        //'<'         初期化、2文字前、1文字前を確定して
        //              CharDataTokenを作成する
        //'>'         確定せずにCharDataTokenを作成する
        //"]"         2文字前を確定して表Cへ移動する
        //その他      2文字前、1文字前、今の文字を確定して
        //              表Aへ移動する
        //EOF         2文字前、1文字前を確定して
        //              CharDataTokenを作成する
        //
        InitUpdateRange_MovePos proc1
                           = new InitUpdateRange_MovePos(m_ctm,2,1);
        //                      proc2 = m_ectm
        InitUpdateRange_MovePos_MoveMap 
                     proc3 = new InitUpdateRange_MovePos_MoveMap(2,2,2);
        InitUpdateRange_MovePos_MoveMap
                     proc4 = new InitUpdateRange_MovePos_MoveMap(0,2,0);
        
        map.put('<', proc1);
        map.put('&', proc1);
        map.put('>', m_ectm);
        map.put(']', proc3);
        map.setOutOfRange(proc4);
        map.setEofFunctor(proc1);

        m_mapList.add(map);
    }
}
字句解析器とトークナイザの実装クラスは--if文を登場せずに--
仕様にある表をソースコードに変換するだけで実装可能になったと思います。
Comment(0)

コメント

コメントを投稿する