Opentk/Source/Bind/FuncProcessor.cs
Stefanos A. 0548806487 Improved debug output on high-order pointers
OpenTK does not currently support generating bindings for pointers of
order 3 or higher. No OpenGL or OpenGL ES API currently uses such
pointers, so we just issue a warning message if such an API is
encountered in the future.
2013-11-03 12:12:39 +01:00

801 lines
31 KiB
C#

#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<Function> wrappers in collection.Values)
{
var must_remove = new List<int>();
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<Function> 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<Function> 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<Function> WrapVoidPointers(Function f, EnumCollection enums)
{
// reference wrapper (e.g. void Foo<T1,T2>(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<T1, T2>(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<T1, T2>(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<T1, T2>(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
}
}