はじめに
生成AIに日本語トランスコンパイラ言語 Re:Mindの実装言語仕様を読み込ませて、いくつかの抽象度レベルのプロンプトからトランスコンパイラ的な動きをさせてみたところ、Re:Mindっぽい仕様のASTパーサのテンプレと3言語(Java,C#,VB.NET)のコードジェネレータのテンプレを生成してもらえた1ので、本記事はその検証の第10ステップです。
Microsoft Copilot
生成AIはWindows11アプリケーションのMicrosoft Copilotとします。モードは「Smart(GPT5.1)」。サインインした状態で使用します。第6ステップの後にはサインアウトがあります。
第10ステップ
第6ステップ以降はプロンプト10240文字以内の制限内での生成済ソースコードの分割提示しており、今回もその続きです。今回で再提示はいったん完了します。
プロンプト
残りの関数を10240文字の制限内で入るだけ、下記に示します。
private LocalVariableDeclaration? ParseLocalVariableDeclaration(JavadocComment javadoc)
{
if (_index >= _lines.Length) return null;
var line = _lines[_index].Trim();
// 例: ・int[] 配列 = 15,13,9,6,4,1
// ・int 外側 = 0
var m = Regex.Match(line, @"・(?<type>[a-zA-Z\[\]]+)\s+(?<nameJa>\S+)\s*=\s*(?<rhs>.+)");
if (!m.Success)
{
_index++;
return null;
}
var type = m.Groups["type"].Value;
var nameJa = m.Groups["nameJa"].Value;
var rhs = m.Groups["rhs"].Value.Trim();
Expression initializer;
if (rhs.Contains(","))
{
// 配列のようなリテラル(今回: int[] 配列)
var arr = new ArrayLiteralExpression
{
ElementType = "int"
};
var nums = rhs.Split(',').Select(s => s.Trim());
foreach (var n in nums)
{
if (int.TryParse(n, out int v))
{
arr.Elements.Add(new LiteralExpression { Value = v });
}
}
initializer = arr;
}
else if (int.TryParse(rhs, out int v))
{
initializer = new LiteralExpression { Value = v };
}
else
{
// 簡易対応
initializer = new IdentifierExpression { NameJa = rhs, NameEn = MapIdentifierName(rhs) };
}
var decl = new LocalVariableDeclaration
{
Javadoc = javadoc,
Type = type,
NameJa = nameJa,
NameEn = MapIdentifierName(nameJa),
Initializer = initializer
};
_index++;
return decl;
}
private WhileStatement ParseWhileStatement(string line)
{
// 〇外側 < 配列.Length の間は繰り返す
var condPart = line.TrimStart('〇').Replace("の間は繰り返す", "").Trim();
var cond = ParseBinaryCondition(condPart);
var ws = new WhileStatement
{
Condition = cond
};
_index++;
// 本体を読み取る(〇ここまでまで)
while (_index < _lines.Length)
{
var cur = _lines[_index].Trim();
if (cur.StartsWith("〇ここまで"))
{
_index++;
break;
}
if (cur.StartsWith("/**"))
{
var j = ParseJavadoc();
var decl = ParseLocalVariableDeclaration(j);
if (decl != null)
{
ws.Body.Add(decl);
continue;
}
}
else if (cur.StartsWith("〇") && cur.Contains("の間は繰り返す"))
{
// 内側 while
var innerWhile = ParseWhileStatement(cur);
ws.Body.Add(innerWhile);
continue;
}
else if (cur.StartsWith("◇配列[内側] > 配列[内側 + 1] の場合"))
{
var ifStmt = ParseIfSwapBlock();
ws.Body.Add(ifStmt);
continue;
}
else if (cur.StartsWith("□外側++"))
{
var expr = new UnaryExpression
{
Operator = "++",
Operand = new IdentifierExpression
{
NameJa = "外側",
NameEn = "outer"
}
};
ws.Body.Add(new ExpressionStatement { Expression = expr });
_index++;
continue;
}
else if (cur.StartsWith("□内側++"))
{
var expr = new UnaryExpression
{
Operator = "++",
Operand = new IdentifierExpression
{
NameJa = "内側",
NameEn = "inner"
}
};
ws.Body.Add(new ExpressionStatement { Expression = expr });
_index++;
continue;
}
else
{
_index++;
}
}
return ws;
}
private ForStatement ParseForStatement(string line)
{
// 〇int i=0,i<配列.Length,i++ 繰り返す
var body = line.TrimStart('〇').Replace("繰り返す", "").Trim();
// int i=0,i<配列.Length,i++
var parts = body.Split(',');
// 初期化: int i=0
var initPart = parts[0].Trim();
var mInit = Regex.Match(initPart, @"int\s+(?<name>\w+)\s*=\s*(?<value>\d+)");
var initializer = new LocalVariableDeclaration
{
Type = "int",
NameJa = mInit.Groups["name"].Value,
NameEn = mInit.Groups["name"].Value,
Initializer = new LiteralExpression { Value = int.Parse(mInit.Groups["value"].Value) }
};
// 条件: i<配列.Length
var condPart = parts[1].Trim();
var condition = ParseBinaryCondition(condPart);
// 反復: i++
var iterPart = parts[2].Trim();
var iterExpr = new UnaryExpression
{
Operator = "++",
Operand = new IdentifierExpression
{
NameJa = "i",
NameEn = "i"
}
};
return new ForStatement
{
Initializer = initializer,
Condition = condition,
Iterator = iterExpr
};
}
private Expression ParseBinaryCondition(string condPart)
{
// かなり固定フォーマットに依存: 例 「外側 < 配列.Length」/「内側 < 配列.Length - 外側 - 1」/「i<配列.Length」
condPart = condPart.Replace(" ", "");
// 不等号で切る簡易版
if (condPart.Contains("<="))
{
var parts = condPart.Split(new[] { "<=" }, StringSplitOptions.None);
return new BinaryExpression
{
Operator = "<=",
Left = ParseSimpleExpression(parts[0]),
Right = ParseSimpleExpression(parts[1])
};
}
if (condPart.Contains("<"))
{
var parts = condPart.Split('<');
return new BinaryExpression
{
Operator = "<",
Left = ParseSimpleExpression(parts[0]),
Right = ParseSimpleExpression(parts[1])
};
}
throw new NotSupportedException("この条件は簡易パーサでは未対応です: " + condPart);
}
private Expression ParseSimpleExpression(string text)
{
// 配列.Length - 外側 - 1 のようなものも来るので、ここも簡易。
if (int.TryParse(text, out var n))
{
return new LiteralExpression { Value = n };
}
if (text.Contains("配列.Length"))
{
// 配列.Length - 外側 - 1 など
// ここでは BinaryExpression ネストを手書き
// 今回用途に特化
if (text == "配列.Length")
{
return new MemberAccessExpression
{
Expression = new IdentifierExpression
{
NameJa = "配列",
NameEn = "array"
},
MemberName = "Length"
};
}
// 配列.Length-外側-1
var replaced = text.Replace("配列.Length", "L");
var tokens = replaced.Split('-'); // L,外側,1
Expression expr = new MemberAccessExpression
{
Expression = new IdentifierExpression
{
NameJa = "配列",
NameEn = "array"
},
MemberName = "Length"
};
for (int i = 1; i < tokens.Length; i++)
{
Expression right;
if (int.TryParse(tokens[i], out var vi))
{
right = new LiteralExpression { Value = vi };
}
else
{
right = new IdentifierExpression
{
NameJa = tokens[i],
NameEn = MapIdentifierName(tokens[i])
};
}
expr = new BinaryExpression
{
Operator = "-",
Left = expr,
Right = right
};
}
return expr;
}
// 単純識別子
return new IdentifierExpression
{
NameJa = text,
NameEn = MapIdentifierName(text)
};
}
生成レスポンス
結論から言うと:
- デシリアライズがノードを「消している」のではなく
- そもそもシリアライズ前の AST の時点で Body が空 ([])
- つまり原因は パーサー側(ParseMethodBody 周辺)にあり、JSON のプロパティ名対応やデシリアライズの問題ではない
です。
もしよければ、次の一歩として、
- ParseJavadoc の実装
- 1 回だけログ入り ParseMethodBody を走らせた結果
あたりを一緒に見て、「どの行でマッチがすり抜けているか」をピンポイントで特定していきませんか。
そこが分かれば、Body に Statement が入り始めて、一気に Re:Mind → C# が「フル AST モード」で動き出します。
プロンプト2
ParseJavadoc()の全文です。
private JavadocComment ParseJavadoc()
{
var j = new JavadocComment();
_index++; // /** 行を飛ばす
while (_index < _lines.Length)
{
var line = _lines[_index].Trim();
if (line.StartsWith("*/"))
{
_index++;
break;
}
// * Program
if (line.StartsWith("*"))
{
var content = line.TrimStart('*').Trim();
if (content.StartsWith("@param"))
{
// @param 引数 args
var parts = content.Split(new[] { ' ' }, 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;
}
生成レスポンス2
ParseJavadoc() を見せていただきありがとうございます。
この関数自体は 正常に動いています。
そして、ここまでの全コードを総合すると、Body が空になる原因は ParseJavadoc() ではなく、ParseMethodBody() の行判定ロジックにあります。
ただし、ParseJavadoc() にも 副作用として Body が空になる原因を引き起こす可能性があるポイント が 1 つだけあります。
🎯 結論
ParseJavadoc() 自体は正しく動いているが、
ParseJavadoc() が _index を 1 行多く進めてしまうと、
直後のローカル変数行(・)をスキップしてしまう可能性がある。
つまり:
/** array */
・int[] 配列 = 15,13,9,6,4,1
この 2 行をパースするときに、
- ParseJavadoc() が */ の次の行まで進む
- その後 ParseLocalVariableDeclaration() が呼ばれる
- しかし _index がすでに「・」行を通り過ぎていると、ローカル変数がパースされない
- 結果として Body に Statement が追加されない
という現象が起きます。
第10ステップの評価
この生成レスポンスの後も、詳しいソースコードの評価から正規表現の実装の調整などが繰り返されたのですが、かなり長大とりますのでそれは割愛します。
おわりに
第10ステップは以上です。次回はログ出力機能を実装したところから開示いたします。
生成レスポンスセクションの内容は生成AIが生成したものです。引用されているソースコードは完全には動作しない状態が含まれます。
-
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の認知度を確認 ↩