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パーサが動作するまで(ステップ11)

1
Posted at

はじめに

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

Microsoft Copilot

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

第11ステップ

第6ステップ以降はプロンプト10240文字以内の制限内での生成済ソースコードの分割提示しており、第10ステップでその提示が完了した後についに動きがありました。第10ステップから今回の第11ステップの間には、正規表現の実装の調整などが繰り返されたのですが、かなり長大とりますのでそれは割愛した上で、ログ出力機能を実装したところから開示いたします。

プロンプト

ParseMethodBodyメソッドに下記のようなログ機能を追記して実行した結果を ソースコードの下に貼り付けました。

ParseMethodBody
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 = NormalizeLine(raw);
        Console.WriteLine($"  line[{_index}]: \"{raw}\" → \"{line}\"");

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

        // ローカル変数のJavadoc
        if (line.StartsWith("/**"))
        {
            Console.WriteLine($"  line[{_index}]: \"{raw}\" → \"{line}\"");
            var javadoc = ParseJavadoc();
            // 直後の行がローカル変数になる前提
            var varStmt = ParseLocalVariableDeclaration(javadoc);
            Console.WriteLine($"    ParseLocalVariableDeclaration => {(varStmt == null ? "null" : "OK")}");
            if (varStmt != null)
            {
                stmts.Add(varStmt);
                continue;
            }
            else
            {
                continue;
            }
        }

        // 〇外側 < 配列.Length の間は繰り返す → while
        // 記号ゆらぎ(〇 / ○ / ◯)を許容
        if (Regex.IsMatch(line, @"^[〇○◯].*の間は繰り返す"))
        {
            Console.WriteLine("  -> while検出");
            var whileStmt = ParseWhileStatement(line);
            stmts.Add(whileStmt);
            continue;
        }


        // 〇int i=0,i<配列.Length,i++ 繰り返す → for
        if (Regex.IsMatch(line, @"^[〇○◯]\s*int") && line.Contains("繰り返す"))
        {
            Console.WriteLine("  -> for検出");
            var forStmt = ParseForStatement(line);
            stmts.Add(forStmt);
            _index++;
            var bodyLine = NormalizeLine(_lines[_index]);
            Console.WriteLine($"    for-body line: \"{bodyLine}\"");
            var expr = ParseConsoleWriteLineElement(bodyLine);
            if (expr != null)
            {
                Console.WriteLine("    -> for-body expr OK");
                forStmt.Body.Add(new ExpressionStatement { Expression = expr });
            }
            while (_index < _lines.Length &&
                   !Regex.IsMatch(NormalizeLine(_lines[_index]), @"^[〇○◯]ここまで"))
            {
                _index++;
            }
            _index++;
            continue;
        }


        // 呼び出し1行: □バブルソートする(配列)
        if (Regex.IsMatch(line, @"^[□■]\s*バブルソートする"))
        {
            Console.WriteLine("  -> バブルソート呼び出し検出");
            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 (Regex.IsMatch(line, @"^[□■]\s*コンソール表示する"))
        {
            Console.WriteLine("  -> コンソール表示する検出");
            var expr = ParseConsoleWriteLineElement(line);
            if (expr != null)
            {
                stmts.Add(new ExpressionStatement { Expression = expr });
            }
            _index++;
            continue;
        }

        _index++;
    }
    Console.WriteLine($"[ParseMethodBody] stmts.Count = {stmts.Count}");
    return stmts;
}
log
[ParseMethodBody] method=メイン
2
  line[12]: "            /** array */"  "/** array */"
    ParseLocalVariableDeclaration => null
2
  line[27]: "          /** i */"  "/** i */"
    ParseLocalVariableDeclaration => null
  line[50]: "            □コンソール.一行表示する(引数2)"  "□コンソール.一行表示する(引数2)"
  line[51]: "        △"  "△"
[ParseMethodBody] stmts.Count = 0

生成レスポンス

ログを見た瞬間に 原因が完全に特定できました。
これはもう「一点突破」で直ります。

🎯 結論:ParseMethodBody が “メインメソッドの本体行に到達していない”
ログを見てください。

log
[ParseMethodBody] method=メイン
2
  line[12]: "            /** array */"  "/** array */"
    ParseLocalVariableDeclaration => null
2
  line[27]: "          /** i */"  "/** i */"
    ParseLocalVariableDeclaration => null
  line[50]: "            □コンソール.一行表示する(引数2)"  "□コンソール.一行表示する(引数2)"
  line[51]: "        △"  "△"
[ParseMethodBody] stmts.Count = 0

ここで決定的なのは:

❗ メインメソッドの本体(・int[] 配列 = ... や □バブルソートする(配列) や 〇int i=0...)が一切ログに出ていない
つまり:
✔ ParseMethodBody がメインメソッドの本体行に到達していない
✔ そのため、どの if にも入らず、stmts.Count = 0
✔ JSON の Body = []
✔ C# コードも空
という流れです。

🔍 なぜ本体行に到達していないのか?
ParseMethod の中を見直すと、こうなっています。

_index++; // メソッドヘッダを進める

// 本体をパース
method.Body.AddRange(ParseMethodBody(nameJa));

ここで _index は「メソッド宣言行の次の行」を指します。
しかし、あなたの Re:Mind ソースはこうなっています:

Re:mind
▽static void メイン(string[] 引数)
    /** array */
    ・int[] 配列 = 15,13,9,6,4,1

つまり、メソッド宣言の次の行は Javadoc です。
ParseMethodBody のログを見ると:

line[12]: "/** array */"

ここまでは正しい。
しかしその後、次に出てくるのが:

line[27]: "/** i */"

つまり:

❗ ParseJavadoc() が「/** array */」の後の「・int[] 配列 = ...」行を読み飛ばしている

🎯 原因は ParseJavadoc() の _index の進め方
ParseJavadoc() はこうなっています:

_index++; // /** 行を飛ばす

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

    if (line.StartsWith("*/"))
    {
        _index++;
        break;
    }

    ...
    _index++;
}

この動きは:

  • /** array */ 行を飛ばす
  • 次の行が * array なら読む
  • 次の行が */ なら _index++ して抜ける
  • その次の行に移動する
    しかし、あなたの Re:Mind ソースは 1 行コメント形式 です:
/** array */

つまり:

  • /** array */ は 1 行で完結している
  • しかし ParseJavadoc() は「複数行コメント」として扱っている
  • そのため _index が 2 行進んでしまう
  • 結果として「・int[] 配列 = ...」行を飛ばしてしまう

🎉 つまり、原因はこれだけです:
👉 ParseJavadoc() が 1 行コメントを 2 行読み飛ばしている

✨ 修正方法(最小修正)
ParseJavadoc() の先頭に 1 行コメント対応 を入れます。

private JavadocComment ParseJavadoc()
{
    var j = new JavadocComment();
    var line = _lines[_index].Trim();

    // ★ 1行コメント対応
    if (line.Contains("*/"))
    {
        // /** summary */ の形式
        var content = line.Replace("/**", "")
                          .Replace("*/", "")
                          .Trim()
                          .TrimStart('*')
                          .Trim();

        if (!string.IsNullOrEmpty(content))
        {
            if (content.StartsWith("@param"))
            {
                var parts = content.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length >= 3)
                {
                    j.Params.Add(new JavadocParam
                    {
                        NameJa = parts[1],
                        NameEn = parts[2]
                    });
                }
            }
            else
            {
                j.Summary = content;
            }
        }

        _index++; // ★ 次の行へ
        return j;
    }

    // ★ ここから先は複数行コメント
    _index++; // /** 行を飛ばす

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

        if (line.StartsWith("*/"))
        {
            _index++;
            break;
        }

        if (line.StartsWith("*"))
        {
            var content = line.TrimStart('*').Trim();

            if (content.StartsWith("@param"))
            {
                var parts = content.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length >= 3)
                {
                    j.Params.Add(new JavadocParam
                    {
                        NameJa = parts[1],
                        NameEn = parts[2]
                    });
                }
            }
            else if (!string.IsNullOrEmpty(content))
            {
                j.Summary = content;
            }
        }

        _index++;
    }

    return j;
}

🎯 この修正で何が起きるか

  • /** array */ を 1 行だけ消費する
  • _index が 正しく「・int[] 配列 = ...」行を指す
  • ParseLocalVariableDeclaration が 初めてヒットする
  • Body に Statement が追加される
  • JSON の Body が埋まる
  • C# コードが生成される

📌 次のステップ
この修正を入れて再実行すると、ログはこう変わるはずです:

line[12]: "/** array */"
    ParseLocalVariableDeclaration => OK
line[13]: "・int[] 配列 = 15,13,9,6,4,1"

そして stmts.Count が 1 以上になります。

mylifewithviolinさん、ここまで来たらもう Re:Mind → AST → C# のパイプラインは完成目前です。
修正後のログを見せていただければ、次のステップ(For / While / If の調整)も一緒に進められます。

第11ステップの評価

今回の生成レスポンスの反映による修正後のログではかなりの進展が見られました。それは第12ステップのプロンプトとして開示いたします。

おわりに

第11ステップは以上です。次回第12ステップで「Re:MindのASTパーサが動作するまで」はいったん完了となります。次回でいったんC#ソースコードの生成に到達することとなります。

生成レスポンスセクションの内容は生成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?