08 ASTからコードを復元する [オリジナル言語インタプリタを作る]

08 ASTからコードを復元する [オリジナル言語インタプリタを作る]

式の構文解析の前に

式の構文解析に移りたいのですが、先にちょっとした確認用の機能を作っておきます。これが済んだら式の解析です。

ASTからソースコードを復元する機能

今回は、デバッグ用に使える機能を追加していきます。ASTからソースコードを作成する機能です。これがあると、後の式の解析機能を実装するのが楽になります。

ToCode()

INodeToCode() を追加します。すべてのASTは ToCode() を実装し、各ノードの状態に対応したコードを返します。

public interface INode
{
    string TokenLiteral();
    string ToCode();
}

追加すると INode を実装するクラス(Root, LetStatement, ReturnStatement, Identifier)についてエラーがコンパイルエラーになるので、それぞれに実装を追加します。

Root に追加

まずは Root です。

Ast/Root.cs

public class Root : INode
{
    // ..
    public string ToCode()
    {
        var builder = new StringBuilder();
        foreach (var ast in this.Statements)
        {
            builder.AppendLine(ast.ToCode());
        }
        return builder.ToString().TrimEnd();
    }
}

プログラムを構成するすべての式について、ToCode() を呼び出した結果を改行区切りで連結した文字列を返します。

末尾に改行文字が余計につくので Trim しています。

後はそれぞれのクラスに元のコードになるように文字列を復元します。

Identifier に追加

public class Identifier : IExpression
{
    // ..
    public string TokenLiteral() => this.Token?.Literal ?? "";
}

LetStatement に追加

public class LetStatement : IStatement
{
    // ..
    public string ToCode()
    {
        var builder = new StringBuilder();
        builder.Append(this.Token?.Literal ?? "");
        builder.Append(" ");
        builder.Append(this.Name?.ToCode() ?? "");
        builder.Append(" = ");
        builder.Append(this.Value?.ToCode() ?? "");
        builder.Append(";");
        return builder.ToString();
    }
}

ReturnStatement に追加

public class ReturnStatement : IStatement
{
    // ..
    public string ToCode()
    {
        var builder = new StringBuilder();
        builder.Append(this.Token?.Literal ?? "");
        builder.Append(" ");
        builder.Append(this.ReturnValue?.ToCode() ?? "");
        builder.Append(";");
        return builder.ToString();
    }
}

テストで確認する

AstTest.cs

using Gorilla.Ast;
using Gorilla.Ast.Expressions;
using Gorilla.Ast.Statements;
using Gorilla.Lexing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;

namespace UnitTestProject
{
    [TestClass]
    public class AstTest
    {
        [TestMethod]
        public void TestNodeToCode1()
        {
            var code = "let x = abc;";

            var root = new Root();
            root.Statements = new List<IStatement>();

            root.Statements.Add(
                new LetStatement()
                {
                    Token = new Token(TokenType.LET, "let"),
                    Name = new Identifier(
                        new Token(TokenType.IDENT, "x"),
                        "x"
                    ),
                    Value = new Identifier(
                        new Token(TokenType.IDENT, "abc"),
                        "abc"
                    ),
                } 
            );

            Assert.AreEqual(code, root.ToCode(), "Root.ToCode() の結果が間違っています。");
        }
    }
}

let x = abc; に相当するASTをコンストラクタを使って構築し、それをコードにして確認しています。右辺の式については実装済なのが識別子だけなので、それを使います。

テストが通れば準備完了です。式の解析にようやく移れます。

テスト名:    TestNodeToCode1
テストの完全名:    UnitTestProject.AstTest.TestNodeToCode1
テスト ソース:    E:\work\cs\Gorilla\UnitTestProject\AstTest.cs : 行 14
テスト成果:    成功
テスト継続時間:    0:00:00.035122

ここまでのコード

mntm0/Gorilla at ee7f1a767813115d1440002aefb11e5533c607e7

以上。

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