パターンインタープリター

含まれるもの

インタープリター パターンは、動作デザイン パターンを指します。このパターンを使用すると、AST ツリーを操作して独自のプログラミング言語を実装できます。AST ツリーの頂点は、言語の機能を提供する Interpret メソッドを実装する終端式と非終端式です。

  • 終端式 – 例: 文字列定数 – 「Hello World」
  • 非終端式 – たとえば、Print(“Hello World”) には、Print と終端式 “Hello World” からの引数が含まれます

違いは何ですか?違いは、解釈は終端式で終了しますが、非終端式の場合は、入力されるすべての頂点/引数にわたって深く継続されることです。 AST ツリーが非終端式のみで構成されている場合、アプリケーションは決して完了しません。どのプロセスにも特定の有限性が必要です。この有限性が終端式の正体であり、通常、終端式には文字列などのデータが含まれます。

AST ツリーの例を以下に示します。


Dcoetzee、CC0、ウィキメディア コモンズ経由

ご覧のとおり、終端式は定数と変数であり、残りは非終端式です。

含まれないもの

インタープリタの実装には、AST ツリーに入力された言語文字列の解析は含まれません。終端式と非終端式のクラスを実装し、入力で Context 引数を使用してメソッドを解釈し、式の AST ツリーを作成し、ルート式で Interpret メソッドを実行するだけで十分です。コンテキストを使用して、実行時にアプリケーションの状態を保存できます。

実装

パターンには以下が含まれます:

  • クライアント – AST ツリーを返し、ルート ノード (クライアント) に対して Interpret(context) を実行します
  • コンテキスト – アプリケーションの状態が含まれており、解釈時に式に渡されます (コンテキスト)
  • 抽象式 – Interpret(context) (Expression) メソッドを含む抽象クラス
  • ターミナル式は最終式であ​​り、抽象式 (ターミナル式) の子孫です
  • 非終端式は有限式ではありません。これには AST ツリーの深部にある頂点へのポインタが含まれます。通常、非終端式(NonterminalExpression)の解釈の結果に影響します。

C# でのクライアントの例

        static void Main(string[] args)
        {
            var context = new Context();
            var initialProgram = new PerformExpression(
                new IExpression[] {
                    new SetExpression("alpha", "1"),
                    new GetExpression("alpha"),
                    new PrintExpression(
                        new IExpression[] {
                            new ConstantExpression("Hello Interpreter Pattern")
                        }
                    )
                }
            );
            System.Console.WriteLine(initialProgram.interpret(context));
        }
}

C# での抽象式の例

{
    String interpret(Context context);
}

C# のターミナル式の例 (文字列定数)

{
    private String constant;

    public ConstantExpression(String constant) {
        this.constant = constant;
    }

    override public String interpret(Context context) {
        return constant;
    }
}

C# の非終端式の例 (区切り文字「;」を使用して、下位頂点の結果を開始して連結する)

{
    public PerformExpression(IExpression[] leafs) : base(leafs) {
        this.leafs = leafs;
    }
    
    override public String interpret(Context context) {
        var output = "";
        foreach (var leaf in leafs) {
            output += leaf.interpret(context) + ";";
        }
        return output;
    }
}

機能的に実行できますか?

知られているように、すべてのチューリング完全言語は同等です。オブジェクト指向パターンを関数型プログラミング言語に移植することは可能ですか?

実験として、Elm という Web 用の FP 言語を取り上げてみましょう。 Elm にはクラスはありませんが、レコードとタイプがあるため、次のレコードとタイプが実装に関係します。

  • 式 – 考えられるすべての言語式のリスト (式)
  • 従属式 – 非終端式 (ExpressionLeaf) に従属する式
  • コンテキスト – アプリケーションの状態 (コンテキスト) を保存するレコード
  • Interpret(context) メソッドを実装する関数 – ターミナル式と非ターミナル式の機能を実装する必要なすべての関数
  • インタープリターの状態の補助レコード – インタープリターの正しい動作に必要で、インタープリターの状態とコンテキストを保存します

Elm で使用可能な式のセット全体の解釈を実装する関数の例:

  case input.expression of
    Constant text ->
      { 
        output = text, 
        context = input.context 
      }
    Perform leafs ->
      let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
        let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
          let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
            {
              output = (runExpressionLeaf outputExpressionInput).output,
              context = input.context
            }
    Print printExpression ->
      run 
      { 
        expression = printExpression, 
        context = input.context 
      }
    Set key value ->
      let variables = Dict.insert key value input.context.variables in
      {
        output = "OK",
        context = { variables = variables }
      }
    Get key ->
      {
        output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
        context = input.context
      }

解析についてはどうですか?

ソース コードを AST ツリーに解析することはインタープリター パターンには含まれていません。ソース コードを解析するにはいくつかの方法がありますが、それについてはまた別の機会に説明します。
Elm のインタープリタの実装では、頂点の解析と下位頂点の解析という 2 つの関数で構成される単純なパーサーを AST ツリーに作成しました。

parseLeafs state =
    let tokensQueue = state.tokensQueue in
        let popped = pop state.tokensQueue in
            let tokensQueueTail = tail state.tokensQueue in
                if popped == "Nothing" then
                    state
                else if popped == "Perform(" then
                    {
                        tokensQueue = tokensQueue,
                        result = (state.result ++ [Node (parse tokensQueue)])
                    }
                else if popped == ")" then
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = state.result
                    }
                else if popped == "Set" then
                    let key = pop tokensQueueTail in
                        let value = pop (tail tokensQueueTail) in
                            parseLeafs {
                                tokensQueue = tail (tail tokensQueueTail),
                                result = (state.result ++ [Node (Set key value)])
                            }
                else if popped == "Get" then
                    let key = pop tokensQueueTail in
                        parseLeafs {
                            tokensQueue = tail tokensQueueTail,
                            result = (state.result ++ [Node (Get key)])
                        }
                else 
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = (state.result ++ [Node (Constant popped)])
                    }

parse tokensQueue =
    let popped = pop tokensQueue in
        let tokensQueueTail = tail tokensQueue in
            if popped == "Perform(" then
                Perform (
                    parseLeafs {
                        tokensQueue = tokensQueueTail, 
                        result = []
                    }
                ).result
            else if popped == "Set" then
                let key = pop tokensQueueTail in
                    let value = pop (tail tokensQueueTail) in
                        Set key value
            else if popped == "Print" then
                Print (parse tokensQueueTail)
            else
                Constant popped

リンク

https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp

ソース

https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/

Leave a Comment

Your email address will not be published. Required fields are marked *