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.)
This commit is contained in:
Stefanos A. 2013-11-01 09:27:46 +01:00
parent 33e6a6eae2
commit bf0f42be82

View file

@ -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<string, string> CSTypes)
void WriteWrappers(BindStreamWriter sw, FunctionCollection wrappers, EnumCollection enums, IDictionary<string, string> 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<string, string> docfiles;
DocProcessor processor_;
DocProcessor Processor
{
get
{
if (processor_ == null)
processor_ = new DocProcessor(Path.Combine(Settings.DocPath, Settings.DocFile));
return processor_;
}
}
Dictionary<string, string> 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("/// <summary>");
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("/// </summary>");
}
@ -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<string>();
var handle_release_statements = new List<string>();
var fixed_statements = new List<string>();
var assign_statements = new List<string>();
// 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<Constant> constants = new List<Constant>(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
}
}