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

第033回_EBNFデータツリーの実装_Visitorの機能拡張1

»
字句解析器の実装がおおよそ完了しましたので構文解析器の実装を始めます。

SyntaxTreeの検討と実装

検討
今回、EBNFデータツリーの実装は第18回のコラムで説明したEbnfDocumentNodeの実装に準拠しますので説明は割愛します。本コラムではEBNFデータツリーを生成する場所として、SyntaxTreeクラスを実装します。

SyntaxTreeの役割
SyntaxTreeクラスは構文の先頭を管理するクラスとします。最上位構文が2つ以上あるので、それぞれのgetterメソッドを用意します。SyntaxTreeのコンストラクタが最上位の構文のEbnfXXXXXNode#getInstanceメソッドを呼び出すことでEBNFデータツリーを構築します。

XMLの構文の場合、
 document ::= prolog element Misc*
 extParsedEnt ::= TextDecl? content
の2つの構文があります。
ただし、EofTokenが最後に来ることを考えると、
 Root ::= document EOF
 extRoot ::= extParsedEnt EOF
が先頭の構文と考えるのが良いでしょう。
#
# SyntaxTreeクラスが管理する構文の先頭は上記の2つだけではありません。
# 実体の置換文字列や独立して別の構文に合致するかを判断する場合も
# それぞれの構文を呼び出すのに使うことになります。
#

実装

public class SyntaxTree
{
    private static SyntaxTree m_instance = new SyntaxTree();

    private EbnfRootNode    m_root;
    private EbnfExtRootNode m_extRoot;
    
    public static SyntaxTree getInstance()
    {
        return m_instance;
    }

    private SyntaxTree()
    {
        m_root    = EbnfRootNode.getInstance();
        m_extRoot = EbnfExtRootNode.getInstance();
    }

    public EbnfRootNode getRoot()
    {
        return m_root;
    }
    
    public EbnfExtRootNode getExtRoot()
    {
        return m_extRoot;
    }
}

EbnfVisitorの検討と実装

検討
EbnfVisitorクラスは、Visitorパターンで巡回するノードクラス側のacceptメソッドの引数の型となるクラスです。

ただし、EBNFデータツリーのすべてのノードクラスに対して、対応するEbnfVisitor#visitを用意しなければいけないわけではありません。

処理が継承クラスの限定実装である
EbnfAsterRepetitionNode(親:EbnfRepetitionNode)
EbnfPlusRepetitionNode(親:EbnfRepetitionNode)
EbnfQuestionNode(親:EbnfRepetitionNode)
EbnfSQuestionNode(親:EbnfQuestionNode→さらに親:EbnfRepetitionNode)
は記載の必要はありません。
これらは自動的に親クラスのノードとして処理します。

実装

public abstract class EbnfVisitor
{
    public abstract void visit(EbnfEofTokenNode n);

    //基本パーサ用クラス
    public abstract void visit(EbnfSequenceNode n);
    public abstract void visit(EbnfAlternativeNode n);
    public abstract void visit(EbnfRepetitionNode n);

    //追加の合成パーサ用クラス(利便性のために用意)
    //memo
    //  このクラス群に抽象構文木の処理は確実に必要ないので
    //  実装しない
    //public abstract void visit(EbnfAsterRepetitionNode n);
    //public abstract void visit(EbnfPlusRepetitionNode n);
    //public abstract void visit(EbnfQuestionNode n);
    //public abstract void visit(EbnfSQuestionNode n);

    //以下処理の必要なノードを記載する
    ・・・・
}

BaseSyntaxParserVisitorの検討と実装

検討
今後のメンテナンス性の向上のために、Visitorクラスは役割毎に分割して継承関係を構築します。

BaseSyntaxParserVisitorクラスはEbnfVisitorの直接の子クラスで、基本のパーサ用クラスの処理を実装します。他にも機能があるので機能一覧を示します。

機能一覧
 1.TokenManagerへのアクセスフィールドの取得
 2.解析の結果を示すm_resultフィールドとアクセスメソッドgetResultの実装
 3.EofTokenのvisitメソッド
 4.基本パーサ用クラス(EbnfSequenceNode,EbnfAlternativeNode,EbnfRepetitionNode)のvisitメソッド

実装は次のようになります。基本パーサ用クラスのvisitメソッドは既にSampleで説明済みですが、TokenManagerからの呼び出し部分が修正してあります。

実装

public abstract class BaseSyntaxParserVisitor extends EbnfVisitor
{
    protected static final TokenManager m_tokenManager
                             = TokenManager.getInstance();
    protected boolean m_result;

    public BaseSyntaxParserVisitor()
    {
        m_result = true;
    }

    public boolean getResult()
    {
        return m_result;
    }

    @Override
    public void visit(EbnfEofTokenNode n)
    {
        Token token = m_tokenManager.nextToken();
        m_result = token.match(n);
    }

    @Override
    public void visit(EbnfSequenceNode n)
    {
        ArrayList<Object> children = n.getChildren();

        for(Object child :children)
        {
            EbnfNodeAcceptor node =  (EbnfNodeAcceptor)child;

            node.accept(this);

            //acceptの結果がOKの場合
            if (m_result)
            {
                //何もしない
            }
            //acceptの結果がNGの場合
            else
            {
                break;
            }
        }
    }

    @Override
    public void visit(EbnfRepetitionNode n)
    {
        int upper = n.getUpper();
        int lower = n.getLower();
        Object child = n.getChild();
        int okCount=0;

        boolean okFlag = true;
        
        //上限値がない場合
        if (upper == EbnfRepetitionNode.ASTER)
        {
            result_false_break1:
            //無限ループ処理
            while(true)
            {
                int count = m_tokenManager.getConsumedCount();
                    
                EbnfNodeAcceptor node =  (EbnfNodeAcceptor)child;

                //子ノード評価を行う
                node.accept(this);

                //評価結果がOKの場合
                if (m_result)
                {
                    //OKカウントを+1する
                    okCount++;
                }
                //評価結果がNGの場合
                else
                {
                    int count2 = m_tokenManager.getConsumedCount();
                    okFlag = (count == count2);
                        
                    //ループ処理を中断する
                    break result_false_break1;
                }
            }
        }
        //上限値がある場合
        else
        {
            result_false_break2:
            //処理ループ(0~上限値まで)
            for (int i=0; i<upper; i++)
            {
                int count = m_tokenManager.getConsumedCount();
                EbnfNodeAcceptor node =  (EbnfNodeAcceptor)child;

                //子ノード評価を行う
                node.accept(this);

                //評価結果がOKの場合
                if (m_result)
                {
                    //OKカウントを+1する
                    okCount++;
                }
                //評価結果がNGの場合
                else
                {
                    int count2 = m_tokenManager.getConsumedCount();
                    okFlag = (count == count2);
                        
                    //ループ処理を中断する
                    break result_false_break2;
                }
            }
        }

        //OKカウントが下限値より少ない場合
        if(okCount<lower)
        {
            //評価NG
            m_result = false;
        }
        //OKカウントが下限値より多い場合
        else
        {
            //評価OK
            m_result = okFlag;
        }
    }

    @Override
    public void visit(EbnfAlternativeNode n)
    {
        ArrayList<Object> children = n.getChildren();

        int count = m_tokenManager.getConsumedCount();

        for(Object child :children)
        {
            EbnfNodeAcceptor node =  (EbnfNodeAcceptor)child;

            node.accept(this);

            //既にconsumeTokenを1回以上実施したか情報を取得する
            int count2 = m_tokenManager.getConsumedCount();

            //consumeTokenをまだ実施していない場合
            if (count == count2)
            {
                //何もしない
            }
            //既にconsumeTokenを1回以上実施した場合
            else
            {
                break;
            }
        }
    }
}

Token側の修正

Sampleではinstanceof句を使っていましたが、本実装はテンプレートパターンを使って処理します。

public abstract class Token
{
    ・・・・・
    
    //追加
    public boolean match(EbnfEofTokenNode n){return false;}
    public boolean match(EbnfCommentNode n){return false;}
    public boolean match(EbnfLimitedLiteralNode n){return false;}
    public boolean match(EbnfSymbolNode n){return false;}
    public boolean match(EbnfWhiteSpaceNode n){return false;}
    public boolean match(EbnfLimitedWordNode n){return false;}
}

Tokenの子クラスの実装

EofTokenクラスの場合は次のようにします。

public class EofToken extends NonDocumentToken
{
    ・・・・
    //追加
    public boolean match(EbnfEofTokenNode n){return true;}
}
Comment(0)

コメント

コメントを投稿する