[ホワイト]コンパイラシリーズ3簡単な式の計算(加算と乗算)(JAVAのリアライズ)を作成するには

私たちの前のテキスト[白] 1シリーズは、どのようなコンパイラコンパイラのフロントエンド技術を作成するには?すでに私たちは、文法的分析は、ASTを作成することです結果を知っています。だから我々は、単純な式の計算を実装することによって生成ASTプロセスの理解を深めなければなりません。:この記事では、に焦点を当てて再帰下降アルゴリズム 文脈自由文法私たちは、説明のみを検討追加 乗算を(議論ここでそれを繰り返す、原理に減算と除算と同じではありません)


原則ドエル

変数宣言文

レッツは、変数宣言文を見ているかを理解「ダウン」

私は先に述べた、とのために、「int型の年齢= 45」この宣言は、我々は、以下に示すASTを構築します。

ルール文で変数を宣言:左部分がある非終端(非ターミナル)それは右である生産(生産者のルール)構文解析の過程では、左側が右側に置き換えられます。非終端後の選択肢がある場合、最終的にはすべて(ターミナル)ターミネータになるまで、トークンで、この代替プロセスを継続します。唯一のターミネータは、ASTの葉ノードになることができます。

int型の変数を宣言するためには、int型トークン、プラスオプションの代入式の背後に続く変数名を、持っている必要があります。私はint型宣言文は、以下の擬似コードの変数を一致させたいです。

//伪代码
MatchIntDeclare(){
  MatchToken(Int);        //匹配Int关键字
  MatchIdentifier();       //匹配标识符
  MatchToken(equal);       //匹配等号
  MatchExpression();       //匹配表达式
}

 次のようにJAVA、特定のコードを実装します。本明細書で使用する場合、トークンストリームは、プリフェッチおよびトークンの動作を読み取ります。


SimpleASTNode node = null;
Token token = tokens.peek();    //预读
if (token != null && token.getType() == TokenType.Int) {   //匹配Int
    token = tokens.read();      //消耗掉int
    if (tokens.peek().getType() == TokenType.Identifier) { //匹配标识符
        token = tokens.read();  //消耗掉标识符
        //创建当前节点,并把变量名记到AST节点的文本值中,
        //这里新建一个变量子节点也是可以的
        node = new SimpleASTNode(ASTNodeType.IntDeclaration, token.getText());
        token = tokens.peek();  //预读
        if (token != null && token.getType() == TokenType.Assignment) {
            tokens.read();      //消耗掉等号
            SimpleASTNode child = additive(tokens);  //匹配一个表达式
            if (child == null) {
                throw new Exception("invalide variable initialization, expecting an expression");
            }
            else{
                node.addChild(child);
            }
        }
    } else {
        throw new Exception("variable name expected");
    }
}

この全体の一致の宣言方法:

変数宣言文を解析すると、トークンで我々初見ではありませんint型そうだとすれば、それは、int型のバック書き留めASTノードを作成し、変数名を、その後、振り返ることで初期化部、とではありません等号プラス表現私たちは、そう、等号がある場合は、チェックアウト一致式が続きます。

いわゆる「ダウン」意味:下位のアルゴリズム優れたアルゴリズムの呼び出し。AST発電性能は、より高いノードを生成するための優れたアルゴリズムは、下位のアルゴリズムは、下位ノードを生成します。

もう少し直感的に、我々は可視化サイトを参照してくださいする前に、まだ、代入文の末尾にASTツリーノードの種類を生産しています。

「INT = 45年齢」 、我々は次のノードを生成します。

算術式

実際、前述の変数宣言文の文法を完全に解決することができ、通常の文法を使用し、定期的な文法はありません。しかし、次の算術式は、直接正規文法を使用することはできませんが、使用する必要があり、文脈自由文法を

その組み合わせが多すぎるので、私たちは知っている、算術式(のみ加算と乗算を考える)のために、我々は、ルールはより多くの悩みを定義します:

  • 4 + 6
  • 4 + 6 * 5
  • 6 * 5 + 4
  • 6 * 5
  • .......

異なる優先順位算術記号による同時に、で、我々は直接固定化し、正規文法が解決使用することはできません。

解決の優先順位の問題

私たちはまず、ある算術式の優先順位、対処する方法を検討しなければならない「の後の最初の乗算、除算、加算と減算」の問題を。この問題を解決する前に、私たちは、ASTがどのように計算した結果である明確でなければなりません。

AST計算:次いで、ルートノードから分割、深さ優先トラバーサル、徐々に下層の計算の値を返します。

以下、第1の計算ノードの最下位(最深部ノード):3×5 = 15、右加算器へ、その後ノード15に戻り、15 + 2 = 17を算出します。

この点で、我々は優先順位、のみ完了したい子ノード缶の加算(減算)と乗算(除算)ノードをこれにより深さ優先トラバーサルは、最初の添加を完了するためにバック加算ノードへ、次いで乗算を計算し、場合。

我々は、次を詳しく見てとる「入れ子になった」文法を。また、文法表現で、より高い優先順位に乗算、私たちは巣乗算式の文法をできるよう、プラス式は、プラス表現、プラスの乗算式として見ることができる一方で、このネストされたアプローチは、我々は2つのだけ、ネストされたルールにマッチしたすべての複雑なプラスの表現を行くことができます再帰として理解することができ、またはアルゴリズムを学んだ友人は、プロセスの途中で動的な計画であることを理解することができます。

例えば:

  • 以下のために2×3この式:ちょうどmultiplicativeExpressionでadditiveExpressionを呼び出します。
  • 以下のために2 + 3×5本複雑な式、ルールがadditiveExpressionプラスmultiplicativeExpression呼び出されます。

長いこの単純なを完了するために、スプリットなどとして、どのくらいの出会い乗算加え算術式の後に関係なくは、二つの形式に分割することができます。これは再帰的な魅力です。

additiveExpression
    :   multiplicativeExpression
    |   additiveExpression Plus multiplicativeExpression
    ;

multiplicativeExpression
    :   IntLiteral
    |   multiplicativeExpression Star IntLiteral
    ;

次のように視覚的に表現。

我々は算術式を解析するときに、この知識により、我々は得ることができる  追加のルールが一致します 。 加え、ルールは次のようになります、ネストされた試合の乗算ルール 私たちは、優先順位計算を達成するために、文法を入れ子に。これは、ことに留意すべきであるほかのルールも再帰的に参照する追加ルール

この文法は正規文法を使用する方法がありません、それはとして知られているより表現正規文法、より普遍的なあり:文脈自由文法 定期的な文法は文脈自由文法のサブセットです。彼らの違いは、文脈自由文法は、再帰呼び出しを可能にし、定期的な文法が許可されていません文脈自由手段は、その、どのような状況下で、文法導出規則は同じです。たとえば、変数宣言文の中で、あなたは他の場所でも、算術式を使用することができながら、変数の初期化を行うために算術式を使用する場合があります。あなたがどんなに、文法算術式が同じで、使用加算と乗算に許可されている優先順位を計算変わりません。

TIPS:文脈自由文法は、ちょうどロシア人形のようなものです、のような、それぞれの層の状況を議論する必要性が同じで、長い文字列は、複雑な数式を分割し、最終的には単純な乗算と加算で構成されています。図低い発現は、 A * = 2 8 9 + + + 2 * 5 *。1. 4 追加の長いリストは、層によって最終層破壊算術式を、乗算、及び最終的にリーフノードに単純である2元の追加2元乗算表現します。(私が表現しようとしています)

無限ループの問題を解決するために左再帰

OK、これまでのところ、我々はすでに知っている、ネストされた使用可能に文脈自由文法を  再帰的に任意の加算乗算算術式を表すことができます。私たちのアイデアは、それを実装するために問題がないことを、全く正しいのですか?もちろんありません!

2 + 3:再帰の単純な足し算で見てみましょう。

additiveExpression
    :   IntLiteral
    |   additiveExpression Plus IntLiteral
    ;

上記の文法に従い、のは、マッチングプロセスを分析してみましょう:

  • 最初の外観は、それは「2 + 3」が加法式で見出され、リテラルではありません。
  • それはまた、方程式であるかどうかを確認し、実際にそれを発見した、再帰;
    • そして、それはリテラルであるかどうかを確認し、見つかりませんでした。
    • そして、それはほかとみなされた場合、我々は、再び再帰的な存在であることがわかっ見ます
      • .....
      • .....

私たちは、方法は、加算式は短い答えは自分自身を再帰的に呼び出すされたことが問題を解決していません我々は再帰に進み、まだ見つかっ。この状況はされて再帰的左上記の分析を通じて、我々は知っている左は、再帰的なアルゴリズムを処理することはできません再帰下降は、これが最大の問題再帰下降アルゴリズムです

どのようにそれを解決するには?どのようにプラス記号に置き換え、「additiveExpression」の後ろに置きますか?のは、それを試してみましょう。

つまり、状況を添加する前に、我々は、フロントプラス議論、この無限の再帰に立って議論しています。我々は従うプラスプラス記号も再帰的になった後、二つに算術型の区切り文字、算術式を合計サインインプラス再帰的に前にログインすると、あなたは完全に問題を解決することができます。私たちは、ターン2 + 3の例を参照して続行します。

  • 前にプラス記号:コールmultiplicativeExpression
    • 発見IntLiteral:2
  • コールmultiplicativeExpression:プラス記号の後
    • 発見IntLiteral:3

無限ループの問題に優れたソリューションを提供します。

additiveExpression
    :   multiplicativeExpression
    |   multiplicativeExpression Plus additiveExpression
    ;
multiplicativeExpression
    :   IntLiteral
    |   multiplicativeExpression Star IntLiteral
    ;

2つの制作プラス式は第1の乗算式に一致しなければならないためではない、これは確かに加算ノードノードでない場合、レッツ・トライ乗算式は、一致させることができます。このような場合には、呼び出し側がこの試合に成功しなかった、それにはnullを返します。乗算表現マッチが成功した場合、再帰的にプラス表現に一致するようであるプラス記号の右側の部分に一致するようにしてみてください。マッチが成功した場合、ASTノードリターンの追加を構築します。(オリジナルソース:コンパイラ理論の米国)

式の評価

ここでは詳細、深さ優先探索完全ASTに行くことはありませんが、ルートノードの値は、式の値が計算されています。

キーコードの実装(テキストの完全なコード末尾を参照してください)

さらに乗算

    /**
     * 语法解析:加法表达式
     * @return
     * @throws Exception
     */
    private SimpleASTNode additive(TokenReader tokens) throws Exception {
        //先加号前面匹配乘法
        SimpleASTNode child1 = multiplicative(tokens);
        SimpleASTNode node = child1;

        Token token = tokens.peek();
        if (child1 != null && token != null) {
            if (token.getType() == TokenType.Plus || token.getType() == TokenType.Minus) {
                token = tokens.read();
                SimpleASTNode child2 = additive(tokens);
                if (child2 != null) {
                    node = new SimpleASTNode(ASTNodeType.Additive, token.getText());
                    node.addChild(child1);
                    node.addChild(child2);
                } else {
                    throw new Exception("【乘法表达式错误】:需要补充加号右边部分");
                }
            }
        }
        return node;
    }

    /**
     * 语法解析:乘法表达式
     * @return
     * @throws Exception
     */
    private SimpleASTNode multiplicative(TokenReader tokens) throws Exception {
        SimpleASTNode child1 = primary(tokens);
        SimpleASTNode node = child1;

        Token token = tokens.peek();
        if (child1 != null && token != null) {
            if (token.getType() == TokenType.Star || token.getType() == TokenType.Slash) {
                token = tokens.read();
                SimpleASTNode child2 = primary(tokens);
                if (child2 != null) {
                    node = new SimpleASTNode(ASTNodeType.Multiplicative, token.getText());
                    node.addChild(child1);
                    node.addChild(child2);
                } else {
                    throw new Exception("【加法表达式错误】:需要补充乘号右边部分");
                }
            }
        }
        return node;
    }

ASTビルドプロセス(再帰)

    /**
     * 语法解析:根节点
     * @return
     * @throws Exception
     */
    private SimpleASTNode prog(TokenReader tokens) throws Exception {
        //构建根节点
        SimpleASTNode node = new SimpleASTNode(ASTNodeType.Programm, "Calculator");

        //构建子节点(递归完成)
        SimpleASTNode child = additive(tokens);

        if (child != null) {
            node.addChild(child);
        }
        return node;
    }

コンピューティングノード値(再帰的に)

    /**
     * 对某个AST节点求值,并打印求值过程。
     * @param node
     * @param indent  打印输出时的缩进量,用tab控制
     * @return
     */
    private int evaluate(ASTNode node, String indent) {
        int result = 0;
        System.out.println(indent + "Calculating: " + node.getType());
        switch (node.getType()) {
            case Programm:
                for (ASTNode child : node.getChildren()) {
                    result = evaluate(child, indent + "\t");
                }
                break;
            case Additive:
                ASTNode child1 = node.getChildren().get(0);
                int value1 = evaluate(child1, indent + "\t");
                ASTNode child2 = node.getChildren().get(1);
                int value2 = evaluate(child2, indent + "\t");
                if (node.getText().equals("+")) {
                    result = value1 + value2;
                } else {
                    result = value1 - value2;
                }
                break;
            case Multiplicative:
                child1 = node.getChildren().get(0);
                value1 = evaluate(child1, indent + "\t");
                child2 = node.getChildren().get(1);
                value2 = evaluate(child2, indent + "\t");
                if (node.getText().equals("*")) {
                    result = value1 * value2;
                } else {
                    result = value1 / value2;
                }
                break;
            case IntLiteral:
                result = Integer.valueOf(node.getText()).intValue();
                break;
            default:
        }
        System.out.println(indent + "Result: " + result);
        return result;
    }

 輸出

概要

  • 「ダウン」と「再帰的」の二つの特徴で再帰降下アルゴリズム。文法規則は、文法、必ずしも書き込みアルゴリズムにより、実質的に均質であるとのことです。
  • 左再帰はループに再帰を引き起こし、そのため再帰的シンボルの添加の前と後から分割する必要ができます。
  • それは再帰的に、後者の缶ない入れ子にすることができることを除き、文脈自由文法は、正規文法よりも普遍的です。

 完全なコード:https://github.com/SongJain/TheBeautyOfCompiling/tree/master/SimpleCalculator


個人的なオリジナルの研究ノートは、もちろんを参照してください「コンパイラの原理の美しさ。」

公開された62元の記事 ウォン称賛34 ビュー20000 +

おすすめ

転載: blog.csdn.net/weixin_41960890/article/details/105102506