1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Copilotが生成した日本語トランスコンパイラ言語 Re:MindのASTパーサが動作するまで(ステップ9)

1
Posted at

はじめに

生成AIに日本語トランスコンパイラ言語 Re:Mindの実装言語仕様を読み込ませて、いくつかの抽象度レベルのプロンプトからトランスコンパイラ的な動きをさせてみたところ、Re:Mindっぽい仕様のASTパーサのテンプレと3言語(Java,C#,VB.NET)のコードジェネレータのテンプレを生成してもらえた1ので、本記事はその検証の第9ステップです。

Microsoft Copilot

生成AIはWindows11アプリケーションのMicrosoft Copilotとします。モードは「Smart(GPT5.1)」。サインインした状態で使用します。第6ステップの後にはサインアウトがあります。

第9ステップ

第4ステップでProgram.csが動作可能となりました。ただし、まだターゲット言語C#のソースコード出力にはいたらずASTをシリアライズしたJson出力もまだ不完全のようでしたので、第5ステップでその点を調整しましたところ、過去に生成されたソースの再確認応答がありましたので、第6ステップ以降はプロンプト10240文字以内の制限内での生成済ソースコードの分割提示しており、今回もその続きです。

プロンプト

以下がParseMethodBodyの全文です。

       private IEnumerable<Statement> ParseMethodBody(string methodNameJa)
        {
            var stmts = new List<Statement>();

            while (_index < _lines.Length)
            {
                var line = _lines[_index].Trim();

                if (line == "△") // メソッド終了
                {
                    _index++;
                    break;
                }

                // ローカル変数のJavadoc
                if (line.StartsWith("/**"))
                {
                    var javadoc = ParseJavadoc();
                    // 直後の行がローカル変数になる前提
                    var varStmt = ParseLocalVariableDeclaration(javadoc);
                    if (varStmt != null)
                    {
                        stmts.Add(varStmt);
                        continue;
                    }
                    else
                    {
                        continue;
                    }
                }

                // 〇外側 < 配列.Length の間は繰り返す → while
                if (line.StartsWith("〇") && line.Contains("の間は繰り返す"))
                {
                    var whileStmt = ParseWhileStatement(line);
                    stmts.Add(whileStmt);
                    continue;
                }

                // 〇int i=0,i<配列.Length,i++ 繰り返す → for
                if (line.StartsWith("〇int") && line.Contains("繰り返す"))
                {
                    var forStmt = ParseForStatement(line);
                    stmts.Add(forStmt);
                    _index++;
                    // for の中身1行(コンソール表示する)のみ、今回前提
                    var bodyLine = _lines[_index].Trim();
                    var expr = ParseConsoleWriteLineElement(bodyLine);
                    if (expr != null)
                    {
                        forStmt.Body.Add(new ExpressionStatement { Expression = expr });
                    }
                    // 〇ここまで
                    while (_index < _lines.Length && !_lines[_index].Trim().StartsWith("〇ここまで"))
                    {
                        _index++;
                    }
                    _index++; // 「〇ここまで」超え
                    continue;
                }

                // 呼び出し1行: □バブルソートする(配列)
                if (line.StartsWith("□バブルソートする"))
                {
                    var expr = new InvocationExpression
                    {
                        Target = new IdentifierExpression
                        {
                            NameJa = "バブルソートする",
                            NameEn = "bubbleSort"
                        }
                    };
                    expr.Arguments.Add(new IdentifierExpression
                    {
                        NameJa = "配列",
                        NameEn = "array"
                    });
                    stmts.Add(new ExpressionStatement { Expression = expr });
                    _index++;
                    continue;
                }

                // □コンソール表示する(配列[i]) のような行(他メソッドでもありうる)
                if (line.StartsWith("□コンソール表示する"))
                {
                    var expr = ParseConsoleWriteLineElement(line);
                    if (expr != null)
                    {
                        stmts.Add(new ExpressionStatement { Expression = expr });
                    }
                    _index++;
                    continue;
                }

                _index++;
            }

            return stmts;
        }

生成レスポンス

原因はかなりはっきりしていて、「デシリアライズ」ではなく「パース側(ParseMethodBody とその中で呼んでいるメソッド)」にあります。

  1. まず前提の整理:デシリアライズは悪くない
    出力されている JSON を見ると、すでにこの段階で Body が空になっています。
"Methods": [
  {
    "NameJa": "メイン",
    "NameEn": "main",
    "ReturnType": "void",
    "Modifiers": [ "static" ],
    "Javadoc": { ... },
    "Parameters": [ ... ],
    "Body": []   ここが最初から空
  }
]

つまり、

  • JsonSerializer.Serialize(ast) の時点で Body が []
  • JsonSerializer.Deserialize(json) をしても、JSON どおり Body.Count == 0
    なので、「ノードリストが要素数 0 になる」原因は 100% パーサー側 です。
  1. ParseMethod → ParseMethodBody の流れ
    ParseMethod は正しくメソッド宣言をパースして、最後にこうしています。
    _index++; // メソッドヘッダを進める

// 本体をパース

method.Body.AddRange(ParseMethodBody(nameJa));

つまり、本体はすべて ParseMethodBody の責任です。

  1. ParseMethodBody がやっていること
    貼っていただいた ParseMethodBody をざっくり言い換えると:
while (_index < _lines.Length)
{
    line = _lines[_index].Trim();

    if (line == "△") メソッド終端として break;

    if (line.StartsWith("/**")) {
        // ローカル変数のJavadoc → ParseLocalVariableDeclaration(javadoc)
    }

    if (line.StartsWith("〇") && line.Contains("の間は繰り返す")) {
        // while 文扱い
    }

    if (line.StartsWith("〇int") && line.Contains("繰り返す")) {
        // for 文扱い(中の1行だけ特別処理)
    }

    if (line.StartsWith("□バブルソートする")) { ... }

    if (line.StartsWith("□コンソール表示する")) { ... }

    _index++;
}

このどれにもヒットしなければ、単に _index++ してスキップして終わり、
stmts は最後まで空のまま返されます。

  1. どこでヒットが落ちているのか(メインメソッドを例に)
    メインメソッドの本体はこの部分です:
        /** array */
        ・int[] 配列 = 15,13,9,6,4,1
       
        /** index */
        □バブルソートする(配列)

        〇int i=0,i<配列.Length,i++ 繰り返す
         □コンソール表示する(配列[i])
        〇ここまで

これに対して ParseMethodBody は本来こう動くはずです。

  • /** array */
    → StartsWith("/**") にヒット
    → ParseJavadoc() → その直後の行を ParseLocalVariableDeclaration
    → stmts.Add(LocalVariableDeclaration) されるはず
  • □バブルソートする(配列)
    → StartsWith("□バブルソートする") にヒット
    → InvocationExpression を生成して stmts.Add(...)
  • 〇int i=0,i<配列.Length,i++ 繰り返す
    → StartsWith("〇int") && Contains("繰り返す") にヒット
    → ParseForStatement → forStmt.Body に ExpressionStatement(Console.WriteLine(...))
    → stmts.Add(forStmt)
    つまり、本来なら Body は 3 つ以上の Statement で埋まるはず です。
    それが空になっているということは、
  • ParseLocalVariableDeclaration(javadoc) が null を返している
  • ParseWhileStatement(line) / ParseForStatement(line) / ParseConsoleWriteLineElement(...) もパターンマッチに失敗している
  • その結果、どの if でも stmts.Add(...) が呼ばれない
    のどれか(あるいくつか)です。
  1. 特に怪しいポイント
    今の情報だけで「確実にここがバグ」と言えるところを挙げると:
  • ParseLocalVariableDeclaration(javadoc) が null を返している可能性が高い
  • そうだとするとローカル変数宣言が一切 AST に入らない
  • その結果、/** array / / /* index */ が丸ごと捨てられている
  • 行パターンの判定が Re:Mind 実際の文字列と微妙にズレている
  • 全角・半角、カンマやスペースの数、< と < など
  • 正規表現や StartsWith の文字列が厳しすぎると一気に全部すり抜けます
  • ParseForStatement の中で _index を進めすぎたり、戻さなかったりしている
  • ただし、これだけではメインメソッドの Body が完全に空になる説明にはならないので、
    「ローカル変数とバブルソート呼び出しが AST に出ていない」ことから、
    やはり 1・2 が本丸だと思います。
  1. どう切り分けるといいか(デバッグ方針)
    ParseMethodBody に 3 行ほどログを入れると一気に原因が見えます。
private IEnumerable<Statement> ParseMethodBody(string methodNameJa)
{
    var stmts = new List<Statement>();

    Console.WriteLine($"[ParseMethodBody] method={methodNameJa}");

    while (_index < _lines.Length)
    {
        var raw = _lines[_index];
        var line = raw.Trim();

        Console.WriteLine($"  line[{_index}]: \"{raw}\" → Trim: \"{line}\"");

        if (line == "△")
        {
            Console.WriteLine("  -> メソッド終端");
            _index++;
            break;
        }

        if (line.StartsWith("/**"))
        {
            Console.WriteLine("  -> ローカルJavadoc検出");
            var javadoc = ParseJavadoc();
            var varStmt = ParseLocalVariableDeclaration(javadoc);
            Console.WriteLine($"    ParseLocalVariableDeclaration => {(varStmt == null ? "null" : "OK")}");
            if (varStmt != null)
            {
                stmts.Add(varStmt);
                continue;
            }
            else
            {
                continue;
            }
        }

        // 以下の if でも同様にログを入れていく
        ...
    }

    Console.WriteLine($"[ParseMethodBody] stmts.Count = {stmts.Count}");
    return stmts;
}

これで、

  • そもそもどの行が ParseMethodBody に見えているか
  • どの if に入っているか/入っていないか
  • ParseLocalVariableDeclaration などの戻り値が null かどうか
    が一目で分かります。
  1. 直感的なまとめ
  • デシリアライズは正しい。空なのは JSON 側の Body がそもそも空だから。
  • JSON の Body が空なのは、ParseMethodBody がどの行も Statement として認識できていないから。
  • 特に、
  • ローカル変数(ParseLocalVariableDeclaration)
  • □バブルソートする(配列) のマッチ
  • 〇int i=0,...繰り返す のマッチ
    あたりが実際にヒットしていないのが原因と考えられる。

もしよければ、次は

  • ParseLocalVariableDeclaration
  • ParseConsoleWriteLineElement
  • ParseForStatement
    あたりの実装を部分的に見せてもらえれば、
    「この正規表現(または StartsWith の文字列)がズレているので、こう直すと Body が埋まる」というところまで具体的に詰められます。

第9ステップの評価

途中のログ出力を追記してくださいはのちに対応することとなるのですが、要求された生成済のコードの再確認に引き続き進行します。

おわりに

第9ステップは以上です。次回の方向性は上記記載のとおりです。

生成レスポンスセクションの内容は生成AIが生成したものです。引用されているソースコードは完全には動作しない状態が含まれます。

  1. Microsoft Copilotで日本語トランスコンパイラ言語 Re:Mindをいじりたおす - Qiita
    「Microsoft Copilotで日本語トランスコンパイラ言語 Re:Mindをいじりたおす」の記事リストです。(ステップ降順)
    ステップ26 ターゲット言語JavaとVB.NETへの展開
    ステップ25 ターゲット言語C#へのASTからの生成処理
    ステップ24 ASTを生成するサンプル実装をC#で生成
    ステップ23 マッピングルール生成
    ステップ22 AST(抽象構文木)風の構造化表示
    ステップ21 バブルソート 実行可能形式 ターゲットJava C# VB.NET
    ステップ20 二分探索法 実行可能形式 ターゲットJava C# VB.NET
    ステップ19 線形探索法 実行可能形式 ターゲットJava C# VB.NET
    ステップ18 バブルソート 1案件づつやりなおし
    ステップ17 二分探索法 1案件づつやりなおし
    ステップ16 線形探索法 1案件づつやりなおし
    ステップ15 3つの定番アルゴリズムソースの忘却原因調査
    ステップ14 バブルソートソースの忘却発生再確認
    ステップ13 二分探索法ソースの忘却発生
    ステップ12 線形探索法の6言語変換
    ステップ11 バージョン情報付きの3定番アルゴリズムソースのフィードバック修正
    ステップ10 バージョン情報付きの3定番アルゴリズムソースのフィードバック修正
    ステップ9 バージョン情報付きで3定番アルゴリズムソースの生成
    ステップ8 バージョニングが可能か確認
    ステップ7 Re:Mindの詳しい言語仕様の学習結果をバブルソートで確認
    ステップ6 Re:Mindの詳しい言語仕様の学習結果を二分探索法で確認
    ステップ5 Re:Mindの詳しい言語仕様の学習結果を線形探索法で確認
    ステップ4 Re:Mindの詳しい言語仕様の学習成果を保持するスコープを確認
    ステップ3 Re:Mindの詳しい言語仕様を学習できる方法を確認
    ステップ2 Re:Mindの言語仕様の認知度を確認
    ステップ1 Re:Mindの認知度を確認

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?