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

第022回_字句解析の共通クラス1_ConditionLexer

»
前回まででTokenManagerの整理が完了したので今回から条件付字句解析器を検討します。
今回は、字句解析器の中のクラスが共通で利用する トークンクラス、CoditionLexerクラス、TermTokenizerクラスを用意します。

トークンクラス

生成するトークンに準正常系などを加えたトークンを用意します。+部分は継承関係を示します。
クラス名  説明
――――――――――――――――――――――――――――――――――――――――
Token  全てのトークンの親クラス(文字列とログ出力のためにトークン位置を保持します)
+DocumentToken  ファイルからRistrictCharを除いたクラス
+NonDocumentToken  字句解析器を通して生成するクラス
 +CharDataToken  CHARDATA_WORDを示すクラス
 +CDataToken  CDATA_WORDを示すクラス
 +IgnoreWordToken  IGNORE_WORDを示すクラス
 +PIWordToken  PI_WORDを示すクラス
 +NormalTokenA1Token  NORMAL_TOKEN_A1を示すクラス
 +NormalTokenA2Token  NORMAL_TOKEN_A2を示すクラス
 +NormalTokenB1Token  NORMAL_TOKEN_B1を示すクラス
 +NormalTokenB2Token  NORMAL_TOKEN_B2を示すクラス
 +CommentToken  COMMENT_TOKENを示すクラス
 +SymbolToken  SYMBOLを示すクラス
 +WhiteSpaceToken  WHITE_SPACEを示すクラス
 +WordToken  WORDを示すクラス
 +LiteralToken  LITERALを示すクラス
 +EofToken  ファイルの終端(EOF)を示すクラス
 +UnknownToken  字句解析器で取りえない文字列を扱うためのクラス
 +BadCommentToken  COMMENT_TOKENで正しくコメントの終りがない場合のクラス
実装は割愛します。

条件付字句解析器に共通の内容-ConditionLexer

条件付字句解析器に共通の内容について説明します。

■ConditionLexerの数
ConditionLexerは抽象クラスなのでインスタンスの数については定義しません。

■TokenManagerがStackLexerを通じて呼び出すメソッド
条件付字句解析器は、StackLexer上でStackとして管理します。また、StackLexerはConditionLexer#nextToken を呼び出すこととします。つまり、ConditionLexerは抽象メソッドnextTokenを実装します。

■解析データに対する解析深度は深度1とする
字句解析器が複雑化する1つの要因は、大量のif文やswitch文によって処理が分岐しすぎる点(複雑になりすぎる点)です。対策として、条件付字句解析器(CondtionLexerクラス)が担当する部分を
 1文字目を解析してトークンが確定しない場合、
 トークン作成器(TermTokenizerクラス)に処理を委譲する
と決めます。これを解析深度1とこのコラムでは呼びます。この設計思想は、
 多くのトークンが最初の1文字でどのトークンになるかが決まること

 最初の1文字で複数のトークン候補があったとしても
 ある程度のグループ化ができること
を勘案すると複雑化を抑えることができ概ね妥当と言えます。

■TokenBuilderフィールド
字句解析中の未完成なトークンはCondtionLexerとTermTokenizerの間を行き来したり、再帰的にクラス内のメソッドを呼び出すこともあるため、トークンを作る機構をTokenBuilderフィールドとして用意します。
ただし、字句解析中のトークンは解析している区間で唯一つであるため、シングルトンの解析中のデータParsingDataにて管理し、ConditionLexer側で参照を保持します。

■オブザーバーパターン
解析中のデータの文脈が切り替わるとき、それまで作成していた(作成中の)TokenBuilderは破棄します。それに合わせて、ConditionLexer側が参照しているTokenBuilderフィールドも参照を更新せねばなりません。
よって、オブザーバーパターンを使って参照の更新が行えるようにします。

■立ち位置
 立ち位置を図示すると次のようになります。
22クラス構成図_1.jpg
22-1.ソフトウェア構成図

 オブザーバーパターンによるTokenBuilderの参照を更新する流れは次のようになります。
22クラス構成図_2.jpg
22-2.TokenBuilderの参照を更新する流れ

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

public abstract class ConditionLexer
implements Observer
{
    private static ParsingDataStack m_parsingData
                   = ParsingDataStack.getInstance();

    protected TokenBuilder m_tokenBuilder;

    public ConditionLexer()
    {
        m_parsingData.addObserver(this);
        m_tokenBuilder = m_parsingData.getTokenBuilder();
    }

    public abstract Token nextToken(StringBuilder str);
    
    //オブザーバパターン
    public void update()
    {
        m_tokenBuilder = m_parsingData.getTokenBuilder();
    }
}

ParsingDataStackの修正

関連してParsingDataStackを以下のように修正します。

public class ParsingDataStack
{
    private ArrayList<Observer> m_observers;

    private ParsingDataStack()
    {
        ・・・
        //追加
        m_observers = new ArrayList<Observer>();
    }

    public void newContext(String parameterName, StringBuilder newBuffer)
    {
        ・・・
        //追加
        this.notifyObserver();
    }

    public void newContext(DocumentToken t)
    {
        ・・・
        //追加
        this.notifyObserver();
    }
    
    //1つ前のコンテキストに戻る
    public void goBackPreviousContext()
    {
        ・・・
        //追加
        //m_tokenBuilderの取り直し
        this.notifyObserver();
    }
    
    ・・・・
    
    //追加
    public StringBuilder getStringBuilder()
    {
        BaseParsingData pd = m_data.peek();
        return pd.getStringBuilder();
    }
    
    //追加
    //オブザーバパターン
    public void addObserver(Observer ob)
    {
        m_observers.add(ob);
    }
    
    //追加
    private void notifyObserver()
    {
        for (Observer ob :m_observers)
        {
            ob.update();
        }
    }
}

ParsingDataの修正

同じくParsingDataを以下のように修正します。

public class ParsingData
{
    ・・・
    
    //追加
    private   TokenBuilder         m_tokenBuilder;
    
    public BaseParsingData(String filePath, StringBuilder newBuffer)
    {
        ・・・
        //追加
        m_tokenBuilder    = new TokenBuilder(filePath);
    }

    //追加
    public TokenBuilder getTokenBuilder()
    {
        return m_tokenBuilder; 
    }

    ・・・・
}

長くなったので、TokenBuilderとTermTokenizerの解説は次回にします。
Comment(0)

コメント

コメントを投稿する