// // (c) 2009, Sigbjorn Finne // // Generating Haskell wrapper modules for .NET classes/interfaces. // using System; namespace HsWrap { /// /// /// public class HsOutput { private System.Type m_type; private System.Reflection.MemberInfo[] m_members; private System.Collections.Specialized.StringCollection m_names; private System.Collections.Specialized.StringCollection m_imports; private System.Collections.Specialized.StringCollection m_noTyModules; private System.String m_modname; private bool m_isTyMod; private bool m_hasTypeModule; public HsOutput(System.Type ty,System.Reflection.MemberInfo[] mems, bool hasTypeMod, bool isTyMod) { m_type = ty; m_members = mems; m_names = new System.Collections.Specialized.StringCollection(); m_imports = new System.Collections.Specialized.StringCollection(); m_noTyModules = new System.Collections.Specialized.StringCollection(); m_modname = "NET." + m_type.FullName; m_isTyMod = isTyMod; m_hasTypeModule = hasTypeMod; m_noTyModules.Add("System.Object"); m_noTyModules.Add("System.Enum"); } protected void OutputImports(System.IO.StreamWriter st) { foreach (String s in m_imports) { st.WriteLine("import qualified {0}", s); } st.WriteLine(""); } protected void OutputHeader(System.IO.StreamWriter st) { String supTy = (m_type.IsInterface ? "System.Object" : m_type.BaseType.FullName); String supTyCls = (m_type.IsInterface ? "Object" : m_type.BaseType.Name); String tySuff = ""; if ( m_isTyMod ) { tySuff = ".Type"; // Assume both have a .Type module if ( HasTypeModule(supTy) ) { supTy = supTy + ".Type"; } } st.WriteLine("{-# OPTIONS -XEmptyDataDecls #-}"); st.WriteLine("module NET.{0}{1} ( module NET.{0}{1} ) where", m_type.FullName, tySuff); st.WriteLine(""); st.WriteLine("import NET"); if ( m_hasTypeModule ) { st.WriteLine("import NET.{0}.Type as NET.{0}", m_type.FullName); } AddImport("NET."+supTy); // Emit those suckers.. OutputImports(st); if (!m_type.IsInterface && m_type.BaseType.FullName == "System.Enum") { // shorten out and go straight to the underlying tag (value) type. // Using Int is Haskell-Enum friendly. The Haskell code that // the OutputToFile() method emits assumes that this is done. if ( m_isTyMod ) { st.WriteLine("type {0} a = Int", m_type.Name); } } else { if ( !m_hasTypeModule ) { st.WriteLine("data {0}_ a", m_type.Name); st.WriteLine("type {0} a = NET.{1}.{2} ({0}_ a)", m_type.Name, supTy, supTyCls); st.WriteLine(""); } } } private String ToHaskellName(String x) { System.String candName, candNameOrig; System.Int32 uniq = 1; if (System.Char.IsUpper(x[0])) { candName = String.Concat(System.Char.ToLower(x[0]), x.Substring(1)); } else { candName = x; } candNameOrig = candName; while (m_names.Contains(candName)) { candName = String.Concat(candNameOrig,"_",uniq.ToString()); uniq++; } m_names.Add(candName); return candName; } private String ToHaskellConName(String x) { System.String candName, candNameOrig; System.Int32 uniq = 1; if (System.Char.IsLower(x[0])) { candName = String.Concat(System.Char.ToUpper(x[0]), x.Substring(1)); } else { candName = x; } candNameOrig = candName; while (m_names.Contains(candName)) { candName = String.Concat(candNameOrig,"_",uniq.ToString()); uniq++; } m_names.Add(candName); return candName; } private bool HasTypeModule(System.String nm) { return (!m_noTyModules.Contains(nm)); } private void AddImport(System.String nm) { if (!m_imports.Contains(nm) && String.Compare(nm, m_modname) != 0) { m_imports.Add(nm); } } protected void OutputHaskellType(System.Text.StringBuilder sb, System.Type ty, System.Int32 idx) { /* Curiously, &-versions of prim types are showing up (cf. System.Uri.HexUnescape). * Just ignore them. */ if (ty.FullName == "System.Boolean" || ty.FullName == "System.Boolean&" ) { sb.Append("Bool"); return; } if (ty.FullName == "System.String") { sb.Append("String"); return; } if (ty.FullName == "System.Char" || ty.FullName == "System.Char&") { sb.Append("Char"); return; } if (ty.FullName == "System.Double" || ty.FullName == "System.Double&") { sb.Append("Double"); return; } if (ty.FullName == "System.Single" || ty.FullName == "System.Single&") { sb.Append("Double"); return; } if (ty.FullName == "System.SByte" || ty.FullName == "System.SByte&") { AddImport("Data.Int"); sb.Append("Data.Int.Int8"); return; } if (ty.FullName == "System.Int16" || ty.FullName == "System.Int16&") { AddImport("Data.Int"); sb.Append("Data.Int.Int16"); return; } if (ty.FullName == "System.Int32" || ty.FullName == "System.Int32&") { sb.Append("Int"); return; } if (ty.FullName == "System.Int64" || ty.FullName == "System.Int64&") { AddImport("Data.Int"); sb.Append("Data.Int.Int64"); return; } if (ty.FullName == "System.Byte" || ty.FullName == "System.Byte&") { AddImport("Data.Word"); sb.Append("Data.Word.Word8"); return; } if (ty.FullName == "System.UInt16" || ty.FullName == "System.UInt16&") { AddImport("Data.Word"); sb.Append("Data.Word.Word16"); return; } if (ty.FullName == "System.UInt32" || ty.FullName == "System.UInt32&") { AddImport("Data.Word"); sb.Append("Data.Word.Word32"); return; } if (ty.FullName == "System.UInt64" || ty.FullName == "System.UInt64&") { AddImport("Data.Word"); sb.Append("Data.Word.Word64"); return; } if (ty.FullName == "System.Void") { sb.Append("()"); return; } if (ty.FullName == "System.Object") { AddImport("NET.System.Object"); sb.AppendFormat("NET.System.Object.Object a{0}",idx); return; } if (ty.IsArray) { AddImport("NET.System.Array"); sb.Append("NET.System.Array.Array ("); OutputHaskellType(sb, ty.GetElementType(), idx); sb.Append(")"); } else { AddImport("NET." + ty.FullName); sb.AppendFormat("NET.{0}.{1} a{2}", ty.FullName, ty.Name, idx); } } protected void OutputMethodSig(System.Text.StringBuilder sb, System.Reflection.MemberInfo mi) { System.Reflection.MethodInfo m = (System.Reflection.MethodInfo)mi; System.Reflection.ParameterInfo[] ps = m.GetParameters(); int i; for (i=0; i < ps.Length; i++) { OutputHaskellType(sb,ps[i].ParameterType,i); sb.Append(" -> "); } if (m.IsStatic) { sb.Append("IO ("); } else { sb.AppendFormat("{0} obj -> IO (", mi.DeclaringType.Name); } OutputHaskellType(sb,m.ReturnType,i); sb.AppendFormat("){0}",System.Environment.NewLine); } protected void OutputFieldSig(System.Text.StringBuilder sb, System.Reflection.FieldInfo fi, bool isSetter) { /* Note: indexed values are provided via properties */ if (isSetter) { OutputHaskellType(sb,fi.FieldType,0); if (!fi.IsStatic) { sb.AppendFormat(" -> {0} obj", fi.DeclaringType.Name); } sb.AppendFormat(" -> IO (){0}",System.Environment.NewLine); } else { if (fi.IsStatic) { sb.Append("IO ("); } else { sb.AppendFormat("{0} obj -> IO (", fi.DeclaringType.Name); } OutputHaskellType(sb,fi.FieldType,0); sb.AppendFormat("){0}",System.Environment.NewLine); } } protected void OutputArgs(System.Text.StringBuilder sb, System.Reflection.MemberInfo mi, System.Boolean isTupled) { System.Reflection.MethodInfo m = (System.Reflection.MethodInfo)mi; Int32 i = 0; System.Reflection.ParameterInfo[] ps = m.GetParameters(); if (isTupled && ps.Length != 1) sb.Append("("); for (i=0; i < ps.Length; i++) { sb.AppendFormat("arg{0}",i); if (isTupled && (i+1) < ps.Length) { sb.Append(","); } else { if (!isTupled) sb.Append(" "); } } if (isTupled && ps.Length != 1) sb.Append(")"); } protected void OutputMember(System.Text.StringBuilder sb, System.Reflection.MemberInfo mi) { switch (mi.MemberType) { case System.Reflection.MemberTypes.Method: System.String methName = ToHaskellName(mi.Name); System.Reflection.MethodInfo m = (System.Reflection.MethodInfo)mi; System.Reflection.ParameterInfo[] ps = m.GetParameters(); sb.AppendFormat("{0} :: ", methName); OutputMethodSig(sb,mi); sb.AppendFormat("{0}", methName); if (ps.Length > 0) { for (int i=0;i < ps.Length; i++) { sb.AppendFormat(" arg{0}", i); } } sb.Append(" ="); if (m.IsStatic) { sb.AppendFormat(" invokeStatic \"{0}\" \"{1}\"", mi.DeclaringType, mi.Name); } else { sb.AppendFormat(" invoke \"{0}\"", mi.Name); } if ( ps.Length == 0 ) { sb.Append(" () "); } else { sb.Append(" ("); for (int i=0;i < ps.Length; i++) { sb.AppendFormat("arg{0}", i); if ( (i+1) < ps.Length ) { sb.Append(", "); } } sb.Append(")"); } sb.Append(System.Environment.NewLine); sb.Append(System.Environment.NewLine); break; case System.Reflection.MemberTypes.Field: System.String fieldName = mi.Name; System.Reflection.FieldInfo f = (System.Reflection.FieldInfo)mi; sb.AppendFormat("get_{0} :: ", fieldName); OutputFieldSig(sb,f,false); if ( f.IsStatic ) { sb.AppendFormat("get_{0} = getFieldStatic \"{1}\" \"{2}\" ()", fieldName, mi.DeclaringType, mi.Name); } else { sb.AppendFormat("get_{0} = getField \"{1}\" ()", mi.Name); } sb.Append(System.Environment.NewLine); if (!f.IsInitOnly) { sb.AppendFormat("set_{0} :: ", fieldName); OutputFieldSig(sb,f,true); if ( f.IsStatic ) { sb.AppendFormat("set_{0} = setFieldStatic \"{1}\" \"{2}\"", fieldName, mi.DeclaringType, mi.Name); } else { sb.AppendFormat("set_{0} = setField \"{1}\" \"{2}\"", fieldName, mi.DeclaringType, mi.Name); } sb.Append(System.Environment.NewLine); } break; default: break; } } protected void OutputField(System.Text.StringBuilder sb, System.Reflection.MemberInfo mi) { switch (mi.MemberType) { case System.Reflection.MemberTypes.Field: System.String fieldName = ToHaskellConName(mi.Name); sb.Append(fieldName); break; default: break; } } public void OutputToFile(String fn) { System.IO.FileStream fs = new System.IO.FileStream(fn,System.IO.FileMode.Create); System.IO.StreamWriter st = new System.IO.StreamWriter(fs,System.Text.Encoding.ASCII); System.Text.StringBuilder sb = new System.Text.StringBuilder(); if ( !m_type.IsInterface && m_type.BaseType.FullName == "System.Enum" ) { /* enumerations are mapped onto Haskell data types. */ if ( m_isTyMod ) { System.String sep = " = "; sb.AppendFormat("data {0}Ty", m_type.Name); sb.Append(System.Environment.NewLine); foreach (System.Reflection.MemberInfo mem in m_members) { if (mem.Name != "value__") { sb.Append(sep); OutputField(sb,mem); sb.Append(System.Environment.NewLine); sep = " | "; } } sb.AppendFormat(" deriving ( Enum, Show, Read ){0}",System.Environment.NewLine); // Emit functions for converting betw alg type and object type. AddImport("NET.System.Type"); AddImport("NET.System.Enum"); sb.AppendFormat("to{0} :: {0}Ty -> {0} (){1}", m_type.Name, System.Environment.NewLine); sb.AppendFormat("to{0} tag = fromEnum tag{1}",m_type.Name,System.Environment.NewLine); sb.Append(System.Environment.NewLine); sb.AppendFormat("from{0} :: {0} () -> {0}Ty{1}", m_type.Name, System.Environment.NewLine); sb.AppendFormat("from{0} obj = toEnum obj", m_type.Name); sb.Append(System.Environment.NewLine); } } else { if ( !m_isTyMod ) { foreach (System.Reflection.MemberInfo mem in m_members) { OutputMember(sb,mem); } } } OutputHeader(st); // Output the method/field wrappers. st.WriteLine(sb.ToString()); st.Flush(); st.Close(); fs.Close(); } } }