From bf0f42be82695bf72d69958ada7a70043e971c54 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Fri, 1 Nov 2013 09:27:46 +0100 Subject: [PATCH] Avoid singletons; collect code generation ISpecWriter implementations must now store explicit references to a Settings object. Additionally, all code generation is now handled inside the ISpecWriter implementation (it used to be scattered over all Type, Parameter, etc classes resulting in unmaintainable spaghetti code.) --- Source/Bind/CSharpSpecWriter.cs | 566 +++++++++++++++++++++++++++++++- 1 file changed, 550 insertions(+), 16 deletions(-) diff --git a/Source/Bind/CSharpSpecWriter.cs b/Source/Bind/CSharpSpecWriter.cs index 0fb396b1..1191114d 100644 --- a/Source/Bind/CSharpSpecWriter.cs +++ b/Source/Bind/CSharpSpecWriter.cs @@ -42,14 +42,34 @@ namespace Bind sealed class CSharpSpecWriter : ISpecWriter { readonly char[] numbers = "0123456789".ToCharArray(); + IBind Generator { get; set; } + Settings Settings { get { return Generator.Settings; } } - #region WriteBindings + #region ISpecWriter Members public void WriteBindings(IBind generator) { + Generator = generator; WriteBindings(generator.Delegates, generator.Wrappers, generator.Enums); } + #endregion + + #region Private Members + + private static void ConsoleRewrite(string text) + { + int left = Console.CursorLeft; + int top = Console.CursorTop; + Console.Write(text); + for (int i = text.Length; i < 80; i++) + Console.Write(" "); + Console.WriteLine(); + Console.SetCursorPosition(left, top); + } + + #region WriteBindings + void WriteBindings(DelegateCollection delegates, FunctionCollection wrappers, EnumCollection enums) { Console.WriteLine("Writing bindings to {0}", Settings.OutputPath); @@ -143,7 +163,7 @@ namespace Bind sw.WriteLine("using System.Text;"); sw.WriteLine("using System.Runtime.InteropServices;"); - WriteWrappers(sw, wrappers, Type.CSTypes); + WriteWrappers(sw, wrappers, enums, Generator.CSTypes); sw.Unindent(); sw.WriteLine("}"); @@ -188,7 +208,7 @@ namespace Bind foreach (Delegate d in delegates.Values) { sw.WriteLine("[System.Security.SuppressUnmanagedCodeSecurity()]"); - sw.WriteLine("internal {0};", d.ToString()); + sw.WriteLine("internal {0};", GetDeclarationString(d, true)); sw.WriteLine("internal {0}static {1} {2}{1};", // = null d.Unsafe ? "unsafe " : "", d.Name, @@ -233,7 +253,7 @@ namespace Bind d.Name, d.Name.EndsWith("W") || d.Name.EndsWith("A") ? ", CharSet = CharSet.Auto" : ", ExactSpelling = true" ); - sw.WriteLine("internal extern static {0};", d.DeclarationString()); + sw.WriteLine("internal extern static {0};", GetDeclarationString(d, false)); } sw.Unindent(); sw.WriteLine("}"); @@ -245,7 +265,7 @@ namespace Bind #region WriteWrappers - public void WriteWrappers(BindStreamWriter sw, FunctionCollection wrappers, Dictionary CSTypes) + void WriteWrappers(BindStreamWriter sw, FunctionCollection wrappers, EnumCollection enums, IDictionary CSTypes) { Trace.WriteLine(String.Format("Writing wrappers to:\t{0}.{1}", Settings.OutputNamespace, Settings.OutputClass)); @@ -283,7 +303,7 @@ namespace Bind wrappers[key].Sort(); foreach (Function f in wrappers[key]) { - current = WriteWrapper(sw, current, f); + current = WriteWrapper(sw, current, f, enums); } if (((Settings.Compatibility & Settings.Legacy.NoSeparateFunctionNamespaces) == Settings.Legacy.None) && key != "Core") @@ -297,19 +317,23 @@ namespace Bind sw.WriteLine("}"); } - int WriteWrapper(BindStreamWriter sw, int current, Function f) + int WriteWrapper(BindStreamWriter sw, int current, Function f, EnumCollection enums) { if ((Settings.Compatibility & Settings.Legacy.NoDocumentation) == 0) { - Console.WriteLine("Creating docs for #{0} ({1})", current++, f.Name); + string text = String.Format("Writing function #{0}: {1}", current++, f.Name); + ConsoleRewrite(text); + WriteDocumentation(sw, f); } - WriteMethod(sw, f); + WriteMethod(sw, f, enums); return current; } - private static void WriteMethod(BindStreamWriter sw, Function f) + private void WriteMethod(BindStreamWriter sw, Function f, EnumCollection enums) { + CreateBody(f, enums); + if (f.Deprecated && Settings.IsEnabled(Settings.Legacy.AddDeprecationWarnings)) { sw.WriteLine("[Obsolete(\"Deprecated in OpenGL {0}\")]", f.DeprecatedVersion); @@ -323,12 +347,21 @@ namespace Bind sw.WriteLine("[AutoGenerated(Category = \"{0}\", Version = \"{1}\", EntryPoint = \"{2}\")]", f.Category, f.Version, Settings.FunctionPrefix + f.WrappedDelegate.Name); sw.WriteLine("public static "); - sw.Write(f); + sw.Write(GetDeclarationString(f)); sw.WriteLine(); } - static DocProcessor processor = new DocProcessor(Path.Combine(Settings.DocPath, Settings.DocFile)); - static Dictionary docfiles; + DocProcessor processor_; + DocProcessor Processor + { + get + { + if (processor_ == null) + processor_ = new DocProcessor(Path.Combine(Settings.DocPath, Settings.DocFile)); + return processor_; + } + } + Dictionary docfiles; void WriteDocumentation(BindStreamWriter sw, Function f) { if (docfiles == null) @@ -352,7 +385,7 @@ namespace Bind string doc = null; if (docfiles.ContainsKey(docfile)) { - doc = processor.ProcessFile(docfiles[docfile]); + doc = Processor.ProcessFile(docfiles[docfile]); } if (doc == null) { @@ -456,8 +489,12 @@ namespace Bind sw.Indent(); } + int current = 0; foreach (Enum @enum in enums.Values) { + string text = String.Format("Writing enum #{0}: {1}", current++, @enum.Name); + ConsoleRewrite(text); + if (!Settings.IsEnabled(Settings.Legacy.NoDocumentation)) { // Document which functions use this enum. @@ -470,8 +507,16 @@ namespace Bind .Distinct(); sw.WriteLine("/// "); - sw.WriteLine(String.Format("/// {0}", functions.Count() > 0 ? - ("Used in " + String.Join(", ", functions.ToArray())) : "Not used directly.")); + sw.WriteLine(String.Format("/// {0}", + functions.Count() >= 3 ? + String.Format("Used in {0} and {1} other function{2}", + String.Join(", ", functions.Take(2).ToArray()), + functions.Count() - 2, + functions.Count() - 2 > 1 ? "s" : "") : + functions.Count() >= 1 ? + String.Format("Used in {0}", + String.Join(", ", functions.ToArray())) : + "Not used directly.")); sw.WriteLine("/// "); } @@ -525,5 +570,494 @@ namespace Bind } #endregion + + // For example, if parameter foo has indirection level = 1, then it + // is consumed as 'foo*' in the fixed_statements and the call string. + readonly static string[] pointer_levels = new string[] { "", "*", "**", "***", "****" }; + readonly static string[] array_levels = new string[] { "", "[]", "[,]", "[,,]", "[,,,]" }; + + static bool IsEnum(string s, EnumCollection enums) + { + return enums.ContainsKey(s); + } + + void CreateBody(Function func, EnumCollection enums) + { + Function f = new Function(func); + f.Body.Clear(); + + var handle_statements = new List(); + var handle_release_statements = new List(); + var fixed_statements = new List(); + var assign_statements = new List(); + + // Obtain pointers by pinning the parameters + int index = -1; + foreach (Parameter p in f.Parameters) + { + index++; + if (p.NeedsPin) + { + if (p.WrapperType == WrapperTypes.GenericParameter) + { + // Use GCHandle to obtain pointer to generic parameters and 'fixed' for arrays. + // This is because fixed can only take the address of fields, not managed objects. + handle_statements.Add(String.Format( + "{0} {1}_ptr = {0}.Alloc({1}, GCHandleType.Pinned);", + "GCHandle", p.Name)); + + handle_release_statements.Add(String.Format("{0}_ptr.Free();", p.Name)); + + // Due to the GCHandle-style pinning (which boxes value types), we need to assign the modified + // value back to the reference parameter (but only if it has an out or in/out flow direction). + if ((p.Flow == FlowDirection.Out || p.Flow == FlowDirection.Undefined) && p.Reference) + { + assign_statements.Add(String.Format( + "{0} = ({1}){0}_ptr.Target;", + p.Name, p.QualifiedType)); + } + + // Note! The following line modifies f.Parameters, *not* this.Parameters + p.Name = "(IntPtr)" + p.Name + "_ptr.AddrOfPinnedObject()"; + } + else if (p.WrapperType == WrapperTypes.PointerParameter || + p.WrapperType == WrapperTypes.ArrayParameter || + p.WrapperType == WrapperTypes.ReferenceParameter) + { + // A fixed statement is issued for all non-generic pointers, arrays and references. + fixed_statements.Add(String.Format( + "fixed ({0}{3} {1} = {2})", + p.QualifiedType, + p.Name + "_ptr", + p.Array > 0 ? p.Name : "&" + p.Name, + pointer_levels[p.IndirectionLevel])); + + // Arrays are not value types, so we don't need to do anything for them. + // Pointers are passed directly by value, so we don't need to assign them back either (they don't change). + if ((p.Flow == FlowDirection.Out || p.Flow == FlowDirection.Undefined) && p.Reference) + { + assign_statements.Add(String.Format("{0} = *{0}_ptr;", p.Name)); + } + + p.Name = p.Name + "_ptr"; + } + else + { + throw new ApplicationException("Unknown parameter type"); + } + } + + p.QualifiedType = f.WrappedDelegate.Parameters[index].QualifiedType; + } + + f.Body.Indent(); + + // Automatic OpenGL error checking. + // See OpenTK.Graphics.ErrorHelper for more information. + // Make sure that no error checking is added to the GetError function, + // as that would cause infinite recursion! + if ((Settings.Compatibility & Settings.Legacy.NoDebugHelpers) == 0) + { + if (f.TrimmedName != "GetError") + { + f.Body.Add("#if DEBUG"); + f.Body.Add("using (new ErrorHelper(GraphicsContext.CurrentContext))"); + f.Body.Add("{"); + if (f.TrimmedName == "Begin") + f.Body.Add("GraphicsContext.CurrentContext.ErrorChecking = false;"); + f.Body.Add("#endif"); + } + } + + if (!f.Unsafe && fixed_statements.Count > 0) + { + f.Body.Add("unsafe"); + f.Body.Add("{"); + f.Body.Indent(); + } + + if (fixed_statements.Count > 0) + { + f.Body.AddRange(fixed_statements); + f.Body.Add("{"); + f.Body.Indent(); + } + + if (handle_statements.Count > 0) + { + f.Body.AddRange(handle_statements); + f.Body.Add("try"); + f.Body.Add("{"); + f.Body.Indent(); + } + + // Hack: When creating untyped enum wrappers, it is possible that the wrapper uses an "All" + // enum, while the delegate uses a specific enum (e.g. "TextureUnit"). For this reason, we need + // to modify the parameters before generating the call string. + // Note: We cannot generate a callstring using WrappedDelegate directly, as its parameters will + // typically be different than the parameters of the wrapper. We need to modify the parameters + // of the wrapper directly. + if ((Settings.Compatibility & Settings.Legacy.KeepUntypedEnums) != 0) + { + int parameter_index = -1; // Used for comparing wrapper parameters with delegate parameters + foreach (Parameter p in f.Parameters) + { + parameter_index++; + if (IsEnum(p.Name, enums) && p.QualifiedType != f.WrappedDelegate.Parameters[parameter_index].QualifiedType) + { + p.QualifiedType = f.WrappedDelegate.Parameters[parameter_index].QualifiedType; + } + } + } + + if (assign_statements.Count > 0) + { + // Call function + string callstring = GetInvocationString(f); + if (f.ReturnType.CurrentType.ToLower().Contains("void")) + { + f.Body.Add(String.Format("{0};", callstring)); + } + else if (func.ReturnType.CurrentType.ToLower().Contains("string")) + { + f.Body.Add(String.Format("{0} {1} = null; unsafe {{ {1} = new string((sbyte*){2}); }}", + func.ReturnType.QualifiedType, "retval", callstring)); + } + else + { + f.Body.Add(String.Format("{0} {1} = {2};", + GetDeclarationString(f.ReturnType), "retval", callstring)); + } + + // Assign out parameters + f.Body.AddRange(assign_statements); + + // Return + if (!f.ReturnType.CurrentType.ToLower().Contains("void")) + { + f.Body.Add("return retval;"); + } + } + else + { + // Call function and return + var callstring = GetInvocationString(f); + if (f.ReturnType.CurrentType.ToLower().Contains("void")) + { + f.Body.Add(String.Format("{0};", callstring)); + } + else if (func.ReturnType.CurrentType.ToLower().Contains("string")) + { + f.Body.Add(String.Format("unsafe {{ return new string((sbyte*){0}); }}", + callstring)); + } + else + { + f.Body.Add(String.Format("return {0};", callstring)); + } + } + + // Free all allocated GCHandles + if (handle_statements.Count > 0) + { + f.Body.Unindent(); + f.Body.Add("}"); + f.Body.Add("finally"); + f.Body.Add("{"); + f.Body.Indent(); + + f.Body.AddRange(handle_release_statements); + + f.Body.Unindent(); + f.Body.Add("}"); + } + + if (!f.Unsafe && fixed_statements.Count > 0) + { + f.Body.Unindent(); + f.Body.Add("}"); + } + + if (fixed_statements.Count > 0) + { + f.Body.Unindent(); + f.Body.Add("}"); + } + + if ((Settings.Compatibility & Settings.Legacy.NoDebugHelpers) == 0) + { + if (f.TrimmedName != "GetError") + { + f.Body.Add("#if DEBUG"); + if (f.TrimmedName == "End") + f.Body.Add("GraphicsContext.CurrentContext.ErrorChecking = true;"); + f.Body.Add("}"); + f.Body.Add("#endif"); + } + } + + f.Body.Unindent(); + + func.Body = f.Body; + } + + string GetDeclarationString(Constant c) + { + if (String.IsNullOrEmpty(c.Name)) + { + throw new InvalidOperationException("Invalid Constant: Name is empty"); + } + + return String.Format("{0} = {1}((int){2}{3})", + c.Name, + c.Unchecked ? "unchecked" : String.Empty, + !String.IsNullOrEmpty(c.Reference) ? + c.Reference + Settings.NamespaceSeparator : + String.Empty, + c.Value); + } + + string GetDeclarationString(Delegate d, bool is_delegate) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(d.Unsafe ? "unsafe " : ""); + if (is_delegate) + sb.Append("delegate "); + sb.Append(GetDeclarationString(d.ReturnType)); + sb.Append(" "); + sb.Append(d.Name); + sb.Append(GetDeclarationString(d.Parameters)); + + return sb.ToString(); + } + + string GetDeclarationString(Enum e) + { + StringBuilder sb = new StringBuilder(); + List constants = new List(e.ConstantCollection.Values); + constants.Sort(delegate(Constant c1, Constant c2) + { + int ret = String.Compare(c1.Value, c2.Value); + if (ret == 0) + return String.Compare(c1.Name, c2.Name); + return ret; + }); + + if (e.IsFlagCollection) + sb.AppendLine("[Flags]"); + sb.Append("public enum "); + sb.Append(e.Name); + sb.Append(" : "); + sb.AppendLine(e.Type); + sb.AppendLine("{"); + + foreach (Constant c in constants) + { + var declaration = GetDeclarationString(c); + sb.Append(" "); + sb.Append(declaration); + if (!String.IsNullOrEmpty(declaration)) + sb.AppendLine(","); + } + sb.Append("}"); + + return sb.ToString(); + } + + string GetDeclarationString(Function f) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(f.Unsafe ? "unsafe " : ""); + sb.Append(GetDeclarationString(f.ReturnType)); + sb.Append(" "); + if ((Settings.Compatibility & Settings.Legacy.NoTrimFunctionEnding) != Settings.Legacy.None) + { + sb.Append(Settings.FunctionPrefix); + } + sb.Append(!String.IsNullOrEmpty(f.TrimmedName) ? f.TrimmedName : f.Name); + + if (f.Parameters.HasGenericParameters) + { + sb.Append("<"); + foreach (Parameter p in f.Parameters) + { + if (p.Generic) + { + sb.Append(p.CurrentType); + sb.Append(","); + } + } + sb.Remove(sb.Length - 1, 1); + sb.Append(">"); + } + sb.AppendLine(GetDeclarationString(f.Parameters)); + if (f.Parameters.HasGenericParameters) + { + foreach (Parameter p in f.Parameters) + { + if (p.Generic) + sb.AppendLine(String.Format(" where {0} : struct", p.CurrentType)); + } + } + + sb.AppendLine("{"); + foreach (var line in f.Body) + { + sb.AppendLine(line); + } + sb.Append("}"); + + return sb.ToString(); + } + + string GetDeclarationString(Parameter p, bool override_unsafe_setting) + { + bool unsafe_allowed = override_unsafe_setting; + + StringBuilder sb = new StringBuilder(); + + if (p.Flow == FlowDirection.Out) + sb.Append("[OutAttribute] "); + else if (p.Flow == FlowDirection.Undefined) + sb.Append("[InAttribute, OutAttribute] "); + + if (p.Reference) + { + if (p.Flow == FlowDirection.Out) + sb.Append("out "); + else + sb.Append("ref "); + } + + if (!override_unsafe_setting && ((Settings.Compatibility & Settings.Legacy.NoPublicUnsafeFunctions) != Settings.Legacy.None)) + { + if (p.Pointer != 0) + { + sb.Append("IntPtr"); + } + else + { + sb.Append(GetDeclarationString(p as Type)); + } + } + else + { + sb.Append(GetDeclarationString(p as Type)); + } + if (!String.IsNullOrEmpty(p.Name)) + { + sb.Append(" "); + sb.Append(p.Name); + } + + return sb.ToString(); + } + + string GetDeclarationString(ParameterCollection parameters) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("("); + if (parameters.Count > 0) + { + foreach (Parameter p in parameters) + { + sb.Append(GetDeclarationString(p, false)); + sb.Append(", "); + } + sb.Replace(", ", ")", sb.Length - 2, 2); + } + else + { + sb.Append(")"); + } + + return sb.ToString(); + } + + string GetDeclarationString(Type type) + { + return String.Format("{0}{1}{2}", + type.QualifiedType, + pointer_levels[type.Pointer], + array_levels[type.Array]); + } + + string GetInvocationString(Delegate d) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(Settings.DelegatesClass); + sb.Append(Settings.NamespaceSeparator); + sb.Append(Settings.FunctionPrefix); + sb.Append(d.Name); + sb.Append(GetInvocationString(d.Parameters)); + + return sb.ToString(); + } + + string GetInvocationString(ParameterCollection parameters) + { + StringBuilder sb = new StringBuilder(); + + sb.Append("("); + + if (parameters.Count > 0) + { + foreach (Parameter p in parameters) + { + if (p.Unchecked) + sb.Append("unchecked((" + p.QualifiedType + ")"); + + if (!p.Generic && p.CurrentType != "object") + { + if (p.CurrentType.ToLower().Contains("string")) + { + sb.Append(String.Format("({0}{1})", + p.QualifiedType, (p.Array > 0) ? "[]" : "")); + } + else if (p.IndirectionLevel != 0) + { + if (((Settings.Compatibility & Settings.Legacy.TurnVoidPointersToIntPtr) != Settings.Legacy.None) && + p.Pointer != 0 && p.CurrentType.Contains("void")) + { + sb.Append("(IntPtr)"); + } + else + { + sb.Append("("); + + sb.Append(p.QualifiedType); + for (int i = 0; i < p.IndirectionLevel; i++) + sb.Append("*"); + sb.Append(")"); + } + } + else + { + sb.Append(String.Format("({0})", p.QualifiedType)); + } + } + + sb.Append(p.Name); + + if (p.Unchecked) + sb.Append(")"); + + sb.Append(", "); + } + sb.Replace(", ", ")", sb.Length - 2, 2); + } + else + { + sb.Append(")"); + } + + return sb.ToString(); + } + + #endregion } }