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

第031回_条件付字句解析器7_WordTokenizer他

»
前回から引き続いてNormalSectionLexerで登場した
 WordTokenizer
 SymbolTokenizer
 SymbolTokenizer2
 LeftAngelTokenizer
について説明します。

WordTokenizerの実装


public class WordTokenizer extends MapSwitchTokenizer
{
    private class WordTokenMaker implements Functor
    {
        public WordTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            return new WordToken();
        }
    }
    
    public WordTokenizer()
    {
        //状態       1つ前の文字は確定済み
        //  記号             処理
        //  -----------------------------------------------------
        //  #x002D-#x002E    今の文字を確定して次へ
        //  #x0030-#x003A    今の文字を確定して次へ
        //  #x0041-#x005A    今の文字を確定して次へ
        //  #x005F           今の文字を確定して次へ
        //  #x0061-#x007A    今の文字を確定して次へ
        //  #x00B7           今の文字を確定して次へ
        //  #x00C0-#x00D6    今の文字を確定して次へ
        //  #x00D8-#x00F6    今の文字を確定して次へ
        //  #x00F8-#x02FF    今の文字を確定して次へ
        //  #x0300-#x036F    今の文字を確定して次へ
        //  #x0370-#x037D    今の文字を確定して次へ
        //  #x037F-#x01FF    今の文字を確定して次へ
        //  #x200C-#x200D    今の文字を確定して次へ
        //  #x203F-#x2040    今の文字を確定して次へ
        //  #x2070-#x218F    今の文字を確定して次へ
        //  #x2C00-#x2FEF    今の文字を確定して次へ
        //  #x3001-#xD7FF    今の文字を確定して次へ
        //  #xF900-#xFDCF    今の文字を確定して次へ
        //  #xFDF0-#xFFFD    今の文字を確定して次へ
        //  その他           WordTokenを作成する
        //  EOF              WordTokenを作成する
        //
        //  処理1:今の文字を確定して次へ
        //  処理2:WordTokenを作成する
        //
        
        Update_MovePos proc1 = new Update_MovePos();
        WordTokenMaker proc2 = new WordTokenMaker();
        
        m_map.put('\u002D', '\u002E', proc1);
        m_map.put('\u0030', '\u003A', proc1);
        m_map.put('\u0041', '\u005A', proc1);
        m_map.put('\u005F'          , proc1);
        m_map.put('\u0061', '\u007A', proc1);
        m_map.put('\u00B7'          , proc1);
        m_map.put('\u00C0', '\u00D6', proc1);
        m_map.put('\u00D8', '\u00F6', proc1);
        m_map.put('\u00F8', '\u02FF', proc1);
        m_map.put('\u0300', '\u036F', proc1);
        m_map.put('\u0370', '\u037D', proc1);
        m_map.put('\u037F', '\u1FFF', proc1);
        m_map.put('\u200C', '\u200D', proc1);
        m_map.put('\u203F', '\u2040', proc1);
        m_map.put('\u2070', '\u218F', proc1);
        m_map.put('\u2C00', '\u2FEF', proc1);
        m_map.put('\u3001', '\uD7FF', proc1);
        m_map.put('\uF900', '\uFDCF', proc1);
        m_map.put('\uFDF0', '\uFFFD', proc1);
        m_map.setOutOfRange(proc2);
        m_map.setEofFunctor(proc2);
    }
}

SymbolTokenizerの検討

NormalSectionLexerで
 SYMBOL ("/>")
を解析するためにSymbolTokenizerを用意します。また、
 SYMBOL ("?")、SYMBOL ("?>")
 SYMBOL ("]")、SYMBOL ("]]>")
を解析するためにSymbolTokenizer2を用意します。

これまでに、SymbolTokenを生成するクラスとして
 JustCreateSymbolTokenizer
 SymbolListTokenizer
 SymbolListTokenizer2
を用意してきましたのでそれぞれの機能について整理すると

クラス名機能説明
JustCreateSymbolTokenizer既に読み込んだ文字列をSymbolTokenにする。
SymbolTokenizer既に読み込んだ文字列に続いて、引数で指定した文字列が一致する場合、SymbolTokenにする。 引数で指定した文字列が一致しない場合、UnknownTokenにする。
SymbolTokenizer2既に読み込んだ文字列に続いて、引数で指定した文字列が一致する場合、SymbolTokenにする。 引数で指定した文字列が一致しない場合、既に読み込んだ部分だけをSymbolTokenにする。
SymbolListTokenizer既に読み込んだ文字列に続いて、引数Listで指定した文字列のいずれかに一致する場合、SymbolTokenにする。 引数Listで指定した文字列のどれにも一致しない場合、UnknownTokenにする。
SymbolListTokenizer2既に読み込んだ文字列に続いて、引数Listで指定した文字列のいずれかに一致する場合、SymbolTokenにする。 引数Listで指定した文字列のどれにも一致しない場合、既に読み込んだ部分だけをSymbolTokenにする。
となります。

SymbolTokenizerとSymbolTokenizer2の実装は、一致しない場合に
 SymbolTokenを作成する

 UnknownTokenを作成する
かの違いだけですから、共通の親クラスとして、AbstractSymbolTokenizerを用意します。

AbstractSymbolTokenizerの実装


public abstract class AbstractSymbolTokenizer extends TermTokenizer
{
    private final String m_target;

    public AbstractSymbolTokenizer(String target)
    {
        m_target = target;
    }

    @Override
    public Token tokenize(StringBuilder str, int pos)
    {
        Token retval = null;

        //比較用の文字数を取得する
        int endIndex = m_target.length();

        int len = str.length();

        if (pos+endIndex < len)
        {
            //対象分の文字を取得する
            //memo
            //  取得するのはendIndex-1まで
            String sub = str.substring(pos, pos+endIndex);

            //対象文字と一致する場合
            if (sub.equals(m_target))
            {
                m_tokenBuilder.update(sub);
                retval = new SymbolToken();
            }
            //対象文字と一致しない場合
            else
            {
                retval = makeTokenIfUnmatch();
            }
        }
        else
        {
            retval = makeTokenIfUnmatch();
        }

        return retval;
    }

    protected abstract Token makeTokenIfUnmatch();
}

SymbolTokenizerの実装


public class SymbolTokenizer extends AbstractSymbolTokenizer
{
    public SymbolTokenizer(String target)
    {
        super(target);
    }

    //マッチしない場合、UnknownTokenを作成する
    protected Token makeTokenIfUnmatch()
    {
        return new UnknownToken();
    }
}

SymbolTokenizer2の実装


public class SymbolTokenizer2 extends AbstractSymbolTokenizer
{
    public SymbolTokenizer2(String target)
    {
        super(target);
    }

    //マッチしない場合もSymbolを作成する
    protected Token makeTokenIfUnmatch()
    {
        return new SymbolToken();
    }
}

LeftAngelTokenizerの検討と実装

検討
LeftAngleTokenizerは、SymbolTokenizer2で上手く解析できないので別クラスとして用意します。

"<"から始まるSYMBOLの整理
 '<'
 '</'
 '<?'
 '<?xml'
 '<!['
 '<![CDATA['
 '<!ATTLIST'
 '<!DOCTYPE'
 '<!ENTITY'
 '<!ELEMENT'
 '<!NOTATION'
 COMMENT (<!--)
の12個があります。

SymbolTokenizer2で実装できない理由
<?xmlと<?の仕様について考える必要があります。

 <?はPI命令の最初のトークン
 <?xmlはXML文書宣言の最初のトークン
で、PI命令の対象名としてxmlを指定することはできません。
ただし、PI命令の対象名の構文は、
 PITarget ::= Name - ( ( 'X' | 'x' ) ( 'M' | 'm' ) ( 'L' | 'l' ) )
ですから、xmlstylesheetなどのxmlから始まる文字列があってもかまいません。

つまり、<?xmlの時点では確定できず、
次の文字がWHITE_SPACEの場合:SYMBOL ('<?xml')
次の文字がWHITE_SPACEでない場合:SYMBOL ('<?') その後ろはWORDとして切り出す
となり、SymbolTokenizer2では実装できないことがわかります。
実装

public class LeftAngleTokenizer extends MultipleMapsSwitchTokenizer
{
    private class DtdSymbolTokenMaker implements Functor
    {
        private final SymbolListTokenizer2 m_dtdSymbolTokenizer;
        
        public DtdSymbolTokenMaker()
        {
            ArrayList<String> list = new ArrayList<String>();
            list.add("!ATTLIST");
            list.add("!DOCTYPE");
            list.add("!ENTITY");
            list.add("!ELEMENT");
            list.add("!NOTATION");
            m_dtdSymbolTokenizer = new SymbolListTokenizer2(list);
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            //1つ行き過ぎてるのでpos-1する
            return m_dtdSymbolTokenizer.tokenize(str, pos-1);
        }
    }
    
    private final JustCreateSymbolTokenizer m_symbolMaker;
    private final SymbolTokenizer2 m_leftBracket_Tokenizer;

    public LeftAngleTokenizer()
    {
        m_symbolMaker           = new JustCreateSymbolTokenizer();
        m_leftBracket_Tokenizer = new SymbolTokenizer2("CDATA[");
        
        makeMap_A(); //0
        //map B      //1
        Character[] clist1 = {'x','X'};
        makeMap_CharList_MovePosMoveMap(clist1,2);
        //map C      //2
        Character[] clist2 = {'m','M'};
        makeMap_CharList_MovePosMoveMap(clist2,3);
        //map D      //3
        Character[] clist3 = {'l','L'};
        makeMap_CharList_MovePosMoveMap(clist3,4);
        makeMap_E(); //4
        makeMap_F(); //5
        makeMap_G(); //6
    }
    
    //表B,C,Dはほぼ同じなので作成用のメソッドを用意する
    private void makeMap_CharList_MovePosMoveMap
                       (Character[] charList, int toMap)
    {
        //
        //表 B,C,D
        //  記号                              処理
        //  -----------------------------------------------------
        //  charList[0]    今の文字を確定せずにtoMapへ
        //  charList[1]    今の文字を確定せずにtoMapへ
        //  ・・・・・
        //  charList[N]    今の文字を確定せずにtoMapへ
        //  その他         SYMBOLを作成する
        //  EOF            SYMBOLを作成する
        //
        //  処理1:今の文字を確定せずにtoMapへ
        //  処理2:SYMBOLを作成する
        //
        MovePos_MoveMap proc1 = new MovePos_MoveMap(toMap);
        //              proc2 = m_symbolMaker
        
        LexicalMap map = new LexicalMap();
        for(Character c : charList)
        {
            map.put(c , proc1);
        }

        map.setOutOfRange(m_symbolMaker);
        map.setEofFunctor(m_symbolMaker);
        
        m_mapList.add(map);
    }

    private void makeMap_A()
    {
        //
        //表 A    前の文字が"<"で確定している状態
        //  記号     処理
        //  -----------------------------------------------------
        //  "/"      今の文字を確定して SYMBOLを作成する
        //  "?"      今の文字を確定してBへ
        //  "!"      今の文字を確定せずにFへ
        //  その他   SYMBOLを作成する
        //  EOF      SYMBOLを作成する
        //
        //  処理1:今の文字を確定して SYMBOLを作成する
        //  処理2:今の文字を確定してBへ
        //  処理3:今の文字を確定せずにFへ
        //  処理4:SYMBOLを作成する
        //

        Update_MovePos         proc1 = new Update_MovePos(m_symbolMaker);
        Update_MovePos_MoveMap proc2 = new Update_MovePos_MoveMap(1);
        MovePos_MoveMap        proc3 = new MovePos_MoveMap(5);
        //                     proc4 = m_symbolMaker
        
        LexicalMap map = new LexicalMap();
        map.put('/', proc1);
        map.put('?', proc2);
        map.put('!', proc3);
        map.setOutOfRange(m_symbolMaker);
        map.setEofFunctor(m_symbolMaker);
        
        m_mapList.add(map);
    }

    private void makeMap_E()
    {
        //
        //表E    確定文字が<? で3つ前がx|X ,
        //         2つ前がm|M,1つ前がl|Lで未確定の状態
        //  記号    Unicode   処理
        //  -----------------------------------------------------
        //  "\n"              3~1つ前までを確定してSYMBOLを作成する
        //  "\r"              3~1つ前までを確定してSYMBOLを作成する
        //         '\u0009'   3~1つ前までを確定してSYMBOLを作成する
        //         '\u0020'   3~1つ前までを確定してSYMBOLを作成する
        //  その他            SYMBOLを作成する(<?を作って残りはPITargetへ)
        //  EOF               3~1つ前までを確定してSYMBOLを作成する
        //
        //  処理1:3~1つ前までを確定してSYMBOLを作成する
        //  処理2:SYMBOLを作成する(<?を作って残りはPITargetへ)
        //

        UpdateRange_MovePos proc1
                     = new UpdateRange_MovePos(m_symbolMaker, 3,1);
        //                  proc2 = m_symbolMaker
        
        LexicalMap map = new LexicalMap();
        map.put('\n'    , proc1);
        map.put('\r'    , proc1);
        map.put('\u0009', proc1);
        map.put('\u0020', proc1);
        map.setOutOfRange(m_symbolMaker);
        map.setEofFunctor(proc1);
        
        m_mapList.add(map);
    }

    private void makeMap_F()
    {
        //
        //表 F    <が確定して、1つ前が!で未確定の状態
        //  記号        処理
        //  -----------------------------------------------------
        //  "["         1つ前と今の文字を確定して、
        //               続きがCDATA[ならSymbolを作成する
        //              違うなら<![を作成する
        //  "-"         今の文字を確定せずにGへ
        //  その他      続きが、ATTLIST,DOCTYPE,ENTITY,
        //                ELEMENT,NOTATIONの場合は
        //                それらを確定させて、SYMBOLを作成する。
        //                違うなら<でSYMBOLを作成する
        //  EOF         SYMBOLを作成する
        //
        //  処理1:1つ前と今の文字を確定して、
        //        続きがCDATA[ならSYMBOLを作成する
        //        違うなら<![を作成する
        //  処理2:今の文字を確定せずにGへ
        //  処理3:続きが、ATTLIST,DOCTYPE,ENTITY,ELEMENT,
        //        NOTATIONの場合はそれらを確定させて、
        //        SYMBOLを作成する。違うなら<でSYMBOLを作成する
        //  処理4:SYMBOLを作成する
        //

        UpdateRange_MovePos proc1
            = new UpdateRange_MovePos(m_leftBracket_Tokenizer, 1,0);
        MovePos_MoveMap     proc2 = new MovePos_MoveMap(6);
        DtdSymbolTokenMaker proc3 = new DtdSymbolTokenMaker();
        //                  proc4 = m_symbolMaker
        
        LexicalMap map = new LexicalMap();
        map.put('['    , proc1);
        map.put('-'    , proc2);
        map.setOutOfRange(proc3);
        map.setEofFunctor(m_symbolMaker);
        
        m_mapList.add(map);
    }

    private void makeMap_G()
    {
        //表 G    <が確定して、2つ前が!、1つ前が- で未確定の状態
        //  記号        処理
        //  -----------------------------------------------------
        //  "-"         2つ前から今の文字を確定して、CommentTokenizerへ
        //  その他      SYMBOLを作成する
        //  EOF         SYMBOLを作成する
        //
        //  処理1:2つ前から今の文字を確定して、CommentTokenizerへ
        //  処理2:SYMBOLを作成する
        //
        CommentTokenizer commentTokenizer = new CommentTokenizer();
        UpdateRange_MovePos proc1
                   = new UpdateRange_MovePos(commentTokenizer, 2,0);
        //                  proc2 = m_symbolMaker
        
        LexicalMap map = new LexicalMap();
        map.put('-'    , proc1);
        map.setOutOfRange(m_symbolMaker);
        map.setEofFunctor(m_symbolMaker);
        
        m_mapList.add(map);
    }
}

CommentTokenizerの実装


public class CommentTokenizer extends MultipleMapsSwitchTokenizer
{
    private class CommentTokenMaker implements Functor
    {
        public CommentTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            m_tokenBuilder.update(m_char);
            pos++;
            return new CommentToken();
        }
    }
    
    private class BadcommentTokenMaker implements Functor
    {
        public BadcommentTokenMaker()
        {
        }
        
        @Override
        public Token tokenize(StringBuilder str, int pos)
        {
            return new BadCommentToken();
        }
    }
    
    private final Update_MovePos_MoveMap m_umpmm_A;
    private final Update_MovePos_MoveMap m_umpmm_B;
    private final Update_MovePos_MoveMap m_umpmm_C;
    private final BadcommentTokenMaker m_btm;
    private final CommentTokenMaker    m_ctm;
    private final Update_MovePos       m_um;
    
    public CommentTokenizer()
    {
        m_umpmm_A = new Update_MovePos_MoveMap(0);
        m_umpmm_B = new Update_MovePos_MoveMap(1);
        m_umpmm_C = new Update_MovePos_MoveMap(2);
        m_btm  = new BadcommentTokenMaker();
        m_ctm  = new CommentTokenMaker();
        m_um   = new Update_MovePos(m_btm);
        
        makeMap_A();
        makeMap_B();
        makeMap_C();
    }
    
    private void makeMap_A()
    {
        LexicalMap map = new LexicalMap();

        //表A
        //
        //記号   UniCode         処理
        //----------------------------------------------
        //Char   #x1-#x002C      1文字確定する。表Aへ
        //'-'    #x002D          1文字確定する。表Bへ
        //Char   #x002E-#xD7FF   1文字確定する。表Aへ
        //Char   #xE000-#xFFFD   1文字確定する。表Aへ
        //その他                 1文字確定する。BAD_COMMENT_TOKENを作成する
        //EOF                    BAD_COMMENT_TOKENを作成する
        
        map.put('\u0001', '\u002C', m_umpmm_A);
        map.put('\u002D',           m_umpmm_B);
        map.put('\u002E', '\uD7FF', m_umpmm_A);
        map.put('\uE000', '\uFFFD', m_umpmm_A);
        map.setOutOfRange(m_um);
        map.setEofFunctor(m_btm);
        
        m_mapList.add(map);
    }
    
    private void makeMap_B()
    {
        LexicalMap map = new LexicalMap();
        
        //表B
        //
        //記号   UniCode         処理
        //----------------------------------------------
        //Char   #x1-#x002C      1文字確定する。表Aへ
        //'-'    #x002D          1文字確定する。表Cへ
        //Char   #x002E-#xD7FF   1文字確定する。表Aへ
        //Char   #xE000-#xFFFD   1文字確定する。表Aへ
        //その他                 1文字確定する。BAD_COMMENT_TOKENを作成する
        //EOF                    BAD_COMMENT_TOKENを作成する
        map.put('\u0001', '\u002C', m_umpmm_A);
        map.put('\u002D',           m_umpmm_C);
        map.put('\u002E', '\uD7FF', m_umpmm_A);
        map.put('\uE000', '\uFFFD', m_umpmm_A);
        map.setOutOfRange(m_um);
        map.setEofFunctor(m_btm);
        
        m_mapList.add(map);
    }

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

        //表C
        //
        //記号    UniCode    処理
        //---------------------------------------------
        //'>'     #x003C     1文字確定する。COMMENT_TOKENを作成する
        //その他                 1文字確定する。BAD_COMMENT_TOKENを作成する
        //EOF                    BAD_COMMENT_TOKENを作成する
        map.put('>',      m_ctm);
        map.setOutOfRange(m_um);
        map.setEofFunctor(m_btm);
        
        m_mapList.add(map);
    }
}
Comment(0)

コメント

コメントを投稿する