シンプルなスクリプト言語を実装するコンパイラのシリーズを作成するためのホワイト[5]

私たちは、単純なスクリプト言語の実現に以前の計算に基づいてコードを追加していくことができます。


追加された機能

  • 変数の宣言と初期化ステートメントのサポート。"INT年齢;"、 "INT年齢= 45;"、 "int型の年齢= 4 + 5;"
  • サポートの割り当て。"年齢= 45;"
  • あなたは、式の中で変数を使用することができます。"年齢+ 10 * 2。"
  • 完全なコマンドライン端末を実現します。

完璧な文法規則

宣言

INTは、最後のセミコロンと結合等号および発現、ある識別子及び任意の初期化部、続いて、で開始します。

intDeclaration : 'int' Identifier ( '=' additiveExpression)? ';';

式文

現在、唯一のそれは、セミコロンの後ろにもあり、将来的にこのような条件式などの他の表現を、追加することができ、また、式をサポートしています。

expressionStatement : additiveExpression ';';

割り当て

識別子は、続いて等号セミコロンで結合され、そして式:

assignmentStatement : Identifier '=' additiveExpression ';';

識別子とブラケット

式の中で変数を使用するためには、我々はまた含むことに加えて、primaryExpressionを書き換える必要があり、整数リテラルの外側を、だけでなく、含まれている識別子括弧式を

primaryExpression : Identifier| IntLiteral | '(' additiveExpression ')';

スクリプト言語のサポート変数をしてみましょう

機能以下のフォームを完了することができるストレージスペースの一定量を、必要なスクリプト言語変数のサポートを取得します。

int age = 45;
age + 10 * 2;

変数に値を代入するには、我々は時のスクリプト言語インタプリタ作成する必要があります店を、別の変数とその値を記録します。

private HashMap<String, Integer> variables = new HashMap<String, Integer>();

私たちは、単に使用HashMapをとして  変数記憶域変数宣言文や課題、およびデータ記憶領域を変更することができ、すべての変数で。


割り当て実現

"年齢=年齢+ 2 * 10。"

割り当てを一致させるには、まず最初のトークンをご覧ください識別子ではありません。ではない、それがnullを返した場合、マッチは失敗します。

最初の識別子トークンは確かに、我々はそれが消費に置く場合は、振り返っては等号が続いていません。

それが等号でない場合、我々は、これは代入文、かもしれないものの表現ではないことを証明します。その後、我々は何事もなかったかのように、単に消費トークンをフォールバックし、NULLを返します。フォールバックメソッドの呼び出しが未読です()。

確かに等号が背後に従った場合、その後、戻って見て表現の後ろに続く式は、セミコロンされていないではありません続けます。そうでない場合は、エラーだけで罰金。これは、代入文の解析を完了します。

private SimpleASTNode assignmentStatement(TokenReader tokens) throws Exception {
    SimpleASTNode node = null;
    Token token = tokens.peek();    //预读,看看下面是不是标识符
    if (token != null && token.getType() == TokenType.Identifier) {
        token = tokens.read();      //读入标识符
        node = new SimpleASTNode(ASTNodeType.AssignmentStmt, 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 assignment statement, expecting an expression");
            }
            else{
                node.addChild(child);   //添加子节点
                token = tokens.peek();  //预读,看看后面是不是分号
                if (token != null && token.getType() == TokenType.SemiColon) {
                    tokens.read();      //消耗掉这个分号

                } else {            //报错,缺少分号
                    throw new Exception("invalid statement, expecting semicolon");
                }
            }
        }
        else {
            tokens.unread();    //回溯,吐出之前消化掉的标识符
            node = null;
        }
    }
    return node;
}

 同様に、我々は、変数宣言文のために同様のロジックで書き直されることができます。


失敗した必要性の後戻り

書類と照合すると試合の半分に一致するように見えることが問題を特定することができませんでした。今回ので、次の試合の構文の必要性は、そう私たちのトークンストリームのために、必要がある初期状態に戻ります私たちは試合前にどのように多くの手順の構文を知らないので、その最良の方法は、別のルールを試して行く、それを直接復元することです。

暫定 と  バックトラック プロセスは、再帰下降アルゴリズムの典型的な特徴です。再帰下降アルゴリズムは単純ですが、それは裁判とバックトラックを介して行われますが、常にその強力な場所で正しい構文を、一致させることができます。欠点は、少し後戻りは効率を低下さです。しかし、我々は改善し、これに基づいて最適化、達成することができ、予測分析を再帰下降を、だけでなく、非再帰的な予測分析

そして、バックエラー

私たちは、あなたが戻って行く必要があるとき、与えられたことをするとき知っておく必要があります。

我々プロンプト  構文エラー 我々は、他の可能なマッチのオプションが用意されていない知っている時間の後戻りを無駄にする必要はありませんと言われています。このため、構文エラー、エラー早いほど良いです。アドバンスレポートの構文エラーは、最適化アルゴリズムは、私たちが書きです。

コンパイラを書くとき、私たちだけではなく、正しい構文を解決できるようにしたいが、また、ヘルプのユーザーに、文法エラーのために可能な限り迅速にやさしいを提供はすぐにエラーを見つけます。


REPLインタラクティブなインターフェース

スクリプト言語は、通常はすぐにそれを解釈して出力を得る、などのNode.js、Pythonのようので、そのようなインタフェースを提供し、あなたは1文で1を入力することができますコマンドラインウィンドウを提供します。この入力は、実行は、印刷サイクルが呼び出されREPL(読む-EVAL-印刷ループ)

また、簡単なREPLを実施しました。セミコロンに直面したとき、基本的に線によって端末回線からコードを読み取り、それは解釈されます。

SimpleParser parser = new SimpleParser();
SimpleScript script = new SimpleScript();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));   //从终端获取输入

String scriptText = "";
System.out.print("\n>");   //提示符

while (true) {             //无限循环
    try {
        String line = reader.readLine().trim(); //读入一行
        if (line.equals("exit();")) {   //硬编码退出条件
            System.out.println("good bye!");
            break;
        }
        scriptText += line + "\n";
        if (line.endsWith(";")) { //如果没有遇到分号的话,会再读一行
            ASTNode tree = parser.parse(scriptText); //语法解析
            if (verbose) {
                parser.dumpAST(tree, "");
            }
          
            script.evaluate(tree, ""); //对AST求值,并打印

            System.out.print("\n>");   //显示一个提示符

            scriptText = "";
        }

    } catch (Exception e) { //如果发现语法错误,报错,然后可以继续执行
        System.out.println(e.getLocalizedMessage());
        System.out.print("\n>");   //提示符
        scriptText = "";
    } 
}

ステートメントが正しい場合、システムはすぐに結果をフィードバックします。文が間違っている場合、REPLは、エラーメッセージにフィードバックされ、次のステートメントに対処していきます。

私のコンパイラ!

式の中の変数宣言、初期化と使用の識別子

 文法エラー


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

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

おすすめ

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