#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2010 the Open Toolkit library. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml.XPath; using Bind.Structures; using Delegate = Bind.Structures.Delegate; namespace Bind { using Enum = Bind.Structures.Enum; using Type = Bind.Structures.Type; class FuncProcessor { static readonly Regex Endings = new Regex(@"((((d|f|fi)|(L?(u?i?64)?u?[isb]))_?(64)?v?)|v)", RegexOptions.Compiled | RegexOptions.RightToLeft); static readonly Regex EndingsNotToTrim = new Regex("(ib|[tdrey]s|[eE]n[vd]|bled|Attrib|Access|Coord|Flag|Tess|Status|Pixels|Instanced|Indexed|Varyings|Boolean|IDs|Uniforms)", RegexOptions.Compiled | RegexOptions.RightToLeft); static readonly Regex EndingsAddV = new Regex("^0", RegexOptions.Compiled); string Overrides { get; set; } IBind Generator { get; set; } Settings Settings { get { return Generator.Settings; } } public FuncProcessor(IBind generator, string overrides) { if (generator == null) throw new ArgumentNullException("generator"); if (overrides == null) throw new ArgumentNullException("overrides"); Generator = generator; Overrides = overrides; } public FunctionCollection Process(EnumProcessor enum_processor, DelegateCollection delegates, EnumCollection enums, string apiname) { Console.WriteLine("Processing delegates."); var nav = new XPathDocument(Overrides).CreateNavigator(); foreach (var d in delegates.Values) { TranslateExtension(d); TranslateReturnType(enum_processor, nav, d, enums, apiname); TranslateParameters(enum_processor, nav, d, enums, apiname); TranslateAttributes(nav, d, enums, apiname); } Console.WriteLine("Generating wrappers."); var wrappers = CreateWrappers(delegates, enums); Console.WriteLine("Creating CLS compliant overloads."); wrappers = CreateCLSCompliantWrappers(wrappers, enums); Console.WriteLine("Removing non-CLS compliant duplicates."); return MarkCLSCompliance(wrappers); } public static string GetOverridesPath(string apiname, string function, string extension) { if (function == null) throw new ArgumentNullException("function"); var path = new StringBuilder(); path.Append("/signatures/replace"); if (apiname != null) { path.Append(String.Format("[contains(concat('|', @name, '|'), '|{0}|')]", apiname)); } if (extension != null) { path.Append(String.Format( "/function[contains(concat('|', @name, '|'), '|{0}|') and contains(concat('|', @extension, '|'), '|{1}|')]", function, extension)); } else { path.Append(String.Format( "/function[contains(concat('|', @name, '|'), '|{0}|')]", function)); } return path.ToString(); } #region Private Members void TranslateType(Bind.Structures.Type type, EnumProcessor enum_processor, XPathNavigator overrides, EnumCollection enums, string category, string apiname) { Bind.Structures.Enum @enum; string s; category = enum_processor.TranslateEnumName(category); // Try to find out if it is an enum. If the type exists in the normal GLEnums list, use this. // Special case for Boolean - it is an enum, but it is dumb to use that instead of the 'bool' type. bool normal = enums.TryGetValue(type.CurrentType, out @enum); // Translate enum types if (normal && @enum.Name != "GLenum" && @enum.Name != "Boolean") { if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) != Settings.Legacy.None) { type.QualifiedType = "int"; } else { // Some functions and enums have the same names. // Make sure we reference the enums rather than the functions. if (normal) type.QualifiedType = type.CurrentType.Insert(0, String.Format("{0}.", Settings.EnumsOutput)); } } else if (Generator.GLTypes.TryGetValue(type.CurrentType, out s)) { // Check if the parameter is a generic GLenum. If it is, search for a better match, // otherwise fallback to Settings.CompleteEnumName (named 'All' by default). if (s.Contains("GLenum") /*&& !String.IsNullOrEmpty(category)*/) { if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) != Settings.Legacy.None) { type.QualifiedType = "int"; } else { // Better match: enum.Name == function.Category (e.g. GL_VERSION_1_1 etc) if (enums.ContainsKey(category)) { type.QualifiedType = String.Format("{0}{1}{2}", Settings.EnumsOutput, Settings.NamespaceSeparator, enum_processor.TranslateEnumName(category)); } else { type.QualifiedType = String.Format("{0}{1}{2}", Settings.EnumsOutput, Settings.NamespaceSeparator, Settings.CompleteEnumName); } } } else { // Todo: what is the point of this here? It is overwritten below. // A few translations for consistency switch (type.CurrentType.ToLower()) { case "string": type.QualifiedType = "String"; break; } type.QualifiedType = s; } } type.CurrentType = Generator.CSTypes.ContainsKey(type.CurrentType) ? Generator.CSTypes[type.CurrentType] : type.CurrentType; // Make sure that enum parameters follow enum overrides, i.e. // if enum ErrorCodes is overriden to ErrorCode, then parameters // of type ErrorCodes should also be overriden to ErrorCode. XPathNavigator enum_override = overrides.SelectSingleNode( EnumProcessor.GetOverridesPath(apiname, type.CurrentType)); if (enum_override != null) { // For consistency - many overrides use string instead of String. if (enum_override.Value == "string") type.QualifiedType = "String"; else if (enum_override.Value == "StringBuilder") type.QualifiedType = "StringBuilder"; else type.CurrentType = enum_override.Value; } if (type.CurrentType == "IntPtr" && String.IsNullOrEmpty(type.PreviousType)) type.Pointer = 0; if (type.Pointer >= 3) { System.Diagnostics.Trace.WriteLine(String.Format( "[Error] Type '{0}' has a high pointer level. Bindings will be incorrect.", type)); } } void TranslateExtension(Delegate d) { var extension = d.Extension.ToUpper(); if (extension.Length > 2) { extension = extension[0] + extension.Substring(1).ToLower(); } d.Extension = extension; } static string GetTrimmedExtension(string name, string extension) { // Extensions are always uppercase int index = name.LastIndexOf(extension.ToUpper()); if (index >= 0) { name = name.Remove(index); } return name; } // Trims unecessary suffices from the specified OpenGL function name. static string GetTrimmedName(Delegate d) { string name = d.Name; string extension = d.Extension; string trimmed_name = GetTrimmedExtension(name, extension); // Note: some endings should not be trimmed, for example: 'b' from Attrib. // Check the endingsNotToTrim regex for details. Match m = EndingsNotToTrim.Match(trimmed_name); if ((m.Index + m.Length) != trimmed_name.Length) { m = Endings.Match(trimmed_name); if (m.Length > 0 && m.Index + m.Length == trimmed_name.Length) { // Only trim endings, not internal matches. if (m.Value[m.Length - 1] == 'v' && EndingsAddV.IsMatch(name) && !name.StartsWith("Get") && !name.StartsWith("MatrixIndex")) { // Only trim ending 'v' when there is a number trimmed_name = trimmed_name.Substring(0, m.Index) + "v"; } else { if (!trimmed_name.EndsWith("xedv")) { trimmed_name = trimmed_name.Substring(0, m.Index); } else { trimmed_name = trimmed_name.Substring(0, m.Index + 1); } } } } return trimmed_name; } static XPathNavigator GetFuncOverride(XPathNavigator nav, Delegate d, string apiname) { string ext = d.Extension; string trimmed_name = GetTrimmedName(d); string extensionless_name = GetTrimmedExtension(d.Name, ext); var function_override = nav.SelectSingleNode(GetOverridesPath(apiname, d.Name, ext)) ?? nav.SelectSingleNode(GetOverridesPath(apiname, extensionless_name, ext)) ?? nav.SelectSingleNode(GetOverridesPath(apiname, trimmed_name, ext)); return function_override; } void TrimName(Function f) { f.TrimmedName = GetTrimmedName(f); } // Translates the opengl return type to the equivalent C# type. // // First, we use the official typemap (gl.tm) to get the correct type. // Then we override this, when it is: // 1) A string (we have to use Marshal.PtrToStringAnsi, to avoid heap corruption) // 2) An array (translates to IntPtr) // 3) A generic object or void* (translates to IntPtr) // 4) A GLenum (translates to int on Legacy.Tao or GL.Enums.GLenum otherwise). // Return types must always be CLS-compliant, because .Net does not support overloading on return types. void TranslateReturnType(EnumProcessor enum_processor, XPathNavigator nav, Delegate d, EnumCollection enums, string apiname) { var function_override = GetFuncOverride(nav, d, apiname); if (function_override != null) { XPathNavigator return_override = function_override.SelectSingleNode("returns"); if (return_override != null) { d.ReturnType.CurrentType = return_override.Value; } } TranslateType(d.ReturnType, enum_processor, nav, enums, d.Category, apiname); if (d.ReturnType.CurrentType.ToLower().Contains("void") && d.ReturnType.Pointer != 0) { d.ReturnType.QualifiedType = "IntPtr"; d.ReturnType.Pointer--; d.ReturnType.WrapperType = WrapperTypes.GenericReturnType; } if (d.ReturnType.CurrentType.ToLower().Contains("string")) { d.ReturnType.QualifiedType = "IntPtr"; d.ReturnType.WrapperType = WrapperTypes.StringReturnType; } if (d.ReturnType.CurrentType.ToLower() == "object") { d.ReturnType.QualifiedType = "IntPtr"; d.ReturnType.WrapperType |= WrapperTypes.GenericReturnType; } if (d.ReturnType.CurrentType.Contains("GLenum")) { if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) == Settings.Legacy.None) d.ReturnType.QualifiedType = String.Format("{0}{1}{2}", Settings.EnumsOutput, Settings.NamespaceSeparator, Settings.CompleteEnumName); else d.ReturnType.QualifiedType = "int"; } d.ReturnType.CurrentType = GetCLSCompliantType(d.ReturnType); } Delegate GetCLSCompliantDelegate(Delegate d) { Delegate f = new Delegate(d); for (int i = 0; i < f.Parameters.Count; i++) { f.Parameters[i].CurrentType = GetCLSCompliantType(f.Parameters[i]); } f.ReturnType.CurrentType = GetCLSCompliantType(f.ReturnType); return f; } void TranslateParameters(EnumProcessor enum_processor, XPathNavigator nav, Delegate d, EnumCollection enums, string apiname) { var function_override = GetFuncOverride(nav, d, apiname); for (int i = 0; i < d.Parameters.Count; i++) { if (function_override != null) { XPathNavigator param_override = function_override.SelectSingleNode( String.Format("param[@name='{0}']", d.Parameters[i].RawName)); if (param_override != null) { foreach (XPathNavigator node in param_override.SelectChildren(XPathNodeType.Element)) { switch (node.Name) { case "type": d.Parameters[i].CurrentType = (string)node.TypedValue; break; case "name": d.Parameters[i].Name = (string)node.TypedValue; break; case "flow": d.Parameters[i].Flow = Parameter.GetFlowDirection((string)node.TypedValue); break; case "count": int count; if (Int32.TryParse(node.Value, out count)) d.Parameters[i].ElementCount = count; break; } } } } TranslateParameter(d.Parameters[i], enum_processor, nav, enums, d.Category, apiname); if (d.Parameters[i].CurrentType == "UInt16" && d.Name.Contains("LineStipple")) d.Parameters[i].WrapperType = WrapperTypes.UncheckedParameter; } } void TranslateParameter(Parameter p, EnumProcessor enum_processor, XPathNavigator overrides, EnumCollection enums, string category, string apiname) { TranslateType(p, enum_processor, overrides, enums, category, apiname); // Find out the necessary wrapper types. if (p.Pointer != 0)/* || CurrentType == "IntPtr")*/ { if (p.CurrentType.ToLower().Contains("string") || p.CurrentType.ToLower().Contains("char") && p.Pointer > 1) { // string* -> [In] String[] or [Out] StringBuilder[] p.QualifiedType = p.Flow == FlowDirection.Out ? "StringBuilder[]" : "String[]"; p.Pointer = 0; p.WrapperType = WrapperTypes.None; } else if (p.CurrentType.ToLower().Contains("char")) { // char* -> [In] String or [Out] StringBuilder p.QualifiedType = p.Flow == FlowDirection.Out ? "StringBuilder" : "String"; p.Pointer = 0; p.WrapperType = WrapperTypes.None; } else if (p.CurrentType.ToLower().Contains("void") || (!String.IsNullOrEmpty(p.PreviousType) && p.PreviousType.ToLower().Contains("void"))) //|| CurrentType.Contains("IntPtr")) { p.CurrentType = "IntPtr"; p.Pointer = 0; p.WrapperType = WrapperTypes.GenericParameter; } else { p.WrapperType = WrapperTypes.ArrayParameter; } } if (p.Reference) p.WrapperType |= WrapperTypes.ReferenceParameter; if (Utilities.Keywords(Settings.Language).Contains(p.Name)) p.Name = Settings.KeywordEscapeCharacter + p.Name; // This causes problems with bool arrays //if (CurrentType.ToLower().Contains("bool")) // WrapperType = WrapperTypes.BoolParameter; } void TranslateAttributes(XPathNavigator nav, Delegate d, EnumCollection enums, string apiname) { var function_override = GetFuncOverride(nav, d, apiname); if (function_override != null) { var version_override = function_override.SelectSingleNode("version"); if (version_override != null) { d.Version = version_override.Value; } var profile_override = function_override.SelectSingleNode("profile"); if (profile_override != null) { Debug.Print("Profile override not yet implemented"); } } } FunctionCollection CreateWrappers(DelegateCollection delegates, EnumCollection enums) { var wrappers = new FunctionCollection(); foreach (var d in delegates.Values) { wrappers.AddRange(CreateNormalWrappers(d, enums)); } return wrappers; } FunctionCollection CreateCLSCompliantWrappers(FunctionCollection functions, EnumCollection enums) { // If the function is not CLS-compliant (e.g. it contains unsigned parameters) // we need to create a CLS-Compliant overload. However, we should only do this // iff the opengl function does not contain unsigned/signed overloads itself // to avoid redefinitions. var wrappers = new FunctionCollection(); foreach (var list in functions.Values) { foreach (var f in list) { wrappers.AddChecked(f); if (!f.CLSCompliant) { Function cls = new Function(f); bool modified = false; for (int i = 0; i < f.Parameters.Count; i++) { cls.Parameters[i].CurrentType = GetCLSCompliantType(cls.Parameters[i]); if (cls.Parameters[i].CurrentType != f.Parameters[i].CurrentType) modified = true; } if (modified) wrappers.AddChecked(cls); } } } return wrappers; } static FunctionCollection MarkCLSCompliance(FunctionCollection collection) { //foreach (var w in // (from list in collection // from w1 in list.Value // from w2 in list.Value // where // w1.TrimmedName == w2.TrimmedName && // w1.Parameters.Count == w2.Parameters.Count && // ParametersDifferOnlyInReference(w1.Parameters, w2.Parameters) // select !w1.Parameters.HasReferenceParameters ? w1 : w2)) // { // results.Add(w); // } foreach (List wrappers in collection.Values) { var must_remove = new List(); for (int i = 0; i < wrappers.Count; i++) { for (int j = i + 1; j < wrappers.Count; j++) { if (wrappers[i].TrimmedName == wrappers[j].TrimmedName && wrappers[i].Parameters.Count == wrappers[j].Parameters.Count) { bool function_i_is_problematic = false; bool function_j_is_problematic = false; int k; for (k = 0; k < wrappers[i].Parameters.Count; k++) { if (wrappers[i].Parameters[k].CurrentType != wrappers[j].Parameters[k].CurrentType) break; if (wrappers[i].Parameters[k].DiffersOnlyOnReference(wrappers[j].Parameters[k])) if (wrappers[i].Parameters[k].Reference) function_i_is_problematic = true; else function_j_is_problematic = true; } if (k == wrappers[i].Parameters.Count) { if (function_i_is_problematic) must_remove.Add(i); if (function_j_is_problematic) must_remove.Add(j); } } } } int count = 0; must_remove.Sort(); foreach (var i in must_remove) { // Careful: whenever we remove a function, the total count // is reduced. We must account for that, or we will remove // the wrong function! wrappers.RemoveAt(i - count); count++; } } return collection; } string GetCLSCompliantType(Type type) { if (!type.CLSCompliant) { if (type.Pointer != 0 && Settings.Compatibility == Settings.Legacy.Tao) return "IntPtr"; switch (type.CurrentType) { case "UInt16": case "ushort": return "Int16"; case "UInt32": case "uint": return "Int32"; case "UInt64": case "ulong": return "Int64"; case "SByte": case "sbyte": return "Byte"; case "UIntPtr": return "IntPtr"; } } return type.CurrentType; } IEnumerable CreateNormalWrappers(Delegate d, EnumCollection enums) { Function f = new Function(d); TrimName(f); WrapReturnType(f); foreach (var wrapper in WrapParameters(f, enums)) { yield return wrapper; } } public IEnumerable WrapParameters(Function func, EnumCollection enums) { Function f; if (func.Parameters.HasPointerParameters) { Function _this = new Function(func); // Array overloads foreach (Parameter p in _this.Parameters) { if (p.WrapperType == WrapperTypes.ArrayParameter) { if (p.ElementCount != 1) { // Create a proper array p.Reference = false; p.Array++; p.Pointer--; } else { // Create a reference p.Reference = true; p.Array--; p.Pointer--; } } } f = new Function(_this); yield return f; foreach (var w in WrapVoidPointers(f, enums)) yield return w; _this = new Function(func); // Reference overloads foreach (Parameter p in _this.Parameters) { if (p.WrapperType == WrapperTypes.ArrayParameter) { p.Reference = true; p.Array--; p.Pointer--; } } f = new Function(_this); yield return f; foreach (var w in WrapVoidPointers(f, enums)) yield return w; _this = func; // Pointer overloads // Should be last to work around an Intellisense bug, where // array overloads are not reported if there is a pointer overload. foreach (Parameter p in _this.Parameters) { if (p.WrapperType == WrapperTypes.ArrayParameter) { p.Reference = false; //p.Array--; //p.Pointer++; } } f = new Function(_this); yield return f; foreach (var w in WrapVoidPointers(f, enums)) yield return w; } else { f = new Function(func); yield return f; } } IEnumerable WrapVoidPointers(Function f, EnumCollection enums) { // reference wrapper (e.g. void Foo(int, ref T1, ref T2)) var func = new Function(f); int index = -1; foreach (var p in func.Parameters) { index++; if (p.WrapperType == WrapperTypes.GenericParameter) { p.Reference = true; p.Array = 0; p.Pointer = 0; p.Generic = true; p.CurrentType = "T" + index.ToString(); p.Flow = FlowDirection.Undefined; func.Parameters.Rebuild = true; } } yield return func; // 1d-array wrapper (e.g. void Foo(int, T1[], T2[])) func = new Function(f); index = -1; foreach (var p in func.Parameters) { index++; if (p.WrapperType == WrapperTypes.GenericParameter) { p.Reference = false; p.Array = 1; p.Pointer = 0; p.Generic = true; p.CurrentType = "T" + index.ToString(); p.Flow = FlowDirection.Undefined; func.Parameters.Rebuild = true; } } yield return func; // 2d-array wrapper (e.g. void Foo(int, T1[,], T2[,])) func = new Function(f); index = -1; foreach (var p in func.Parameters) { index++; if (p.WrapperType == WrapperTypes.GenericParameter) { p.Reference = false; p.Array = 2; p.Pointer = 0; p.Generic = true; p.CurrentType = "T" + index.ToString(); p.Flow = FlowDirection.Undefined; func.Parameters.Rebuild = true; } } yield return func; // 3d-array wrapper (e.g. void Foo(int, T1[,,], T2[,,])) func = new Function(f); index = -1; foreach (var p in func.Parameters) { index++; if (p.WrapperType == WrapperTypes.GenericParameter) { p.Reference = false; p.Array = 3; p.Pointer = 0; p.Generic = true; p.CurrentType = "T" + index.ToString(); p.Flow = FlowDirection.Undefined; func.Parameters.Rebuild = true; } } yield return func; } static void WrapReturnType(Function func) { switch (func.ReturnType.WrapperType) { case WrapperTypes.StringReturnType: func.ReturnType.QualifiedType = "String"; break; } } #endregion } }