Opentk/Source/Bind/XmlSpecReader.cs
Stefanos A. a29e132172 Trim elements when reading them
Sometimes elements in overrides.xml contain extra spaces due to typos,
which are quite difficult to track down. The XmlSpecReader can now
cope with that.
2013-11-03 12:17:09 +01:00

405 lines
16 KiB
C#

#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2013 Stefanos Apostolopoulos for 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.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;
using Bind.Structures;
namespace Bind
{
using Delegate = Bind.Structures.Delegate;
using Enum = Bind.Structures.Enum;
class XmlSpecReader : ISpecReader
{
Settings Settings { get; set; }
#region Constructors
public XmlSpecReader(Settings settings)
{
if (settings == null)
throw new ArgumentNullException("settings");
Settings = settings;
}
#endregion
#region ISpecReader Members
public void ReadDelegates(string file, DelegateCollection delegates, string apiname, string apiversion)
{
var specs = new XPathDocument(file);
// The pre-GL4.4 spec format does not distinguish between
// different apinames (it is assumed that different APIs
// are stored in distinct signature.xml files).
// To maintain compatibility, we detect the version of the
// signatures.xml file and ignore apiname if it is version 1.
var specversion = GetSpecVersion(specs);
if (specversion == "1")
{
apiname = null;
}
string xpath_add, xpath_delete;
GetSignaturePaths(apiname, apiversion, out xpath_add, out xpath_delete);
foreach (XPathNavigator nav in specs.CreateNavigator().Select(xpath_delete))
{
foreach (XPathNavigator node in nav.SelectChildren("function", String.Empty))
delegates.Remove(node.GetAttribute("name", String.Empty));
}
foreach (XPathNavigator nav in specs.CreateNavigator().Select(xpath_add))
{
Utilities.Merge(delegates, ReadDelegates(nav, apiversion));
}
}
public void ReadEnums(string file, EnumCollection enums, string apiname, string apiversion)
{
var specs = new XPathDocument(file);
// The pre-GL4.4 spec format does not distinguish between
// different apinames (it is assumed that different APIs
// are stored in distinct signature.xml files).
// To maintain compatibility, we detect the version of the
// signatures.xml file and ignore apiname if it is version 1.
var specversion = GetSpecVersion(specs);
if (specversion == "1")
{
apiname = null;
}
string xpath_add, xpath_delete;
GetSignaturePaths(apiname, apiversion, out xpath_add, out xpath_delete);
// First, read all enum definitions from spec and override file.
// Afterwards, read all token/enum overrides from overrides file.
foreach (XPathNavigator nav in specs.CreateNavigator().Select(xpath_delete))
{
foreach (XPathNavigator node in nav.SelectChildren("enum", String.Empty))
enums.Remove(node.GetAttribute("name", String.Empty));
}
foreach (XPathNavigator nav in specs.CreateNavigator().Select(xpath_add))
{
Utilities.Merge(enums, ReadEnums(nav));
}
}
public Dictionary<string, string> ReadTypeMap(string file)
{
using (var sr = new StreamReader(file))
{
Console.WriteLine("Reading opengl types.");
Dictionary<string, string> GLTypes = new Dictionary<string, string>();
if (sr == null)
return GLTypes;
do
{
string line = sr.ReadLine();
if (String.IsNullOrEmpty(line) || line.StartsWith("#"))
continue;
string[] words = line.Split(" ,*\t".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (words[0].ToLower() == "void")
{
// Special case for "void" -> "". We make it "void" -> "void"
GLTypes.Add(words[0], "void");
}
else if (words[0] == "VoidPointer" || words[0] == "ConstVoidPointer")
{
// "(Const)VoidPointer" -> "void*"
GLTypes.Add(words[0], "void*");
}
else if (words[0] == "CharPointer" || words[0] == "charPointerARB" ||
words[0] == "ConstCharPointer")
{
// The typematching logic cannot handle pointers to pointers, e.g. CharPointer* -> char** -> string* -> string[].
// Hence we give it a push.
// Note: When both CurrentType == "String" and Pointer == true, the typematching is hardcoded to use
// String[] or StringBuilder[].
GLTypes.Add(words[0], "String");
}
/*else if (words[0].Contains("Pointer"))
{
GLTypes.Add(words[0], words[1].Replace("Pointer", "*"));
}*/
else if (words[1].Contains("GLvoid"))
{
GLTypes.Add(words[0], "void");
}
else if (words[1] == "const" && words[2] == "GLubyte")
{
GLTypes.Add(words[0], "String");
}
else if (words[1] == "struct")
{
GLTypes.Add(words[0], words[2]);
}
else
{
GLTypes.Add(words[0], words[1]);
}
}
while (!sr.EndOfStream);
return GLTypes;
}
}
public Dictionary<string, string> ReadCSTypeMap(string file)
{
using (var sr = new StreamReader(file))
{
Dictionary<string, string> CSTypes = new Dictionary<string, string>();
Console.WriteLine("Reading C# types.");
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
if (String.IsNullOrEmpty(line) || line.StartsWith("#"))
continue;
string[] words = line.Split(" ,\t".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (words.Length < 2)
continue;
if (((Settings.Compatibility & Settings.Legacy.NoBoolParameters) != Settings.Legacy.None) && words[1] == "bool")
words[1] = "Int32";
CSTypes.Add(words[0], words[1]);
}
return CSTypes;
}
}
#endregion
#region Private Members
static void GetSignaturePaths(string apiname, string apiversion, out string xpath_add, out string xpath_delete)
{
xpath_add = "/signatures/add";
xpath_delete = "/signatures/delete";
if (!String.IsNullOrEmpty(apiname))
{
xpath_add += String.Format("[contains(concat('|', @name, '|'), '|{0}|')]", apiname);
xpath_delete += String.Format("[contains(concat('|', @name, '|'), '|{0}|')]", apiname);
}
}
string GetSpecVersion(XPathDocument specs)
{
var version =
specs.CreateNavigator().SelectSingleNode("/signatures")
.GetAttribute("version", String.Empty);
if (String.IsNullOrEmpty(version))
{
version = "1";
}
return version;
}
DelegateCollection ReadDelegates(XPathNavigator specs, string apiversion)
{
DelegateCollection delegates = new DelegateCollection();
var extensions = new List<string>();
string path = "function";
foreach (XPathNavigator node in specs.SelectChildren(path, String.Empty))
{
var name = node.GetAttribute("name", String.Empty).Trim();
var version = node.GetAttribute("version", String.Empty).Trim();
// Ignore functions that have a higher version number than
// our current apiversion. Extensions do not have a version,
// so we add them anyway (which is desirable).
if (!String.IsNullOrEmpty(version) && !String.IsNullOrEmpty(apiversion) &&
Decimal.Parse(version) > Decimal.Parse(apiversion))
continue;
// Check whether we are adding to an existing delegate or creating a new one.
Delegate d = null;
if (delegates.ContainsKey(name))
{
d = delegates[name];
}
else
{
d = new Delegate();
d.Name = name;
d.Version = node.GetAttribute("version", String.Empty).Trim();
d.Category = node.GetAttribute("category", String.Empty).Trim();
d.DeprecatedVersion = node.GetAttribute("deprecated", String.Empty).Trim();
d.Deprecated = !String.IsNullOrEmpty(d.DeprecatedVersion);
d.Extension = node.GetAttribute("extension", String.Empty).Trim() ?? "Core";
if (!extensions.Contains(d.Extension))
extensions.Add(d.Extension);
}
foreach (XPathNavigator param in node.SelectChildren(XPathNodeType.Element))
{
switch (param.Name)
{
case "returns":
d.ReturnType.CurrentType = param.GetAttribute("type", String.Empty).Trim();
break;
case "param":
Parameter p = new Parameter();
p.CurrentType = param.GetAttribute("type", String.Empty).Trim();
p.Name = param.GetAttribute("name", String.Empty).Trim();
string element_count = param.GetAttribute("elementcount", String.Empty).Trim();
if (String.IsNullOrEmpty(element_count))
{
element_count = param.GetAttribute("count", String.Empty).Trim();
if (!String.IsNullOrEmpty(element_count))
{
int count;
if (Int32.TryParse(element_count, out count))
{
p.ElementCount = count;
}
}
}
p.Flow = Parameter.GetFlowDirection(param.GetAttribute("flow", String.Empty).Trim());
d.Parameters.Add(p);
break;
}
}
delegates.Add(d);
}
Utilities.InitExtensions(extensions);
return delegates;
}
EnumCollection ReadEnums(XPathNavigator nav)
{
EnumCollection enums = new EnumCollection();
Enum all = new Enum() { Name = Settings.CompleteEnumName };
if (nav != null)
{
foreach (XPathNavigator node in nav.SelectChildren("enum", String.Empty))
{
Enum e = new Enum()
{
Name = node.GetAttribute("name", String.Empty).Trim(),
Type = node.GetAttribute("type", String.Empty).Trim()
};
if (String.IsNullOrEmpty(e.Name))
throw new InvalidOperationException(String.Format("Empty name for enum element {0}", node.ToString()));
// It seems that all flag collections contain "Mask" in their names.
// This looks like a heuristic, but it holds 100% in practice
// (checked all enums to make sure).
e.IsFlagCollection = e.Name.ToLower().Contains("mask");
foreach (XPathNavigator param in node.SelectChildren(XPathNodeType.Element))
{
Constant c = null;
switch (param.Name)
{
case "token":
c = new Constant
{
Name = param.GetAttribute("name", String.Empty).Trim(),
Value = param.GetAttribute("value", String.Empty).Trim()
};
break;
case "use":
c = new Constant
{
Name = param.GetAttribute("token", String.Empty).Trim(),
Reference = param.GetAttribute("enum", String.Empty).Trim(),
Value = param.GetAttribute("token", String.Empty).Trim(),
};
break;
default:
throw new NotSupportedException();
}
Utilities.Merge(all, c);
try
{
if (!e.ConstantCollection.ContainsKey(c.Name))
{
e.ConstantCollection.Add(c.Name, c);
}
else if (e.ConstantCollection[c.Name].Value != c.Value)
{
var existing = e.ConstantCollection[c.Name];
if (existing.Reference != null && c.Reference == null)
{
e.ConstantCollection[c.Name] = c;
}
else if (existing.Reference == null && c.Reference != null)
{ } // Keep existing
else
{
Console.WriteLine("[Warning] Conflicting token {0}.{1} with value {2} != {3}",
e.Name, c.Name, e.ConstantCollection[c.Name].Value, c.Value);
}
}
}
catch (ArgumentException ex)
{
Console.WriteLine("[Warning] Failed to add constant {0} to enum {1}: {2}", c.Name, e.Name, ex.Message);
}
}
Utilities.Merge(enums, e);
}
}
Utilities.Merge(enums, all);
return enums;
}
#endregion
}
}