// // (c) 2009, Sigbjorn Finne // // Generating Haskell wrapper modules for .NET classes/interfaces. // using System; using System.Reflection; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace HsWrap { #if false // test class for type constraints: public class HsFoo where T : System.IO.FileStream, System.IConvertible { public T getT() { return null; } }; #endif public class HsNames { public static string hsPrefix = "NET."; public static string hsBase = "Base"; public static string hsNetType = "Type"; public static string hsNetArg = "Arg"; public static string hsNetRes = "Result"; }; /// /// To generate for wrappers to generic methods, we need to /// supply type evidence at run-time -- what types we are using /// the generic type's type variables at -- to resolve the field/method /// and access the actual contents. /// /// The GenTypeInfo class bundles together the values we need to keep /// track of for each of these type variables when emitting the wrapper. /// Code is then generated from this info which nagivates the type /// structure of the arguments, hence we need to keep track of type /// constructor arity + what type argument we are interested in. /// public class GenTypeInfo { String m_argName; int m_argTyConArity; bool m_isTypeFamily; int m_argTyPos; GenTypeInfo m_nested; public GenTypeInfo( String argName , int ari , bool isTyFam , int tyPos ) { m_argName = argName; m_argTyConArity = ari; m_isTypeFamily = isTyFam; m_argTyPos = tyPos; m_nested = null; } public String ArgName { get { return m_argName; } set { m_argName = value; } } public int ArgTyConArity { get { return m_argTyConArity; } set { m_argTyConArity = value; } } public int ArgTyPos { get { return m_argTyPos; } set { m_argTyPos = value; } } public bool TypeFamily { get { return m_isTypeFamily; } set { m_isTypeFamily = value; } } public GenTypeInfo NestedType { get { return m_nested; } set { m_nested = value; } } } /// /// /// 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 WrapQ m_modQueue; private bool m_okSig; private String m_tyArgs; private Dictionary m_tyGenericArgs; static private System.Collections.Hashtable m_typeMap; static private String m_prefix; static HsOutput() { BuildTypeMap(); m_prefix = "NET."; } public HsOutput(System.Type ty, System.Reflection.MemberInfo[] mems, WrapQ wq, 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 = m_prefix + m_type.FullName; m_isTyMod = isTyMod; m_modQueue = wq; m_okSig = true; m_tyArgs = ""; m_tyGenericArgs = new Dictionary(); m_noTyModules.Add(HsNames.hsPrefix + "System.Object"); m_noTyModules.Add(HsNames.hsPrefix + "System.Type"); m_noTyModules.Add(HsNames.hsPrefix + "System.Enum"); } public void SetTypeModuleFlag(bool flg) { m_isTyMod = flg; } protected void OutputImports(System.IO.StreamWriter st) { foreach (String s in m_imports) { if ( !Config.DryRunMode ) { String comment = ""; String tyBa = s; int spos = 0; if ( s.StartsWith(m_prefix) ) { tyBa = s.Substring(m_prefix.Length); spos = m_prefix.Length; } if ( tyBa.EndsWith(".Type") && s != "NET.System.Type" ) { String tyNm = s.Substring(0,tyBa.LastIndexOf('.')+spos); String tyNm1 = tyBa.Substring(0,tyBa.LastIndexOf('.')); if ( !m_modQueue.Blacklist.IsTypeOfInterest(tyNm1,false) ) { comment = "--"; } st.WriteLine("{2}import qualified {0} as {1}", s, tyNm, comment); } else { if ( !m_modQueue.Blacklist.IsTypeOfInterest(tyBa,false) ) { comment = "--"; } // ToDo: simplify if ( s == "NET.System.Object" || s == "NET.System.Array" || IsHsTypeFamily(s) || m_modQueue.IsExternalModule(s)) { st.WriteLine("{0}import qualified {1}", comment, s); } else { st.WriteLine("{0}import qualified {1}.Type as {1}", comment, s); } } } // // Check if the imported module already exists (in the same directory tree..) // If not, add it to the queue of to-be-imported modules. // if (s.StartsWith(m_prefix)) { String tyName = s.Substring(m_prefix.Length); if ( tyName.EndsWith(".Type") && s != "NET.System.Type" ) { tyName = tyName.Substring(0,tyName.Length-5); } String extName = HsType.TypeToModuleFile(tyName) + ".hs"; // Console.WriteLine("-{0}",tyName); try { if ( !m_modQueue.Blacklist.IsTypeKnown(tyName) ) { Console.WriteLine("WARNING: missing module {0} {1}",tyName,s); //m_modQueue.AddType(tyName,extName); } } catch (Exception){ ; } } } if ( !Config.DryRunMode ) { st.WriteLine(""); } } protected void OutputHeader(System.IO.StreamWriter st) { String superTy = TypeInfo.GetSuperName(m_type,true); int idx; String extraTyArgs = ""; AddImport(m_prefix+superTy); if ( (idx =superTy.IndexOf('`')) > 0 ) { List tArgs = TypeArgs(superTy.Substring(idx)); superTy = superTy.Substring(0,idx); foreach(String t in tArgs) { extraTyArgs += " g"+t; } } String supTyCls = TypeInfo.GetSuperName(m_type,false); if ( (idx =supTyCls.IndexOf('`')) > 0 ) { supTyCls = supTyCls.Substring(0,idx); } // ToDo: have per-type prefixes; there is no guarantee that // the superclass belongs to a user's custom type prefix hierarchy. // Indeed, if it something other than NET. , System.Object children // will have a wrong 'super' reference. String hSupTy = m_prefix + superTy; String modNameBase = m_prefix + m_type.FullName; String modName = m_prefix + m_type.FullName; String tyName = HsType.TypeToModuleFile(m_type.Name); /* Console.WriteLine("OutputHeader: '{0}' '{1}' '{2}' '{3}' '{4}'", superTy, tyName, m_type.ToString(), m_type.BaseType.Name, m_type.FullName); */ modName = HsType.TypeToModuleName(modName); modNameBase = HsType.TypeToModuleName(modNameBase); if ( m_isTyMod ) { modName += ".Type"; // ToDo: check for existence? if ( HasTypeModule(hSupTy) ) { hSupTy += ".Type"; } } if ( !Config.DryRunMode ) { st.WriteLine("{-# OPTIONS -XEmptyDataDecls #-}"); st.WriteLine("-- Automatically generated; local edits may not stick ;-)"); st.WriteLine("-- Generated by hswrap: {0}", DateTime.Now.ToUniversalTime().ToString("s")); st.WriteLine("module {0} ( module {0} ) where", modName); st.WriteLine(""); st.WriteLine("import qualified NET"); if ( Config.WithTypeModule && !m_isTyMod ) { st.WriteLine("import {0}.Type as {0}", modNameBase); } } if ( !Config.WithTypeModule || m_isTyMod ) { foreach (System.Type ifa in m_type.GetInterfaces()) { String fullName = ifa.FullName; if ( ifa.ContainsGenericParameters ) { fullName = ifa.GetGenericTypeDefinition().FullName; } if ( Config.WithTypeModule ) { AddImport(m_prefix+fullName, true); } else { AddImport(m_prefix+fullName); } } } // Emit those suckers.. OutputImports(st); if ( !Config.WithTypeModule || m_isTyMod ) { if ( !Config.DryRunMode ) { foreach (System.Type ifa in m_type.GetInterfaces()) { String comment = ""; String fullName = ifa.FullName; if ( ifa.ContainsGenericParameters ) { fullName = ifa.GetGenericTypeDefinition().FullName; } if ( !m_modQueue.Blacklist.IsTypeOfInterest(fullName,false) ) { comment = "--"; } String argSuffix = "_ a"; if (!m_type.IsInterface && m_type.BaseType != null && m_type.BaseType.FullName == "System.Enum") { argSuffix = "Ty"; } st.WriteLine("{3}instance {4}{0}.{1} ({2}{6}{5}) where", HsType.TypeToModuleName(fullName), HsType.TypeToModuleName(ifa.Name), tyName, comment, m_prefix,m_tyArgs,argSuffix); st.WriteLine("{3} instanceName_{0} _ = \"{4}{1}.{2}\"", HsType.TypeToModuleName(ifa.Name), m_type.FullName, tyName, comment, m_prefix); st.WriteLine(); } } if (!m_type.IsInterface && m_type.BaseType != null && 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", tyName); } } else { // if we've emitted the type into a separate, loop-avoiding module, // don't repeat it now. (cf. 'import' emittage above.) if ( !Config.WithTypeModule || m_isTyMod ) { if ( m_type.IsInterface ) { // Interfaces get a Haskell class with a single method: // (why that method? could provide useful hint when debugging + // avoids the non-portable use of empty classes.) st.WriteLine("class {0} a where ", tyName); st.WriteLine(" instanceName_{0} :: a -> String",tyName); st.WriteLine(""); st.WriteLine("data {0}_ a{1}", tyName, m_tyArgs); st.WriteLine(""); } else { st.WriteLine("data {0}_ a{1}", tyName, m_tyArgs); st.WriteLine("instance {0}{1} ({2}_ a{3}) where", m_prefix, HsNames.hsNetType, tyName, m_tyArgs); st.WriteLine(" tyName _ = [\"{0}\"]",m_type.FullName); if ( superTy == "System.Object" ) { st.WriteLine("type {0} a{4} = {3}Object ({0}_ a{4})", tyName, superTy, supTyCls, m_prefix, m_tyArgs); } else { st.WriteLine("type {0} a{4} = {3}Object ({0}_ ({3}{1}.{2}_ a{5}) {4})", tyName, superTy, supTyCls, m_prefix, m_tyArgs, extraTyArgs); } 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) { AddImport(nm,false); } private void AddImport(System.String nm, bool isTyMod) { // Note: does not have .Type in 'nm'. bool addIt = true; // String ty = HsType.TypeToModuleName(nm); HsTypeDetails tyDet = HsType.ToTypeDetails(nm); tyDet.IsTypeModule = isTyMod; // // Check if the imported module already exists (in the same directory tree..) // If not, add it to the queue of to-be-imported modules. // String extName = HsType.TypeToModuleFile(tyDet.HsTypeName) + ".hs"; // Console.WriteLine("-{0} ({1}) {2} {3}",tyDet.HsTypeName, tyDet.HsFullName, nm, isTyMod); if ( !m_modQueue.IsExternalModule(tyDet.HsTypeName) && !m_modQueue.Blacklist.DoesTypeExist(tyDet.HsTypeName) && !m_modQueue.IsElem(tyDet.HsFullName) ) { try { // Console.WriteLine("adding: {0} {1} {2}", tyDet.HsTypeName, tyDet.HsFullName, tyDet.HsOrigName); if ( !m_modQueue.Blacklist.IsTypeKnown(tyDet.HsFullName) && !m_modQueue.Blacklist.DoesTypeExist(tyDet.HsTypeName) ) { // Console.WriteLine("checking...{0}", tyDet.HsTypeName); if ( m_modQueue.IsAGo(HsTypeDetails.DropPrefix(tyDet.HsTypeName)) ) { // Console.WriteLine("Adding: {0} '{1}'", tyDet.HsFullName, extName); m_modQueue.AddType(tyDet.HsFullName,extName); } else { m_okSig = false; m_modQueue.AddException(tyDet.HsFullName); m_modQueue.AddException(tyDet.HsTypeName); addIt = false; } } else { if ( !m_modQueue.Blacklist.IsTypeOfInterest(tyDet.HsTypeName,false) ) { m_okSig = false; addIt = false; } } } catch (Exception){ ; } } if ( addIt && !m_imports.Contains(tyDet.HsTypeModName) && String.Compare(tyDet.HsTypeModName, m_modname) != 0) { m_imports.Add(tyDet.HsTypeModName); } } // // Initialize the map from .NET value types to their // Haskell equivalents. // static void BuildTypeMap () { m_typeMap = new System.Collections.Hashtable(); m_typeMap.Add("System.Boolean", "Bool"); m_typeMap.Add("System.String", "String"); m_typeMap.Add("System.Char", "Char"); m_typeMap.Add("System.Double", "Double"); m_typeMap.Add("System.Single", "Float"); m_typeMap.Add("System.SByte", "Data.Int.Int8"); m_typeMap.Add("System.Int16", "Data.Int.Int16"); m_typeMap.Add("System.Int32", "Data.Int.Int32"); m_typeMap.Add("System.Int64", "Data.Int.Int64"); m_typeMap.Add("System.Byte", "Data.Word.Word8"); m_typeMap.Add("System.UInt16", "Data.Word.Word16"); m_typeMap.Add("System.UInt32", "Data.Word.Word32"); m_typeMap.Add("System.UInt64", "Data.Word.Word64"); m_typeMap.Add("System.Void*", "Foreign.Ptr.Ptr ()"); m_typeMap.Add("System.Void", "()"); } static String ClassPrefix { get { return m_prefix; } set { m_prefix = value; } } /* Special case: to represent the family of delegates below on the * Haskell side, we tuple up the arguments for their types., * * NET.System.Func (String,Queue () String) (Queue () String) * */ static bool IsTypeFamily(String s) { return (s.StartsWith("System.Action") || s.StartsWith("System.Func") || s.StartsWith("System.Pred")); } static bool IsHsTypeFamily(String s) { return (s.StartsWith("NET.System.Action") || s.StartsWith("NET.System.Func") || s.StartsWith("NET.System.Pred")); } static bool IsFunc(String s) { return ( s.StartsWith("System.Func") ); } // // OutputHaskellType() handles the translation from a // System.Type into the corresponding Haskell type. // protected void OutputHaskellType(System.Text.StringBuilder sb, ref Dictionary ctxt, Dictionary tyArgs, System.Type ty, System.Int32 idx, bool isResult, bool inOuterFun, bool isNested) { /* Curiously, &-versions of prim types are showing up (cf. System.Uri.HexUnescape). * Just ignore them. */ Char[] nms = {'&'}; String tyFullNm, tyFullName = ""; String tyNm; if ( ty.IsByRef ) { // ToDo: record ref'ness for later processing? Reqd, I think.. ty = ty.GetElementType(); } if ( ty.IsGenericParameter || ty.IsGenericType ) { tyNm = ty.Name; tyFullNm = tyNm; if ( ty.FullName != null && ty.FullName != "" ) { tyFullNm = ty.FullName; } else if ( ty.ContainsGenericParameters ) { try { tyFullNm = ty.GetGenericTypeDefinition().FullName; } catch (Exception) { ; } } tyFullName = tyFullNm; tyNm = HsType.TypeToModuleName(tyNm); tyFullNm = HsType.TypeToModuleName(tyFullNm); } else { try { tyNm = ty.Name; if ( ty.FullName != null ) { tyFullNm = ty.FullName; } else { tyFullNm = tyNm; } tyFullName = tyFullNm; } catch(Exception e) { Console.WriteLine("OutputHaskellType: exception {0} {1} {2}",ty,ty.Name,ty.FullName); throw(e); } } // Check if it is a primitive type.. if ( m_typeMap.ContainsKey(tyFullNm) ) { String haTy = (String)m_typeMap[tyFullNm]; // ..so, if the Haskell type name is qualified, // add its module to the list of imports. // int i1 = haTy.LastIndexOf('.'); if ( i1 != (-1) ) { AddImport(haTy.Substring(0,i1)); } sb.Append(haTy); return; } if ( /*ty.IsGenericType || */ tyFullNm == "System.Object" ) { sb.AppendFormat("NET.Object (objTy{0} a{0})",idx); return; } if ( ty.IsArray ) { AddImport("NET.System.Array"); sb.Append("NET.System.Array.Array ("); OutputHaskellType(sb, ref ctxt, tyArgs, ty.GetElementType(), idx, false, inOuterFun || IsFunc(tyFullNm),true); sb.Append(")"); } else if ( ty.IsGenericType ) { int i=0; int arity = HsType.TypeParams(tyFullName); String tyStr = m_prefix + tyFullNm + "." + tyNm + " " + "a"+idx; Type[] ts = ty.GetGenericArguments(); bool isTupled = IsTypeFamily(tyFullNm) && ts.Length > 2; bool closeParen = false; String objParam = ""; if ( ty.IsInterface ) { objParam = "(objTy_"+idx+" t"+idx+" "; tyStr = "NET.Object " + objParam; closeParen = true; isTupled = false; AddImport(m_prefix + tyFullNm); } AddImport(m_prefix + tyFullName); if ( IsFunc(tyFullNm) && !isNested ) { tyStr="("; closeParen = true; } if ( isTupled ) { tyStr += " ("; } foreach(Type tyArg in ts) { String tyArgStr = tyArg.ToString(); String tyStrExtra = ""; bool checkConstraints = false; if ( tyArgs != null && tyArgs.ContainsKey(tyArgStr) ) { tyStrExtra = "g"+tyArgStr; // make note of an argument + its type arg position that uses // this generic argument. So as to be able to emit code later on // that will derive the types at which we invoke the generic method at. // checkConstraints = true; } else if ( m_tyGenericArgs.ContainsKey(tyArgStr) ) { tyStrExtra = "g"+tyArgStr; checkConstraints = true; } else { System.Text.StringBuilder nestedSb = new System.Text.StringBuilder(); OutputHaskellType(nestedSb,ref ctxt,tyArgs,tyArg,10*idx+1,isResult,inOuterFun || IsFunc(tyFullNm),true); tyStrExtra = nestedSb.ToString(); if ( tyStrExtra.Contains(" ") ) { tyStrExtra = "(" + tyStrExtra + ")"; } // Console.WriteLine("curious unbound generic arg: {0} {1}{2} '{3}'",tyArgStr,idx,tyStrExtra,tyStrExtra.Contains(" ")); // tyStrExtra = "t"+idx+i; } if ( checkConstraints ) { foreach(Type constraintT in tyArg.GetGenericParameterConstraints()) { String tyFullC = HsType.TypeToModuleName(constraintT.FullName); String tyNmC = HsType.TypeToModuleName(constraintT.Name); if ( constraintT.IsClass ) { // constrained to a particular class tyStrExtra = "(" + m_prefix + tyFullC + "." + tyNmC + " " + tyStrExtra + ")"; AddImport(m_prefix + tyFullC); } else { // constrained to a particular interface; add instance to context. String s = tyFullC + "." + tyNmC + " g" + tyArgStr; AddImport(m_prefix + tyFullC); ctxt[s] = s; } } } if ( tyStrExtra != "" && tyStrExtra.StartsWith("g") ) { String s = HsNames.hsNetType+" "+tyStrExtra; ctxt[s] = s; if ( IsFunc(tyFullNm) && !isNested ) { s = ( (i <= ts.Length - 2) ? HsNames.hsNetArg : HsNames.hsNetRes) + " "+tyStrExtra; ctxt[s] = s; } #if false if ( ty.IsInterface ) { s = tyFullNm + "." + tyNm + " " + objParam + tyStrExtra + ")"; ctxt[s] = s; } #endif } if ( isTupled && (i < (ts.Length - 2)) ) { tyStrExtra += ","; } else if ( isTupled && (i == (ts.Length - 2)) ) { tyStrExtra += ")"; } if ( IsFunc(tyFullNm) && !isNested && (i == (ts.Length - 2)) ) { tyStrExtra += " -> IO "; } tyStr += " " + tyStrExtra; if ( objParam != "" ) { objParam += " " + tyStrExtra; } i++; } if ( closeParen ) { tyStr += ")"; } sb.Append(tyStr); if ( ty.IsInterface ) { String s = tyFullNm + "." + tyNm + " " + objParam + ")"; ctxt[s] = s; if ( inOuterFun || (IsFunc(tyFullNm) && !isNested) ) { s = HsNames.hsNetType + " " + objParam + ")"; ctxt[s] = s; } } } else if ( ty.IsInterface ) { // Console.WriteLine("iface-add: {0} {1} {2}", ty, tyFullNm, tyFullName); AddImport(m_prefix + tyFullNm); String s = tyFullNm + "." + tyNm + " a" + idx.ToString(); ctxt[s] = s; sb.AppendFormat("NET.Object (objTy{0} a{0})", idx); } else { String tyStr = m_prefix + tyFullNm + "." + tyNm + " a"+idx; if ( ty.IsGenericParameter ) { String tyStrExtra = "g" + ty.Name; String s = HsNames.hsNetType+" "+tyStrExtra; ctxt[s] = s; if ( isResult ) { s = HsNames.hsNetRes + " "+tyStrExtra; ctxt[s] = s; } else { s = HsNames.hsNetArg + " "+tyStrExtra; ctxt[s] = s; } foreach(Type constraintT in ty.GetGenericParameterConstraints()) { String tyFullC = HsType.TypeToModuleName(constraintT.FullName); String tyNmC = HsType.TypeToModuleName(constraintT.Name); if ( constraintT.IsClass ) { // constrained to a particular class tyStrExtra = "(" + m_prefix + tyFullC + "." + tyNmC + " " + tyStrExtra + ")"; AddImport(m_prefix + tyFullC); } else { // constrained to a particular interface; add instance to context. s = tyFullC + "." + tyNmC + " g" + ty.Name; ctxt[s] = s; AddImport(m_prefix + tyFullC); } } // Only emit a type variable; allows the use of non-Object args. tyStr = tyStrExtra; // "NET.Object (objTy"+idx+" t" + idx + " " + tyStrExtra + ")"; } else { AddImport(m_prefix + tyFullNm); } sb.Append(tyStr); } } protected void OutputMethodSig(System.Text.StringBuilder sb, Dictionary tyArgs, System.Reflection.MethodInfo mi) { ParameterInfo[] ps = mi.GetParameters(); int i; Dictionary ctxt = new Dictionary(); int ctxtStartPos = sb.Length; String ifaceObjTy = "NET.Object (objTy obj "; if ( m_tyGenericArgs != null && m_tyGenericArgs.Count > 0 ) { i = 0; foreach(KeyValuePair kv in m_tyGenericArgs) { String tyArgStr = "g"+kv.Key.ToString(); ifaceObjTy += " " + tyArgStr; String s = HsNames.hsNetType + " "+tyArgStr; ctxt[s] = s; // ctxt[HsNames.hsNetType + " "+tyArgStr] = HsNames.hsNetType + " "+ifaceObjTy+")"; //tyArgStr; i++; } } ifaceObjTy += ")"; i = 0; if ( mi.IsDefined(typeof(ExtensionAttribute),true) ) { i = 1; } for (; i < ps.Length; i++) { // Console.WriteLine("arg{0} {1} {2}", i, ps[i].Name, ps[i].ParameterType); OutputHaskellType(sb,ref ctxt,tyArgs,ps[i].ParameterType,i,false, false,false); sb.Append(" -> "); } if ( mi.IsDefined(typeof(ExtensionAttribute),true) ) { OutputHaskellType(sb,ref ctxt,tyArgs,ps[0].ParameterType,0,false, false,false); sb.Append("-> IO ("); } else if (mi.IsStatic ) { sb.Append("IO ("); } else if ( mi.DeclaringType.IsInterface ) { sb.AppendFormat("{0} -> IO (", ifaceObjTy); } else { sb.AppendFormat("{0} obj{1} -> IO (", HsType.TypeToModuleName(mi.DeclaringType.Name),m_tyArgs); } OutputHaskellType(sb,ref ctxt,tyArgs,mi.ReturnType,i,true,false,false); if ( ctxt.Count > 0 || mi.DeclaringType.IsInterface ) { String s = "("; i=0; foreach(KeyValuePair kv in ctxt) { s = s + m_prefix + kv.Value; if (i < (ctxt.Count - 1) ) { s = s + ", "; } i++; } if (mi.DeclaringType.IsInterface) { if (ctxt.Count > 0) { s = s + ", "; } s = s + HsType.TypeToModuleName(mi.DeclaringType.Name) + " obj"; } s = s + ") => "; sb.Insert(ctxtStartPos,s); } sb.AppendFormat("){0}",System.Environment.NewLine); } protected void OutputFieldSig(System.Text.StringBuilder sb, System.Reflection.FieldInfo fi, Dictionary tyArgs, bool isSetter) { Dictionary ctxt = new Dictionary(); int ctxtStartPos = sb.Length; /* Note: indexed values are provided via properties */ if (isSetter) { OutputHaskellType(sb,ref ctxt,tyArgs,fi.FieldType,0,false,false,false); if (!fi.IsStatic) { sb.AppendFormat(" -> {0} obj", HsType.TypeToModuleName(fi.DeclaringType.Name)); } sb.AppendFormat(" -> IO (){0}",System.Environment.NewLine); } else { if (fi.IsStatic) { sb.Append("IO ("); } else if ( fi.DeclaringType.IsInterface ) { sb.AppendFormat("NET.Object obj -> IO ("); } else { sb.AppendFormat("{0} obj -> IO (", HsType.TypeToModuleName(fi.DeclaringType.Name)); } OutputHaskellType(sb,ref ctxt,tyArgs,fi.FieldType,0,true,false,false); if ( ctxt.Count > 0 || fi.DeclaringType.IsInterface ) { String s = "("; int i=0; foreach(KeyValuePair kv in ctxt) { s = s + m_prefix + kv.Value; if (i < (ctxt.Count - 1) ) { s = s + ", "; } i++; } if (fi.DeclaringType.IsInterface) { if (ctxt.Count > 0) { s = s + ", "; } s = s + HsType.TypeToModuleName(fi.DeclaringType.Name) + " obj"; } s = s + ") => "; sb.Insert(ctxtStartPos,s); } sb.AppendFormat("){0}",System.Environment.NewLine); } } /* * Unravel a sequence of, possibly nested, type arguments to a generic * type name, i.e., ...[A][[B]][C.D] */ private static List TypeArgs(String s) { int currentPos = 0, off = 0, startPos = 0; int nest = 1; char[] delims = new char[]{'[',']','+',',','`'}; List args = new List(); if ( (startPos = s.IndexOf('[')) < 0 ) { return args; } // skip along to the start. s = s.Substring(startPos); startPos = 1; currentPos = startPos; /* Go down the [][][][] list, keeping in mind nestings..*/ while ( currentPos < s.Length ) { if ( (off = s.Substring(currentPos).IndexOfAny(delims)) >= 0 ) { if ( s[currentPos + off] == '`' ) { // nesting...advance past the "`[" bit, increase nesting and continue.. int idx; idx = s.Substring(currentPos + off).IndexOf('['); currentPos += off + idx + 1; nest++; } else if ( s[currentPos + off] == '+' ) { // if we're at the outermost level, treat '+' as a delimiter.. if ( nest == 0 ) { return args; } else { // if not, continue.. currentPos += off; } } else if ( s[currentPos+off] == ']' || s[currentPos+off] == ',' ) { // If at the end of [..] at the outermost level, add it and // adjust starting position for next just beyond its terminating ']'. char d = s[currentPos+off]; if ( nest == 1 ) { args.Add(s.Substring(startPos,(currentPos-startPos)+off)); currentPos += off + 1; startPos = currentPos; } else { // If not, move the current position along (but keeping the // start-pos the same.) currentPos += off + 1; } if ( d != ',' ) { nest--; } } else { // a '[' nest++; // record anchor position if ( nest == 1 ) { startPos += off + 1; } currentPos += off + 1; } } else { return args; } } return args; } private GenTypeInfo LocateTypeArg(String tyName, List ps, Type tyThis, Type tyRes) { int i, j, idx; i = 0; foreach(String pStr in ps) { if ( (idx = pStr.IndexOf('`')) > 0 ) { List tArgs = TypeArgs(pStr.Substring(idx)); j = 1; foreach(String ta in tArgs) { if ( ta == tyName ) { // Console.WriteLine("p-arg: '{0}' '{1}' '{2}' {3} {4} {5}", i, pStr, IsTypeFamily(pStr),tArgs.Count,j,ta); bool isFam = IsTypeFamily(pStr); return new GenTypeInfo("arg"+i,tArgs.Count,isFam,j); } else if ( ta.Contains(tyName) ) { String tsub = ta; if ( (idx = ta.IndexOf('`')) > 0 ) { tsub = tsub.Substring(idx); } List innerPs = new List(); innerPs.Add(ta); // Console.WriteLine("---inner: {0} '{1}'", ta, tyName); GenTypeInfo inner = LocateTypeArg(tyName,innerPs,tyThis,tyRes); // if found inside, embed inside this argument (so that we // can later on unravel the type argument correctly.) if (inner != null) { // Console.WriteLine("---embedded: {0} {1} '{2}' '{3}' '{4}'", ta, tyName, "arg"+i, pStr,IsTypeFamily(pStr)); bool isFam = IsTypeFamily(pStr); GenTypeInfo gt = new GenTypeInfo("arg"+i,tArgs.Count,isFam,j); gt.NestedType = inner; GenTypeInfo sp = inner; /* normalize the arg name */ while ( sp!= null) { sp.ArgName = gt.ArgName; sp = sp.NestedType; } return gt; } } j++; } } i++; } if ( tyThis != null && (idx = tyThis.ToString().IndexOf('`')) > 0 ) { List tArgs = TypeArgs(tyThis.ToString().Substring(idx)); j = 1; foreach(String ta in tArgs) { if ( ta == tyName ) { bool isFam = IsTypeFamily(tyThis.ToString()); return new GenTypeInfo("obj",tArgs.Count,isFam,j); } else if ( ta.Contains(tyName) ) { String tsub = ta; if ( (idx = ta.IndexOf('`')) > 0 ) { tsub = tsub.Substring(idx); } List innerPs = new List(); innerPs.Add(ta); GenTypeInfo inner = LocateTypeArg(tyName,innerPs,null,tyRes); // see above comment. if (inner != null) { bool isFam = IsTypeFamily(tyThis.ToString()); // Console.WriteLine("---embedded-this: {0} {1} '{2}' '{3}' '{4}'", ta, tyName, tArgs.Count, tyThis.ToString(),isFam); GenTypeInfo gt = new GenTypeInfo("obj",tArgs.Count,isFam,j); gt.NestedType = inner; GenTypeInfo sp = inner; /* normalize the arg name */ while ( sp!= null) { sp.ArgName = gt.ArgName; sp = sp.NestedType; } return gt; } } j++; } } if ( tyRes != null && (idx = tyRes.ToString().IndexOf('`')) > 0 ) { List tArgs = TypeArgs(tyRes.ToString().Substring(idx)); j = 1; foreach(String ta in tArgs) { if ( ta == tyName ) { bool isFam = IsTypeFamily(tyRes.ToString()); return new GenTypeInfo("res",tArgs.Count,isFam,j); } else if ( ta.Contains(tyName) ) { String tsub = ta; if ( (idx = ta.IndexOf('`')) > 0 ) { tsub = tsub.Substring(idx); } List innerPs = new List(); innerPs.Add(ta); // Console.WriteLine("res: {0} {1} [ {2} ]", tyName, ta,tyRes.ToString()); GenTypeInfo inner = LocateTypeArg(tyName,innerPs,tyThis,null); // see above comment. if (inner != null) { bool isFam = IsTypeFamily(tyRes.ToString()); // Console.WriteLine("---embedded-res: {0} {1} '{2}' '{3}' '{4}'", ta, tyName, tArgs.Count, tyRes.ToString(),isFam); GenTypeInfo gt = new GenTypeInfo("res",tArgs.Count,isFam,j); gt.NestedType = inner; GenTypeInfo sp = inner; /* normalize the arg name */ while ( sp!= null) { sp.ArgName = gt.ArgName; sp = sp.NestedType; } return gt; } } j++; } } if ( tyRes != null && (idx = tyRes.ToString().IndexOf('`')) > 0 ) { List tArgs = TypeArgs(tyRes.ToString().Substring(idx)); j = 1; foreach(String ta in tArgs) { if ( ta == tyName ) { bool isFam = IsTypeFamily(tyRes.ToString()); return new GenTypeInfo("res",tArgs.Count + (isFam?0:1),isFam,j); } j++; } } #if false Console.WriteLine("WARNING!!!!: {0} '{1}' '{2}' '{3}' '{4}'", tyName, ps,tyThis,tyRes,ps.Count); foreach (String pi in ps ) { Console.Write("{0}|", pi); } Console.WriteLine(""); #endif return null; } protected String GenerateTypeMarshal(GenTypeInfo gt,String suf) { String res = suf; if ( gt == null ) { return res; } if ( gt.NestedType != null ) { // Console.WriteLine("nested: {0}", gt.ArgName); String nsuf = ""; res = GenerateTypeMarshal(gt.NestedType,nsuf); } if ( gt.TypeFamily ) { if ( gt.ArgName == "res" || (gt.ArgTyConArity >= 2 && gt.ArgTyConArity == gt.ArgTyPos) ) { return (res + (res==""? "" : " $ ") + "NET.t22{-" + gt.ArgTyConArity + " " + gt.ArgTyPos + "-} $ " + /* (gt.NestedType!=null ? " NET.t11 $ " : "") + */ "NET.t22 $ NET.t11 "); // return ("NET.t22{-" + gt.ArgTyConArity + " " + gt.ArgTyPos + "-} $ " + /* (gt.NestedType!=null ? " NET.t11 $ " : "") + */ "NET.t22 $ NET.t11 $ " + res); } else { res = (" NET.t22 $ NET.t11 " + res); if (gt.ArgTyConArity > 2 ) { if ( gt.NestedType != null ) { res = res + (res==""?"":" $ ") + "NET.t" + (gt.ArgTyConArity-1) + (gt.ArgTyPos-1) + " $ NET.t11 $ NET.t" + (gt.ArgTyConArity-1) + (gt.ArgTyPos-1); } else { ; } } } return res; } else { return (res + (res==""? "" : " $ ") + " NET.t" + (1+gt.ArgTyConArity).ToString() + (1+gt.ArgTyPos).ToString() + " $ NET.t11 "); } } protected void OutputMethod(System.Text.StringBuilder sb, System.Reflection.MethodInfo mi) { int ctxtPos; String comment = ""; Dictionary tyArgs = null; Type methType = mi.DeclaringType; String methName = ToHaskellName(mi.Name); ParameterInfo[] ps = mi.GetParameters(); if ( mi.ContainsGenericParameters ) { tyArgs = new Dictionary(); int i = 0; Type[] gts = mi.GetGenericArguments(); List pts = new List(); foreach(ParameterInfo pi in ps) { // Console.WriteLine("gen-param {0} '{1}' '{2}'", mi, pi.ParameterType.ToString(), methName); pts.Add(pi.ParameterType.ToString()); } // For each of the generic type variables, locate what Haskell argument // will be parameterized over it & record its properties (type arg arity and position // of argument.) foreach (Type t in gts) { //Console.WriteLine("gen-arg {0} '{1}' '{2}' '{3}'", mi, i, t.Name, t); GenTypeInfo tyInfo = LocateTypeArg(t.ToString(), pts, (mi.IsStatic ? null : mi.DeclaringType), mi.ReturnType); tyArgs.Add(t.ToString(),tyInfo); i++; } if ( mi.ReturnType.ContainsGenericParameters ) { gts = mi.ReturnType.GetGenericArguments(); List inp = new List(gts); List targs = new List(); pts = new List(); foreach(ParameterInfo pi in ps) { pts.Add(pi.ParameterType.ToString()); } while (inp.Count > 0) { if ( inp[0].IsGenericParameter ) { if ( !targs.Contains(inp[0]) ) { targs.Add(inp[0]); } } else if ( inp[0].ContainsGenericParameters ) { Type[] its = inp[0].GetGenericArguments(); foreach (Type it in its) { inp.Add(it); } } inp.RemoveAt(0); } foreach (Type t in targs) { // Console.WriteLine("R-gen-arg {0} '{1}' '{2}' '{3}'", mi, mi.ReturnType,t.Name, tyArgs.ContainsKey(t.ToString())); if ( !tyArgs.ContainsKey(t.ToString()) /* && !m_tyGenericArgs.ContainsKey(t.ToString()) */ ) { GenTypeInfo tyInfo = LocateTypeArg(t.ToString(), pts, null, mi.ReturnType); // Console.WriteLine("R-gen-arg {0} '{1}' '{2}' '{3}'", t.ToString(), tyInfo.ArgName, tyInfo.ArgTyConArity, tyInfo.ArgTyPos); tyArgs.Add(t.ToString(),tyInfo); } } } } ctxtPos = sb.Length; sb.AppendFormat("{0} :: ", methName); OutputMethodSig(sb,tyArgs,mi); if ( !m_okSig ) { comment = "-- "; sb.Insert(ctxtPos, comment); m_okSig = true; } sb.AppendFormat("{1}{0}", methName, comment); if (ps.Length > 0) { int i = 0; if ( mi.IsDefined(typeof(ExtensionAttribute),true) ) { i = 1; } for (;i < ps.Length; i++) { sb.AppendFormat(" arg{0}", i); } if ( mi.IsDefined(typeof(ExtensionAttribute),true) ) { sb.Append(" arg0"); } } if ( !mi.IsStatic ) { sb.Append(" obj"); } sb.Append(" ="); String argStr = ""; for(int j=0;j>= \\ arg"+j+" -> "; } } sb.Append(argStr); argStr = ""; if ( ps.Length == 0 ) { argStr = " () "; } else { argStr += " ("; for (int i=0;i < ps.Length; i++) { argStr += String.Format("arg{0}", i); if ( (i+1) < ps.Length ) { argStr += ", "; } } argStr += ")"; } if ( !mi.IsStatic ) { argStr += " obj"; } String tyApp = ""; bool needRes = false; if ( m_tyArgs.Length > 0 || (tyArgs != null && tyArgs.Count > 0) ) { tyApp += "("; foreach (KeyValuePair kv in m_tyGenericArgs) { // Console.WriteLine("{0} = ({1},{2})", kv.Key, kv.Value.ArgName, kv.Value.ArgTyConArity); String arg = kv.Value.ArgName; GenTypeInfo gt = kv.Value; String marsh = GenerateTypeMarshal(gt,""); // Can't consult the 'this' parameter for a static method; continue.. if ( arg == "obj" && mi.IsStatic ) { continue; } if ( gt.ArgName == "res" ) { needRes = true; tyApp += " NET.tyNameCons (" + marsh + " $ NET.t11 res) $ "; } else { tyApp += " NET.tyNameCons (" + marsh + arg + ") $ "; } } if ( tyArgs != null && tyArgs.Count > 0 ) { tyApp += " NET.tyMethSplit $ "; foreach (KeyValuePair kv in tyArgs) { // Console.WriteLine("meth-ty: {0} = ({1},{2})", mi.Name, kv.Key, (kv.Value!=null ? kv.Value.ArgName : "")); String arg = kv.Value.ArgName; String marsh = GenerateTypeMarshal(kv.Value,""); // If evidence for the type var has been provided at the // class/type level, skip it here. // Console.WriteLine("meth-ty: {0} = ({1},{2}){3}", mi.Name, kv.Key, kv.Value.ArgName, marsh); if ( m_tyGenericArgs.ContainsKey(kv.Key) ) { if ( !mi.IsStatic || m_tyGenericArgs[kv.Key].ArgName != "obj" ) { continue; } } // Can't consult the 'this' parameter for a static method; continue.. if ( arg == "obj" && mi.IsStatic ) { continue; } if ( kv.Value.ArgName == "res" ) { needRes = true; tyApp += " NET.tyNameCons (" + marsh + " $ NET.t11 res) $ "; } else { tyApp += " NET.tyNameCons (" + marsh + arg + ") $ "; } } // Console.WriteLine("meth-res: {0}", tyApp); } tyApp += " [])"; } if (mi.IsStatic) { if ( tyApp != "" ) { if ( needRes ) { sb.AppendFormat(" let res = NET.invokeGenericStatic \"{0}\" \"{1}\" {2} {3} in res", methType, mi.Name, tyApp,argStr); } else { sb.AppendFormat(" NET.invokeGenericStatic \"{0}\" \"{1}\" {2} {3}", methType, mi.Name, tyApp, argStr); } } else { sb.AppendFormat(" NET.invokeStatic \"{0}\" \"{1}\" {2}", methType, mi.Name, argStr); } } else { if ( tyApp != "" ) { if ( needRes ) { sb.AppendFormat(" let res = NET.invokeGeneric \"{0}\" {1} {2} in res", mi.Name, tyApp,argStr); } else { sb.AppendFormat(" NET.invokeGeneric \"{0}\" {1} {2}", mi.Name, tyApp, argStr); } } else { sb.AppendFormat(" NET.invoke \"{0}\" {1}", mi.Name, argStr); } } sb.Append(System.Environment.NewLine); sb.Append(System.Environment.NewLine); } protected void OutputField(System.Text.StringBuilder sb, System.Reflection.FieldInfo f) { String comment = ""; System.String fieldName = f.Name; int ctxtPos = sb.Length; Dictionary tyArgs = null; if ( f.FieldType.GetGenericArguments().Length > 0 ) { tyArgs = new Dictionary(); int i = 0; Type[] ts = f.GetType().GetGenericArguments(); List pts = new List(); foreach (Type t in ts) { // Console.WriteLine("gen-arg {0} '{1}' '{2}' '{3}'", f, i, t.Name, t); GenTypeInfo tyInfo = LocateTypeArg(t.ToString(), pts,(f.IsStatic ? null : f.DeclaringType), f.FieldType); tyArgs.Add(t.ToString(),tyInfo); i++; } } sb.AppendFormat("get_{0} :: ", fieldName); OutputFieldSig(sb,f,tyArgs,false); if ( !m_okSig ) { comment = "-- "; sb.Insert(ctxtPos, comment); m_okSig = true; } if ( f.IsStatic ) { sb.AppendFormat("{3}get_{0} = NET.getFieldStatic \"{1}\" \"{2}\" ()", fieldName, f.DeclaringType, f.Name, comment); } else { sb.AppendFormat("{2}get_{0} = NET.getField \"{1}\" ()", fieldName, f.Name, comment); } sb.Append(System.Environment.NewLine); if (!f.IsInitOnly) { sb.AppendFormat("{1}set_{0} :: ", fieldName, comment); OutputFieldSig(sb,f,tyArgs,true); if ( f.IsStatic ) { sb.AppendFormat("{3}set_{0} = NET.setFieldStatic \"{1}\" \"{2}\"", fieldName, f.DeclaringType, f.Name, comment); } else { sb.AppendFormat("{3}set_{0} = NET.setField \"{1}\" \"{2}\"", fieldName, f.DeclaringType, f.Name, comment); } sb.Append(System.Environment.NewLine); } } protected void OutputMember(System.Text.StringBuilder sb, System.Reflection.MemberInfo mi) { m_okSig = true; switch (mi.MemberType) { case System.Reflection.MemberTypes.Method: OutputMethod(sb,(System.Reflection.MethodInfo)mi); break; case System.Reflection.MemberTypes.Field: OutputField(sb,(System.Reflection.FieldInfo)mi); break; default: break; } } protected void OutputFieldName(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 = null; System.IO.StreamWriter st = null; System.Text.StringBuilder sb = new System.Text.StringBuilder(); if ( !Config.DryRunMode ) { // Console.WriteLine("IO=> opening file (w): {0}", fn); fs = new System.IO.FileStream(fn,System.IO.FileMode.Create); st = new System.IO.StreamWriter(fs,System.Text.Encoding.ASCII); } /* If an open generic type, record the type names it is parameterised over..*/ if ( m_type.IsGenericType ) { Type[] ts = m_type.GetGenericArguments(); m_tyArgs += " "; for(int i=0;i pts = new List(); m_tyArgs += " g" + ts[i].ToString(); // Console.WriteLine("Locating: '{0}' '{1}'", ts[i].ToString(), m_type); GenTypeInfo gt = LocateTypeArg(ts[i].ToString(),pts,m_type,m_type); // Console.WriteLine("Located: '{0}' '{1}' '{2}' '{3}' '{4}'", ts[i].ToString(), m_type, gt.ArgName, gt.ArgTyConArity, gt.TypeFamily); m_tyGenericArgs.Add(ts[i].ToString(),gt); } } if ( !m_type.IsInterface && m_type.BaseType != null && m_type.BaseType.FullName == "System.Enum" ) { /* enumerations are mapped onto Haskell data types. */ if ( m_isTyMod ) { if ( !Config.DryRunMode ) { 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); OutputFieldName(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"); if ( !Config.DryRunMode ) { 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); if ( !Config.DryRunMode ) { // Output the method/field wrappers. st.WriteLine(sb.ToString()); st.Flush(); st.Close(); fs.Close(); } } }; }