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のC#コード生成が動作するまで(ステップ2)

1
Posted at

はじめに

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

Microsoft Copilot

生成AIはWindows11アプリケーションのMicrosoft Copilotとします。モードは「Smart(GPT5.1)」。サインインした状態で使用します。

第2ステップ

「Re:MindのASTパーサが動作するまで」の12ステップ2は非常に長きにわたるもので、筆者も生成AIと実装コードがらみで根気よく向き合ったのはこれが初めてでした。こちらの12ステップでいったんC#のソースコードが生成されるようにはなりましたので、以降はC#コードジェネレータが正常動作することを目指しています。

プロンプト

下記のようにCSharpCodeGeneratorクラスにBuildAliasMapとGenerateInvocationを追記しましたが GenerateInvocation内でGenerateArgs、expr.MemberName、expr.Target.NameEnが未定義エラーと なるようです。

using System;
using System.Linq;
using ReMindAst;

namespace ReMindBackend
{
    public class CSharpCodeGenerator
    {
        private readonly IndentWriter _w = new();

        public string Generate(CompilationUnit cu)
        {
            // using
            foreach (var imp in cu.Imports)
            {
                _w.WriteLine($"using {imp.Name};");
            }
            _w.WriteLine();

            // namespace
            foreach (var ns in cu.Namespaces)
            {
                _w.WriteLine($"namespace {ns.Name}");
                _w.WriteLine("{");
                _w.Indent();

                foreach (var cls in ns.Classes)
                {
                    GenerateClass(cls);
                }

                _w.Unindent();
                _w.WriteLine("}");
            }

            return _w.ToString();
        }
        
		private Dictionary<string, AliasClass> BuildAliasMap(CompilationUnit cu)
		{
		    var map = new Dictionary<string, AliasClass>();

		    foreach (var import in cu.Imports)
		    {
		        foreach (var alias in import.AliasClasses)
		        {
		            map[alias.OriginalName] = alias;
		        }
		    }

		    return map;
		}
		
        private void GenerateClass(ClassDeclaration cls)
        {
            // XML コメント
            if (cls.Javadoc != null)
                WriteXmlComment(cls.Javadoc);

            // public class ProgramType
            var mods = string.Join(" ", cls.Modifiers);
            _w.WriteLine($"{mods} class {cls.NameEn}");
            _w.WriteLine("{");
            _w.Indent();

            foreach (var m in cls.Methods)
            {
                GenerateMethod(m);
                _w.WriteLine();
            }

            _w.Unindent();
            _w.WriteLine("}");
        }

        private void GenerateMethod(MethodDeclaration m)
        {
            if (m.Javadoc != null)
                WriteXmlComment(m.Javadoc);

            var mods = string.Join(" ", m.Modifiers);
            var paramList = string.Join(", ",
                m.Parameters.Select(p => $"{MapType(p.Type)} {p.NameEn}"));

            _w.WriteLine($"{mods} {MapType(m.ReturnType)} {m.NameEn}({paramList})");
            _w.WriteLine("{");
            _w.Indent();

            foreach (var stmt in m.Body)
            {
                GenerateStatement(stmt);
            }

            _w.Unindent();
            _w.WriteLine("}");
        }

        private void GenerateStatement(Statement stmt)
        {
            switch (stmt)
            {
                case LocalVariableDeclaration v:
                    GenerateLocalVariable(v);
                    break;

                case ExpressionStatement es:
                    _w.WriteLine($"{GenerateExpression(es.Expression)};");
                    break;

                case ForStatement fs:
                    GenerateFor(fs);
                    break;

                case WhileStatement ws:
                    GenerateWhile(ws);
                    break;

                case IfStatement ifs:
                    GenerateIf(ifs);
                    break;

                case AssignmentStatement ass:
                    _w.WriteLine($"{GenerateExpression(ass.Left)} = {GenerateExpression(ass.Right)};");
                    break;

                default:
                    _w.WriteLine("// 未対応ステートメント");
                    break;
            }
        }

        private void GenerateLocalVariable(LocalVariableDeclaration v)
        {
            if (v.Javadoc != null)
                WriteXmlComment(v.Javadoc);

            if (v.Initializer != null)
            {
                _w.WriteLine($"{MapType(v.Type)} {v.NameEn} = {GenerateExpression(v.Initializer)};");
            }
            else
            {
                _w.WriteLine($"{MapType(v.Type)} {v.NameEn};");
            }
        }

        private void GenerateFor(ForStatement fs)
        {
            var init = $"{MapType(fs.Initializer.Type)} {fs.Initializer.NameEn} = {GenerateExpression(fs.Initializer.Initializer)}";
            var cond = GenerateExpression(fs.Condition);
            var iter = GenerateExpression(fs.Iterator);

            _w.WriteLine($"for ({init}; {cond}; {iter})");
            _w.WriteLine("{");
            _w.Indent();

            foreach (var s in fs.Body)
                GenerateStatement(s);

            _w.Unindent();
            _w.WriteLine("}");
        }

        private void GenerateWhile(WhileStatement ws)
        {
            _w.WriteLine($"while ({GenerateExpression(ws.Condition)})");
            _w.WriteLine("{");
            _w.Indent();

            foreach (var s in ws.Body)
                GenerateStatement(s);

            _w.Unindent();
            _w.WriteLine("}");
        }

        private void GenerateIf(IfStatement ifs)
        {
            _w.WriteLine($"if ({GenerateExpression(ifs.Condition)})");
            _w.WriteLine("{");
            _w.Indent();

            foreach (var s in ifs.ThenBody)
                GenerateStatement(s);

            _w.Unindent();
            _w.WriteLine("}");

            if (ifs.ElseBody.Any())
            {
                _w.WriteLine("else");
                _w.WriteLine("{");
                _w.Indent();

                foreach (var s in ifs.ElseBody)
                    GenerateStatement(s);

                _w.Unindent();
                _w.WriteLine("}");
            }
        }

        private string GenerateExpression(Expression expr)
        {
            return expr switch
            {
                IdentifierExpression id => id.NameEn,
                LiteralExpression lit => lit.Value is string s ? $"\"{s}\"" : lit.Value!.ToString()!,
                BinaryExpression bin => $"{GenerateExpression(bin.Left)} {bin.Operator} {GenerateExpression(bin.Right)}",
                UnaryExpression un => $"{GenerateExpression(un.Operand)}{un.Operator}",
                MemberAccessExpression ma => $"{GenerateExpression(ma.Expression)}.{ma.MemberName}",
                ElementAccessExpression ea => $"{GenerateExpression(ea.ArrayExpression)}[{GenerateExpression(ea.IndexExpression)}]",
                InvocationExpression inv => $"{GenerateExpression(inv.Target)}({string.Join(", ", inv.Arguments.Select(GenerateExpression))})",
                ArrayLiteralExpression arr => $"new {arr.ElementType}[] {{ {string.Join(", ", arr.Elements.Select(GenerateExpression))} }}",
                _ => "/* 未対応式 */"
            };
        }

		private string GenerateInvocation(InvocationExpression expr, Dictionary<string, AliasClass> aliasMap)
		{
		    // expr.Target は IdentifierExpression または MemberAccessExpression
		    if (expr.Target is IdentifierExpression id)
		    {
		        if (aliasMap.TryGetValue(id.NameJa, out var alias))
		        {
		            // メンバー名の変換
		            var member = alias.Members
		                .FirstOrDefault(m => m.OriginalName == expr.MemberName);

		            var memberName = member?.TranspiledName ?? expr.MemberName;

		            return $"{alias.TranspiledName}.{memberName}({GenerateArgs(expr.Arguments)})";
		        }
		    }

		    // fallback
		    return $"{expr.Target.NameEn}({GenerateArgs(expr.Arguments)})";
		}

        private void WriteXmlComment(JavadocComment j)
        {
            if (!string.IsNullOrEmpty(j.Summary))
                _w.WriteLine($"/// <summary>{j.Summary}</summary>");

            foreach (var p in j.Params)
                _w.WriteLine($"/// <param name=\"{p.NameEn}\">{p.NameJa}</param>");
        }

        private string MapType(string t)
        {
            return t switch
            {
                "string" => "string",
                "string[]" => "string[]",
                "int" => "int",
                "int[]" => "int[]",
                "void" => "void",
                _ => t
            };
        }
    }
}

生成レスポンス

ここは 完全に原因が特定できていて、しかも綺麗に直せるポイント です。
あなたが遭遇している未定義エラーは、次の 3 つが理由です。

🎯 未定義エラーの原因(3つ)
① GenerateArgs が存在しない
→ これはまだ実装していない補助関数なので当然エラーになります。
② expr.MemberName が存在しない
→ あなたの InvocationExpression 型には MemberName プロパティが存在しない ためです。
実際の AST では:

  • InvocationExpression.Target- が
  • IdentifierExpression(関数呼び出し)
  • または MemberAccessExpression(オブジェクト.メソッド呼び出し)
    になっており、
    メソッド名は Target の中に入っています。
    つまり:

□コンソール.一行表示する(引数2)
は AST では:

InvocationExpression
  Target = MemberAccessExpression
      Expression = IdentifierExpression("コンソール")
      MemberName = "一行表示する"
  Arguments = [...]

なので、expr.MemberName ではなく:

((MemberAccessExpression)expr.Target).MemberName

と読む必要があります。
③ expr.Target.NameEn が存在しない
→ expr.Target は Expression 型なので、
IdentifierExpression なら NameEn があるが、
MemberAccessExpression には NameEn がありません。
つまり、ここも Target の型を見て分岐する必要があります。

🎉 では、どう直すか(完全版を提示します)
以下の 3 点を修正します。

✅ 修正1:GenerateArgs を追加する

private string GenerateArgs(IEnumerable<Expression> args)
{
    return string.Join(", ", args.Select(GenerateExpression));
}

✅ 修正2:InvocationExpression のメソッド名を正しく取得する

private string GetInvocationMemberName(InvocationExpression expr)
{
    return expr.Target switch
    {
        MemberAccessExpression ma => ma.MemberName,
        IdentifierExpression id => id.NameEn, // fallback
        _ => ""
    };
}

✅ 修正3:GenerateInvocation を正しく書き直す

private string GenerateInvocation(InvocationExpression expr, Dictionary<string, AliasClass> aliasMap)
{
    // ターゲットが MemberAccessExpression の場合(例:コンソール.一行表示する)
    if (expr.Target is MemberAccessExpression ma)
    {
        // 左側(コンソール)
        if (ma.Expression is IdentifierExpression id &&
            aliasMap.TryGetValue(id.NameJa, out var alias))
        {
            // メソッド名(例:一行表示する)
            var originalMember = ma.MemberName;

            // マッピングを探す
            var member = alia

✨ まとめ

エラー 原因 対策
GenerateArgs 未定義 補助関数未実装 GenerateArgs を追加
expr.MemberName 未定義 InvocationExpression に MemberName がない MemberAccessExpression から取得
expr.Target.NameEn 未定義 Target が Identifier とは限らない 型分岐して処理

mylifewithviolinさん、ここまで来ると Re:Mind → AST → C# のフルパイプラインが完成目前 です。
次は:

  • IfSwap の中の 配列[内側] → array[inner] の変換
  • Java / VB.NET 生成器の追加
  • Imports の構文拡張
    など、どんどん広げられます。
    次はどこを進めましょう。

第2ステップの評価

解決が確認される前に次のステップをご提案するのはどうなんだろうと思いつつ改修結果を確認しました。書き換えたGenerateExpressionは何度も呼ばれるようになりましたが、まだstatic void consoleOutの定義が出力されないようでした。

おわりに

第2ステップは以上です。次回の方向性は上記の結果の提示からとなります。

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

  1. Microsoft Copilotで日本語トランスコンパイラ言語 Re:Mindをいじりたおす - Qiita
    「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の認知度を確認

  2. Copilotが生成した日本語トランスコンパイラ言語 Re:MindのASTパーサが動作するまで - Qiita
    「Re:MindのASTパーサが動作するまで」の記事リストです。(ステップ降順)
    ステップ12 いったん生成
    ステップ11
    ステップ10 生成済コードの伝達完了
    ステップ9
    ステップ8
    ステップ7
    ステップ6 生成済コードの伝達開始
    ステップ5
    ステップ4 Program.csが動作
    ステップ3
    ステップ2
    ステップ1 検証環境構築 2

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?