[C#] 使用 C# 重新製作一個語言 - Hello World

2012-10-05


繼續上一篇文章http://www.dotblogs.com.tw/junegoat/archive/2012/09/16/c-sharp-make-a-language-lia-start.aspx
我們如何來進行編譯 sample.lia 的文檔呢?!

因為目前只有print 還有 printline 得部分..

對於 ILGenerator.Emit Method 還不致於太難..



首先我們得先驗證文法 是不是符合我定義的Lia 規範:

/// <summary>

/// 驗證是否符合DTD規範的XML文件

/// </summary>

/// <param name="xmlPath"> 該xml 文檔位置</param>

/// <returns></returns>

public static bool IsValidXML(string xmlPath)

{

    var xmlReaderSettings = new XmlReaderSettings();

    //如果設定為Igonre就會不驗證DTD 指驗證XML 的完整性
    // xmlReaderSettings.DtdProcessing = DtdProcessing.Ignore;
    xmlReaderSettings.DtdProcessing = DtdProcessing.Parse;

    //設定為DTD psrse 
    xmlReaderSettings.ValidationType = ValidationType.DTD;
    xmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings;
    XmlReader reader = XmlReader.Create(xmlPath, xmlReaderSettings);



    try
    {
        while (reader.Read())
        {
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("\r\n" + "----------- Lia Compiler Result -----------" + "\r\n");
        Console.WriteLine("Error! This is not valid Lia gramma >" + ex.Message + "\r\n");
        return false;
    }
    finally
    {
        //記得關閉免得被lock
        reader.Close();
    }
    return true;

}




透過這方式,就可以很簡單去判斷如對方輸入奇怪的字眼並非是我預期的..

譬如,我在Lia sample code 裡面鍵入echo 他並沒有符合我的規範..



2012-09-16_222350



再來 就是 關於 主程式部分







static void Main(string[] args)
{

    //驗證 確保輸入 code source 還有輸出路徑
    if (args.Length != 2)
    {
        Console.WriteLine("\r\n使用方式: Liac.exe  source.xml target.exe");
        return;
    }

    try
    {
        //判斷source 檔案是否真的存在
        if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + args[0]))
        {
            Console.WriteLine("\r\n" + "----------- Lia Compiler Result -----------" + "\r\n");
            Console.WriteLine("Error! I can't find the source >" + args[0] + "\r\n");
            return;
        }





        //判斷文法 是否符合 Lia 規範

        var isValid = IsValidXML(AppDomain.CurrentDomain.BaseDirectory + args[0]);
        if (!isValid)
        {
            return;
        }

        Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory + args[0]);        
        CodeGenerator codeGenerator = new CodeGenerator();

        // 因為只是先測試 print printline 所以都先封裝在一個function 內部測試
        codeGenerator.TestPrintPrintline(args[1], File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + args[0]));

        Console.WriteLine("\r\n" + "----------- Lia Compiler Result -----------" + "\r\n");
        Console.WriteLine("Success! Output File > " + args[1] + "\r\n");
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine(ex.Message);
    }


}




上述程式很簡單,只是驗證 還有處理一些 compile 時候錯誤或是成功訊息提式..

真正的處理部分我都先放在 CodeGenerator 部分, 因為只是先測試 所以我都先封裝在 TestPrintPrintline 這 function 中,這樣方便閱讀..







using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml;



namespace LiaCompiler
{
    public class CodeGenerator
    {

        public void TestPrintPrintline(string outFileName, string xmlSource)
        {

            AssemblyName am = new AssemblyName();

            am.Name = outFileName.ToUpper().Replace(".LIA", "");
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(am, AssemblyBuilderAccess.Save);

            //訂立一永續性模組
            ModuleBuilder mb = ab.DefineDynamicModule(am.Name, outFileName);
            TypeBuilder tb = mb.DefineType("LiaType", TypeAttributes.Public);
            MethodBuilder metb = tb.DefineMethod("Main", MethodAttributes.Public |
            MethodAttributes.Static, null, null);
            ab.SetEntryPoint(metb);

            ILGenerator il = metb.GetILGenerator();
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xmlSource);
            foreach (XmlNode node in xmlDocument.ChildNodes)
            {
                //讀取Lia Element 中資料
                if (node.Name == "Lia")
                {
                    foreach (XmlNode liaNode in node.ChildNodes)
                    {
                        //建議這樣拆 處理每一個特殊的element
                        if (liaNode.Name == "print")
                        {
                            AddPrintSegment(il, liaNode);
                        }

                        else if (liaNode.Name == "printline")
                        {
                            AddPrintlineSegment(il, liaNode);
                        }

                    }

                }

            }

            //返回計算
            il.Emit(OpCodes.Ret);
            tb.CreateType();
            ab.Save(outFileName);

        }





        private void AddPrintSegment(ILGenerator ilGenerator, XmlNode node)
        {
            //插入變量後 執行write 印出
            ilGenerator.Emit(OpCodes.Ldstr, node.InnerText);
            ilGenerator.Emit(OpCodes.Call, typeof(System.Console).GetMethod("Write", new System.Type[] { typeof(string) }));

        }





        private void AddPrintlineSegment(ILGenerator ilGenerator, XmlNode node)
        {
            //插入 執行writeline
            ilGenerator.EmitWriteLine(node.InnerText);
        }

    }

}




這樣我們就可以編譯 sample.lia 檔案..



2012-09-16_225846



查看一次MSIL Code



2012-09-16_225746



執行結果  compile and run



2012-09-16_233732



基本上 簡單的你就製作一個具有 print 還有 printline  的語言..

當然在公司 Lia Language Project 依然緩慢進行中..不過玩到後面你會覺得越來越困難,當然也會越來越好玩.







範例下載:





參考文件:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.ilgenerator.emit.aspx

http://msdn.microsoft.com/en-us/library/aa329957(v=vs.71).aspx

http://msdn.microsoft.com/en-us/magazine/cc136756.aspx


當麻許的超技八 2014 | Donma Hsu Design.