07 return文の構文解析 [オリジナル言語インタプリタを作る]

07 return文の構文解析 [オリジナル言語インタプリタを作る]

return 文

引き続き、構文解析器(パーサー) を拡張していきます。今回は return文 をパースできるようにします。

パーサーの拡張拡張の方法は簡単です。

  1. AST を定義する。
  2. Ast を返す PrseXxx() を定義する。
  3. ParseXxx() を呼び出す。

return文をいくつか具体例を挙げると以下のような感じです。

return 1;
return x;
return add(1, 2);

リテラルや識別子、関数の呼び出し結果が考えられます。抽象的にreturn文の構文規則について考えると次のようになります。

return <expression>;

return の後ろに返却する値を生成する式が来るだけです。例によってまだ ParseExpression() は実装されていないので、セミコロンまで読み飛ばす仮実装で済ませます。式がパースできるようになれば、let文のパースと合わせて修正します。

実装

AST の定義

ではreturn文を表す抽象構文木を定義します。名前は ReturnStatement です。

Ast/Expressions/Return

using Gorilla.Lexing;

namespace Gorilla.Ast.Statements
{
    public class ReturnStatement: IStatement
    {
        public Token Token { get; set; }
        public IExpression ReturnValue { get; set; }

        public string TokenLiteral() => this.Token.Literal;
    }
}

式なので IStatement クラスを実装し、プロパティで return トークンと返す値を表す式を持ちます。

テストを書く

では実装前にテストを作成します。テストはlet文とあまり変わりません。式のパースがまだなのでテストできるのはちゃんと ReturnStatement が来るかどうかくらいです。

[TestMethod]
public void TestReturnStatement1()
{
    var input = @"return 5;
return 10;
return = 993322;";

    var lexer = new Lexer(input);
    var parser = new Parser(lexer);
    var root = parser.ParseProgram();
    this._CheckParserErrors(parser);

    Assert.AreEqual(
        root.Statements.Count, 3,
        "Root.Statementsの数が間違っています。"
    );

    foreach (var statement in root.Statements)
    {
        var returnStatement = statement as ReturnStatement;
        if (returnStatement == null)
        {
            Assert.Fail("statement が ReturnStatement ではありません。");
        }

        Assert.AreEqual(
            returnStatement.TokenLiteral(), "return",
            $"return のリテラルが間違っています。"
        );
    }
}

このテストを通すために実装をしていきます。

ParseReturnStatement

ParseReturnStatement() は、現在のトークンからreturn文をパースして ReturnStatement を返します。

Parsing/Parser.cs

public class Parser
{
    // ..
    public IStatement ParseStatement()
    {
        switch (this.CurrentToken.Type)
        {
            case TokenType.LET:
                return this.ParseLetStatement();
            case TokenType.RETURN:
                return this.ParseReturnStatement();
            default:
                return null;
        }
    }

    public ReturnStatement ParseReturnStatement()
    {
        var statement = new ReturnStatement();
        statement.Token = this.CurrentToken;
        this.ReadToken();

        // TODO: 後で実装。
        while (this.CurrentToken.Type != TokenType.SEMICOLON)
        {
            // セミコロンが見つかるまで
            this.ReadToken();
        }

        return statement;
    }
}

式のパースはセミコロンまで読み飛ばすことでひとまず無視して、あとで戻ってきましょう。あとはlet文と同じです。というより “return” トークンの確認のみなのでそれより簡単です。呼び出し元も ParseStatement() に条件分岐を1つ追加するだけです。簡単ですね。

これで先ほどのテストが通ります。

まとめ

return文のパースを実装しました。実装はすでにあるパーサーを拡張する形で作りました。式のパースは相変わらず後まわしにしています。

次はようやく式のパースに移るのですが、なぜ先にreturn文の構文解析を先に実装したかというと、作成中のインタプリタ(Gorilla)の構文規則において、文はletとreturnしかないからです!! つまり、あとはすべて式となります。いまのところ..。

ifも式だし、関数定義も式です。例外が式文と呼ばれるもので、1つの式をラップした文のことです。

大抵のスクリプト言語だと、式だけからなる文というのを持つ。

1 + 1;

このコードは Javascript だと有効です。しかしこのような式だけからなる文を許さない言語もあります。C#がまさにそうです。

Gorilla は、式文を用いて1つの式を1つの文として扱えるようにします。

次回は式の構文解析に取り掛かります。以上

開発いろいろカテゴリの最新記事