IronRuby 是 .NET 平台上的 Ruby 實作,利用 DLR (Dynamic Language Runtime) 執行。雖然 Ruby 本身支援反射可取得方法名稱,但無法直接獲取方法參數清單與參數名稱。本文展示如何透過 IronRuby 解析 Ruby 腳本,提取類別名稱、方法名稱與參數資訊,並以樹狀圖形化呈現。
引擎初始化建立 ScriptEngine,提供載入檔案/目錄、取得類別名稱與詳細資訊的方法:
public static class RubyScriptEngine
{
private static readonly ScriptEngine engine = Ruby.CreateEngine();
public static bool InitRelativeFiles(string directory) { /* 載入目錄下所有 .rb 檔 */ }
public static bool InitRelativeFile(string fileName) { /* 載入單一 .rb 檔 */ }
public static IList<string> GetClassNames() { /* 取得全域變數名稱作為類別名 */ }
public static IList<ClassItem> GetClassesInfos() { /* 批次取得所有類別資訊 */ }
public static ClassItem GetClassInfo(string className, params object[] parameters)
{
RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className);
dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters);
ClassItem classItem = new ClassItem(className);
foreach (string memberName in engine.Operations.GetMemberNames(instance))
{
RubyMethodInfo methodInfo = rubyClass.GetMethod(memberName) as RubyMethodInfo;
if (methodInfo == null) continue;
MethodItem methodItem = new MethodItem(memberName, className);
RubyArray parameterArray = methodInfo.GetRubyParameterArray();
SimpleAssignmentExpression[] expressions = methodInfo.Parameters.Optional;
for (int i = 0; i < parameterArray.Count; i++)
{
RubyArray vas = parameterArray[i] as RubyArray;
string type = vas[0].ToString();
string name = vas[1].ToString();
ParameterItem parameterItem = new ParameterItem(name);
if (type == "rest") { parameterItem.DefaultName = "*" + name; parameterItem.Description = RubyResource.ArrayParamDesc; }
else if (type == "opt") { /* 處理預設值參數 */ }
else if (type == "block") { parameterItem.DefaultName = "&" + name; parameterItem.Description = RubyResource.BlockParamDesc; }
else { parameterItem.DefaultName = name; }
methodItem.Parameters.Add(parameterItem);
}
classItem.Methods.Add(methodItem);
}
return classItem;
}
}
主要方法 CreateImage 與 CreateCurrentClassBlock:前者負責繪製 Bitmap,後者將資料模型 (ClassItem) 轉換為座標模型 (ClassBlock),自動計算區塊位置與連線。
public Bitmap CreateImage(ClassItem classItem, Font font)
{
if (classItem == null || font == null) return null;
ClassBlock classBlock = CreateCurrentClassBlock(classItem);
AddLinesAndBlockTexts(classBlock, font);
Bitmap bitmap = new Bitmap(3 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH, this.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.Clear(Color.White);
Pen pen = new Pen(Color.Blue, 2);
foreach (Line line in this.Lines)
{
g.DrawLine(pen, line.X1, line.Y1, line.X2, line.Y2);
if (line.HasArrow) DrawArrowHead(g, CreateArrowPoints(...));
}
foreach (BlockText content in this.Contents)
g.DrawString(content.Content, font, Brushes.Black, content.StartX, content.StartY);
}
return bitmap;
}
若僅需呼叫單一類別方法,.NET 4.0 的 dynamic 更簡潔:
dynamic globals = engine.Runtime.Globals;
dynamic obj = globals.PersonClass.@new();
obj.nonArgsMethod();
本文採用 IronRuby API 係為通用性處理,支援依類別名稱與建構參數動態解析任意結構。
原始碼下載:IronRuby Ruby 類別樹狀結構原始碼