Opentk/Source/Bind/Structures/Function.cs
the_fiddler 97b3cd2e93 Type, Parameter, ParameterCollection and Function classes now implement IComparable directly (rather than casting to strings and comparing those).
Modified comparison order to place pointer overloads after reference or array overloads. Fixes issue [#1098]: "[Bind] Work around VS bug by ensuring pointer parameters are generated last".
2009-11-17 09:26:05 +00:00

742 lines
25 KiB
C#

#region --- License ---
/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
* See license.txt for license info
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Bind.Structures
{
public class Function : Delegate, IEquatable<Function>, IComparable<Function>
{
#region Static Members
internal static FunctionCollection Wrappers;
static bool loaded;
#region internal static void Initialize()
internal static void Initialize()
{
if (!loaded)
{
Wrappers = new FunctionCollection();
loaded = true;
}
}
#endregion
#endregion
#region Fields
Delegate wrapped_delegate;
int index;
#endregion
#region --- Constructors ---
public Function(Delegate d)
: base(d)
{
Name = d.Name;
Body = new FunctionBody();
WrappedDelegate = d;
}
public Function(Function f)
: this(f.WrappedDelegate)
{
Parameters = new ParameterCollection(f.Parameters);
ReturnType = new Type(f.ReturnType);
Body.AddRange(f.Body);
}
#endregion
#region public Delegate WrappedDelegate
public Delegate WrappedDelegate
{
get { return wrapped_delegate; }
set { wrapped_delegate = value; }
}
#endregion
#region public void TurnVoidPointersToIntPtr()
public void TurnVoidPointersToIntPtr()
{
foreach (Parameter p in Parameters)
{
if (p.Pointer != 0 && p.CurrentType == "void")
{
p.CurrentType = "IntPtr";
p.Pointer = 0;
}
}
}
#endregion
#region public override bool Unsafe
public override bool Unsafe
{
get
{
if ((Settings.Compatibility & Settings.Legacy.NoPublicUnsafeFunctions) != Settings.Legacy.None)
return false;
return base.Unsafe;
}
}
#endregion
#region public FunctionBody Body
FunctionBody _body;
public FunctionBody Body
{
get { return _body; }
set { _body = value; }
}
#endregion
#region public string TrimmedName
public string TrimmedName;
#endregion
#region public override string Name
/// <summary>
/// Gets or sets the name of the opengl function.
/// If no Tao compatibility is set, set TrimmedName to Name, after removing
/// [u][bsifd][v].
/// </summary>
public override string Name
{
get { return base.Name; }
set
{
base.Name = value;
if ((Settings.Compatibility & Settings.Legacy.NoTrimFunctionEnding) != Settings.Legacy.None)
{
// If we don't need compatibility with Tao,
// remove the Extension and the overload information from the name
// (Extension == "ARB", "EXT", etc, overload == [u][bsidf][v])
// TODO: Use some regex's here, to reduce clutter.
TrimmedName = value;
}
else
{
TrimmedName = Utilities.StripGL2Extension(value);
Match m = endingsNotToTrim.Match(TrimmedName);
if ((m.Index + m.Length) != TrimmedName.Length)
{
// Some endings should not be trimmed, for example: 'b' from Attrib
m = endings.Match(TrimmedName);
if (m.Length > 0 && m.Index + m.Length == TrimmedName.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
TrimmedName = TrimmedName.Substring(0, m.Index) + "v";
}
else
{
if (!TrimmedName.EndsWith("xedv"))
TrimmedName = TrimmedName.Substring(0, m.Index);
else
TrimmedName = TrimmedName.Substring(0, m.Index + 1);
}
}
}
}
}
}
#endregion
#region public override string ToString()
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(Unsafe ? "unsafe " : "");
sb.Append(ReturnType);
sb.Append(" ");
if ((Settings.Compatibility & Settings.Legacy.NoTrimFunctionEnding) != Settings.Legacy.None)
{
sb.Append(Settings.FunctionPrefix);
}
sb.Append(!String.IsNullOrEmpty(TrimmedName) ? TrimmedName : Name);
if (Parameters.HasGenericParameters)
{
sb.Append("<");
foreach (Parameter p in Parameters)
{
if (p.Generic)
{
sb.Append(p.CurrentType);
sb.Append(",");
}
}
sb.Remove(sb.Length - 1, 1);
sb.Append(">");
}
sb.AppendLine(Parameters.ToString(false));
if (Parameters.HasGenericParameters)
{
foreach (Parameter p in Parameters)
{
if (p.Generic)
sb.AppendLine(String.Format(" where {0} : struct", p.CurrentType));
}
}
if (Body.Count > 0)
{
sb.Append(Body.ToString());
}
return sb.ToString();
}
#endregion
#region IEquatable<Function> Members
public bool Equals(Function other)
{
return
!String.IsNullOrEmpty(TrimmedName) && !String.IsNullOrEmpty(other.TrimmedName) &&
TrimmedName == other.TrimmedName &&
Parameters.ToString(true) == other.Parameters.ToString(true);
}
#endregion
#region public void WrapParameters(List<Function> wrappers)
public void WrapParameters(List<Function> wrappers)
{
Function f;
if (Parameters.HasPointerParameters)
{
Function _this = new Function(this);
// Array overloads
foreach (Parameter p in _this.Parameters)
{
if (p.WrapperType == WrapperTypes.ArrayParameter && p.ElementCount != 1)
{
p.Reference = false;
p.Array++;
p.Pointer--;
}
}
f = new Function(_this);
f.CreateBody(false);
wrappers.Add(f);
new Function(f).WrapVoidPointers(wrappers);
_this = new Function(this);
// Reference overloads
foreach (Parameter p in _this.Parameters)
{
if (p.WrapperType == WrapperTypes.ArrayParameter)
{
p.Reference = true;
p.Array--;
p.Pointer--;
}
}
f = new Function(_this);
f.CreateBody(false);
wrappers.Add(f);
new Function(f).WrapVoidPointers(wrappers);
_this = this;
// Pointer overloads
// Should be last to work around 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);
f.CreateBody(false);
wrappers.Add(f);
new Function(f).WrapVoidPointers(wrappers);
}
else
{
f = new Function(this);
f.CreateBody(false);
wrappers.Add(f);
}
}
#endregion
#region public void WrapVoidPointers(List<Function> wrappers)
public void WrapVoidPointers(List<Function> wrappers)
{
if (index >= 0 && index < Parameters.Count)
{
if (Parameters[index].WrapperType == WrapperTypes.GenericParameter)
{
// Recurse to the last parameter
++index;
WrapVoidPointers(wrappers);
--index;
// On stack rewind, create generic wrappers
Parameters[index].Reference = true;
Parameters[index].Array = 0;
Parameters[index].Pointer = 0;
Parameters[index].Generic = true;
Parameters[index].CurrentType = "T" + index.ToString();
Parameters[index].Flow = FlowDirection.Undefined;
Parameters.Rebuild = true;
CreateBody(false);
wrappers.Add(new Function(this));
Parameters[index].Reference = false;
Parameters[index].Array = 1;
Parameters[index].Pointer = 0;
Parameters[index].Generic = true;
Parameters[index].CurrentType = "T" + index.ToString();
Parameters[index].Flow = FlowDirection.Undefined;
Parameters.Rebuild = true;
CreateBody(false);
wrappers.Add(new Function(this));
Parameters[index].Reference = false;
Parameters[index].Array = 2;
Parameters[index].Pointer = 0;
Parameters[index].Generic = true;
Parameters[index].CurrentType = "T" + index.ToString();
Parameters[index].Flow = FlowDirection.Undefined;
Parameters.Rebuild = true;
CreateBody(false);
wrappers.Add(new Function(this));
Parameters[index].Reference = false;
Parameters[index].Array = 3;
Parameters[index].Pointer = 0;
Parameters[index].Generic = true;
Parameters[index].CurrentType = "T" + index.ToString();
Parameters[index].Flow = FlowDirection.Undefined;
Parameters.Rebuild = true;
CreateBody(false);
wrappers.Add(new Function(this));
}
else
{
// Recurse to the last parameter
++index;
WrapVoidPointers(wrappers);
--index;
}
}
}
#endregion
#region public void WrapReturnType()
public void WrapReturnType()
{
switch (ReturnType.WrapperType)
{
case WrapperTypes.StringReturnType:
ReturnType.QualifiedType = "String";
break;
}
}
#endregion
#region public void CreateBody(bool wantCLSCompliance)
readonly static List<string> handle_statements = new List<string>();
readonly static List<string> handle_release_statements = new List<string>();
readonly static List<string> fixed_statements = new List<string>();
readonly static List<string> assign_statements = new List<string>();
// For example, if parameter foo has indirection level = 1, then it
// is consumed as 'foo*' in the fixed_statements and the call string.
string[] indirection_levels = new string[] { "", "*", "**", "***", "****" };
public void CreateBody(bool wantCLSCompliance)
{
Function f = new Function(this);
f.Body.Clear();
handle_statements.Clear();
handle_release_statements.Clear();
fixed_statements.Clear();
assign_statements.Clear();
// Obtain pointers by pinning the parameters
foreach (Parameter p in f.Parameters)
{
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})",
wantCLSCompliance && !p.CLSCompliant ? p.GetCLSCompliantType() : p.QualifiedType,
p.Name + "_ptr",
p.Array > 0 ? p.Name : "&" + p.Name,
indirection_levels[p.IndirectionLevel]));
if (p.Name == "pixels_ptr")
System.Diagnostics.Debugger.Break();
// 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");
}
}
}
// 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 (p.IsEnum && p.QualifiedType != f.WrappedDelegate.Parameters[parameter_index].QualifiedType)
{
p.QualifiedType = f.WrappedDelegate.Parameters[parameter_index].QualifiedType;
}
}
}
if (assign_statements.Count > 0)
{
// Call function
string method_call = f.CallString();
if (f.ReturnType.CurrentType.ToLower().Contains("void"))
f.Body.Add(String.Format("{0};", method_call));
else if (ReturnType.CurrentType.ToLower().Contains("string"))
f.Body.Add(String.Format("{0} {1} = null; unsafe {{ {1} = new string((sbyte*){2}); }}",
ReturnType.QualifiedType, "retval", method_call));
else
f.Body.Add(String.Format("{0} {1} = {2};", f.ReturnType.QualifiedType, "retval", method_call));
// 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
if (f.ReturnType.CurrentType.ToLower().Contains("void"))
f.Body.Add(String.Format("{0};", f.CallString()));
else if (ReturnType.CurrentType.ToLower().Contains("string"))
f.Body.Add(String.Format("unsafe {{ return new string((sbyte*){0}); }}",
f.CallString()));
else
f.Body.Add(String.Format("return {0};", f.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");
}
}
Body = f.Body;
}
#endregion
#region IComparable<Function> Members
public int CompareTo(Function other)
{
int ret = Name.CompareTo(other.Name);
if (ret == 0)
ret = Parameters.CompareTo(other.Parameters);
if (ret == 0)
ret = ReturnType.CompareTo(other.ReturnType);
return ret;
}
#endregion
}
#region class FunctionBody : List<string>
public class FunctionBody : List<string>
{
public FunctionBody()
{
}
public FunctionBody(FunctionBody fb)
{
foreach (string s in fb)
{
Add(s);
}
}
private string indent = "";
public void Indent()
{
indent += " ";
}
public void Unindent()
{
if (indent.Length >= 4)
indent = indent.Substring(4);
}
new public void Add(string s)
{
base.Add(indent + s);
}
new public void AddRange(IEnumerable<string> collection)
{
foreach (string t in collection)
{
Add(t);
}
}
public override string ToString()
{
if (Count == 0)
return String.Empty;
StringBuilder sb = new StringBuilder(Count);
sb.AppendLine("{");
foreach (string s in this)
{
sb.AppendLine(" " + s);
}
sb.Append("}");
return sb.ToString();
}
}
#endregion
#region class FunctionCollection : SortedDictionary<string, List<Function>>
class FunctionCollection : SortedDictionary<string, List<Function>>
{
Regex unsignedFunctions = new Regex(@".+(u[dfisb]v?)", RegexOptions.Compiled);
public void Add(Function f)
{
if (!ContainsKey(f.Extension))
{
Add(f.Extension, new List<Function>());
this[f.Extension].Add(f);
}
else
{
this[f.Extension].Add(f);
}
}
public void AddRange(IEnumerable<Function> functions)
{
foreach (Function f in functions)
{
Add(f);
}
}
/// <summary>
/// Adds the function to the collection, if a function with the same name and parameters doesn't already exist.
/// </summary>
/// <param name="f">The Function to add.</param>
public void AddChecked(Function f)
{
if (Function.Wrappers.ContainsKey(f.Extension))
{
int index = Function.Wrappers[f.Extension].IndexOf(f);
if (index == -1)
{
Function.Wrappers.Add(f);
}
else
{
Function existing = Function.Wrappers[f.Extension][index];
if ((existing.Parameters.HasUnsignedParameters && !unsignedFunctions.IsMatch(existing.Name) && unsignedFunctions.IsMatch(f.Name)) ||
(!existing.Parameters.HasUnsignedParameters && unsignedFunctions.IsMatch(existing.Name) && !unsignedFunctions.IsMatch(f.Name)))
{
Function.Wrappers[f.Extension].RemoveAt(index);
Function.Wrappers[f.Extension].Add(f);
}
}
}
else
{
Function.Wrappers.Add(f);
}
}
}
#endregion
}