// Copyright (c) 2010 SharpDX - Alexandre Mutel
//
// 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.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using SharpGen.Config;
using SharpGen.CppModel;
using SharpGen.Logging;
using SharpGen.Parser;
namespace SharpGen.Platform
{
internal static class Extension
{
///
/// Get the value from an attribute.
///
/// The object to get the attribute from.
/// The name of the attribute.
///
public static string AttributeValue(this XElement xElement, string name) => xElement.Attribute(name)?.Value;
}
///
/// Full C++ Parser built on top of .
///
public sealed class CppParser
{
private CppModule _group;
private readonly HashSet _includeToProcess = new();
private readonly Dictionary _includeIsAttached = new();
private readonly Dictionary> _includeAttachedTypes = new();
private readonly HashSet _boundTypes = new();
private readonly ConfigFile _configRoot;
private CppInclude _currentCppInclude;
private readonly Dictionary _mapIdToXElement = new();
private readonly Dictionary> _mapFileToXElement = new();
private readonly Dictionary _mapIncludeToAnonymousEnumCount = new();
private readonly Ioc ioc;
public CppParser(ConfigFile configRoot, Ioc ioc)
{
this.ioc = ioc ?? throw new ArgumentNullException(nameof(ioc));
_configRoot = configRoot ?? throw new ArgumentNullException(nameof(configRoot));
Initialize();
}
public string OutputPath { get; set; }
private Logger Logger => ioc.Logger;
private void Initialize()
{
foreach (var bindRule in _configRoot.ConfigFilesLoaded.SelectMany(cfg => cfg.Bindings))
_boundTypes.Add(bindRule.From);
foreach (var configFile in _configRoot.ConfigFilesLoaded)
{
foreach (var includeRule in configFile.Includes)
{
_includeToProcess.Add(includeRule.Id);
// Handle attach types
// Set that the include is attached (so that all types inside are attached
var isIncludeFullyAttached = includeRule.Attach ?? false;
if (isIncludeFullyAttached || includeRule.AttachTypes.Count > 0)
{
// An include can be fully attached ( include rule is set to true)
// or partially attached (the include rule contains Attach for specific types)
// We need to know which includes are attached, if they are fully or partially
if (!_includeIsAttached.ContainsKey(includeRule.Id))
_includeIsAttached.Add(includeRule.Id, isIncludeFullyAttached);
else if (isIncludeFullyAttached)
{
_includeIsAttached[includeRule.Id] = true;
}
// Attach types if any
if (includeRule.AttachTypes.Count > 0)
{
if (!_includeAttachedTypes.TryGetValue(includeRule.Id, out HashSet typesToAttach))
{
typesToAttach = new HashSet();
_includeAttachedTypes.Add(includeRule.Id, typesToAttach);
}
// For specific attach types, register them
foreach (var attachTypeName in includeRule.AttachTypes)
{
typesToAttach.Add(attachTypeName);
}
}
}
}
// Register extension headers
if (configFile.Extension.Any(rule => rule.GeneratesExtensionHeader()))
{
_includeToProcess.Add(configFile.ExtensionId);
if (!_includeIsAttached.ContainsKey(configFile.ExtensionId))
_includeIsAttached.Add(configFile.ExtensionId, true);
}
}
}
///
/// Gets the name of the generated GCCXML file.
///
/// The name of the generated GCCXML file.
private string GccXmlFileName => Path.Combine(OutputPath, _configRoot.Id + "-gcc.xml");
public string RootConfigHeaderFileName => Path.Combine(OutputPath, _configRoot.HeaderFileName);
///
/// Gets or sets the GccXml doc.
///
/// The GccXml doc.
private XDocument GccXmlDoc { get; set; }
public Dictionary IncludeMacroCounts { get; } = new();
///
/// Runs this instance.
///
///
public CppModule Run(CppModule groupSkeleton, StreamReader xmlReader)
{
_group = groupSkeleton;
Logger.Message("Config files changed.");
const string progressMessage = "Parsing C++ headers starts, please wait...";
try
{
Logger.Progress(15, progressMessage);
if (xmlReader != null)
{
Parse(xmlReader);
}
Logger.Progress(30, progressMessage);
}
catch (Exception ex)
{
Logger.Error(null, "Unexpected error", ex);
}
finally
{
Logger.Message("Parsing headers is finished.");
// Write back GCC-XML document on the disk
try
{
using var stream = File.Open(GccXmlFileName, FileMode.Create, FileAccess.Write);
GccXmlDoc?.Save(stream);
}
catch (Exception e)
{
Logger.LogRawMessage(
LogLevel.Warning, LoggingCodes.ParserDiagnosticDumpIoError,
"Writing GCC-XML file [{0}] failed", e, GccXmlFileName
);
}
}
// Track number of included macros for statistics
foreach (var cppInclude in _group.Includes)
{
IncludeMacroCounts.TryGetValue(cppInclude.Name, out var count);
count += cppInclude.Macros.Count();
IncludeMacroCounts[cppInclude.Name] = count;
}
return _group;
}
///
/// Parses the specified reader.
///
/// The reader.
private void Parse(StreamReader reader)
{
var doc = XDocument.Load(reader);
GccXmlDoc = doc;
// Collects all GccXml elements and build map from their id
foreach (var xElement in doc.Elements("GCC_XML").Elements())
{
var id = xElement.Attribute("id").Value;
_mapIdToXElement.Add(id, xElement);
var file = xElement.AttributeValue("file");
if (file != null)
{
if (!_mapFileToXElement.TryGetValue(file, out List elementsInFile))
{
elementsInFile = new List();
_mapFileToXElement.Add(file, elementsInFile);
}
elementsInFile.Add(xElement);
}
}
// Fix all structure names
AdjustTypeNamesFromTypedefs(doc);
// Find all elements that are referring to a context and attach them to
// the context as child elements
foreach (var xElement in _mapIdToXElement.Values)
{
var id = xElement.AttributeValue("context");
if (id != null)
{
xElement.Remove();
_mapIdToXElement[id].Add(xElement);
}
}
ParseAllElements();
}
private void AdjustTypeNamesFromTypedefs(XDocument doc)
{
foreach (var xTypedef in doc.Elements("GCC_XML").Elements(CastXml.TagTypedef))
{
var xStruct = _mapIdToXElement[xTypedef.AttributeValue("type")];
switch (xStruct.Name.LocalName)
{
case CastXml.TagStruct:
case CastXml.TagUnion:
case CastXml.TagEnumeration:
var structName = xStruct.AttributeValue("name");
// Rename all structure starting with tagXXXX to XXXX
if (structName.StartsWith("tag") || structName.StartsWith("_") || string.IsNullOrEmpty(structName))
{
var typeName = xTypedef.AttributeValue("name");
xStruct.SetAttributeValue("name", typeName);
}
break;
}
}
}
///
/// Parses a C++ function.
///
/// The gccxml that describes a C++ function.
/// A C++ function parsed
private CppFunction ParseFunction(XElement xElement)
{
CppFunction cppMethod = new(xElement.AttributeValue("name"));
ParseCallable(cppMethod, xElement);
return cppMethod;
}
///
/// Parses a C++ parameters.
///
/// The gccxml that describes a C++ parameter.
/// The method or function to populate.
private void ParseParameters(XElement xElement, CppContainer methodOrFunction)
{
var paramCount = 0;
foreach (var parameter in xElement.Elements())
{
if (parameter.Name.LocalName != "Argument")
continue;
var name = parameter.AttributeValue("name");
if (string.IsNullOrEmpty(name))
name = "arg" + paramCount;
CppParameter cppParameter = new(name);
ParseAnnotations(parameter, cppParameter);
Logger.PushContext("Parameter:[{0}]", cppParameter.Name);
ResolveAndFillType(parameter.AttributeValue("type"), cppParameter);
methodOrFunction.Add(cppParameter);
Logger.PopContext();
paramCount++;
}
}
///
/// Parses C++ annotations/attributes.
///
/// The gccxml that contains C++ annotations/attributes.
/// The C++ element to populate.
private static void ParseAnnotations(XElement xElement, CppElement cppElement)
{
// Check that the xml contains the "attributes" attribute
var attributes = xElement.AttributeValue("attributes");
if (string.IsNullOrWhiteSpace(attributes))
return;
// Strip whitespaces inside annotate("...")
var stripSpaces = new StringBuilder();
var doubleQuoteCount = 0;
for (var i = 0; i < attributes.Length; i++)
{
var addThisChar = true;
var attributeChar = attributes[i];
if (attributeChar == '(')
{
doubleQuoteCount++;
}
else if (attributeChar == ')')
{
doubleQuoteCount--;
}
else if (doubleQuoteCount > 0 && (char.IsWhiteSpace(attributeChar) | attributeChar == '"'))
{
addThisChar = false;
}
if (addThisChar)
stripSpaces.Append(attributeChar);
}
attributes = stripSpaces.ToString();
// Default calling convention
var cppCallingConvention = CppCallingConvention.Unknown;
// Default parameter attribute
var paramAttribute = ParamAttribute.None;
// Default Guid
string guid = null;
// Parse attributes
const string gccXmlAttribute = "annotate(";
var isPost = false;
var hasWritable = false;
// Clang outputs attributes in reverse order
// TODO: Check if applies to all declarations
foreach (var item in attributes.Split(' ').Reverse())
{
var newItem = item;
if (newItem.StartsWith(gccXmlAttribute))
newItem = newItem.Substring(gccXmlAttribute.Length);
if (newItem.StartsWith("SAL_pre"))
{
isPost = false;
}
else if (newItem.StartsWith("SAL_post"))
{
isPost = true;
}
else if (isPost && newItem.StartsWith("SAL_valid"))
{
paramAttribute |= ParamAttribute.Out;
}
else if (newItem.StartsWith("SAL_maybenull") || (newItem.StartsWith("SAL_null") && newItem.Contains("__maybe")))
{
paramAttribute |= ParamAttribute.Optional;
}
else if (newItem.StartsWith("SAL_readableTo") || newItem.StartsWith("SAL_writableTo"))
{
if (newItem.StartsWith("SAL_writableTo"))
{
// When changing something related to in/out SAL, check resulting attributes
// for IInspectable::GetIids::iids (_Outptr_result_buffer_to_maybenull_)
// Should be Out|Buffer|Optional
if (!isPost) paramAttribute |= ParamAttribute.Out;
hasWritable = true;
}
if (!newItem.Contains("SPECSTRINGIZE(1)") && !newItem.Contains("elementCount(1)"))
paramAttribute |= ParamAttribute.Buffer;
}
else if (newItem.StartsWith("__stdcall__"))
{
cppCallingConvention = CppCallingConvention.StdCall;
}
else if (newItem.StartsWith("__cdecl__"))
{
cppCallingConvention = CppCallingConvention.CDecl;
}
else if (newItem.StartsWith("__thiscall__"))
{
cppCallingConvention = CppCallingConvention.ThisCall;
}
else if (newItem.StartsWith("uuid("))
{
guid = newItem.Trim(')').Substring("uuid(".Length).Trim('"', '{', '}');
}
}
// If no writable, than this is an In parameter
if (!hasWritable)
{
paramAttribute |= ParamAttribute.In;
}
// Update CppElement based on its type
if (cppElement is CppParameter param)
{
// Replace in & out with inout.
// Todo check to use in & out instead of inout
if ((paramAttribute & ParamAttribute.In) != 0 && (paramAttribute & ParamAttribute.Out) != 0)
{
paramAttribute ^= ParamAttribute.In;
paramAttribute ^= ParamAttribute.Out;
paramAttribute |= ParamAttribute.InOut;
}
param.Attribute = paramAttribute;
}
else if (cppElement is CppCallable callable && cppCallingConvention != CppCallingConvention.Unknown)
{
callable.CallingConvention = cppCallingConvention;
}
else if (cppElement is CppInterface iface && guid != null)
{
iface.Guid = guid;
}
}
///
/// Parses a C++ method or function.
///
/// The gccxml that describes a C++ method/function declaration.
/// The C++ parsed T.
private void ParseCallable(CppCallable cppCallable, XElement xElement)
{
Logger.PushContext("Callable:[{0}]", cppCallable.Name);
// Parse annotations
ParseAnnotations(xElement, cppCallable);
// Parse parameters
ParseParameters(xElement, cppCallable);
cppCallable.ReturnValue = new CppReturnValue();
ResolveAndFillType(xElement.AttributeValue("returns"), cppCallable.ReturnValue);
Logger.PopContext();
}
///
/// Parses a C++ COM interface.
///
/// The gccxml that describes a C++ COM interface declaration.
/// A C++ interface parsed
private CppInterface ParseInterface(XElement xElement)
{
// If element is already transformed, return it
var cppInterface = xElement.Annotation();
if (cppInterface != null)
return cppInterface;
// Else, create a new CppInterface
cppInterface = new CppInterface(xElement.AttributeValue("name"));
xElement.AddAnnotation(cppInterface);
// Enter Interface description
Logger.PushContext("Interface:[{0}]", cppInterface.Name);
// Calculate offset method using inheritance
var offsetMethod = 0;
var basesValue = xElement.AttributeValue("bases");
var bases = basesValue?.Split(' ') ?? Enumerable.Empty();
foreach (var xElementBaseId in bases)
{
if (string.IsNullOrEmpty(xElementBaseId))
continue;
var xElementBase = _mapIdToXElement[xElementBaseId];
CppInterface cppInterfaceBase = null;
Logger.RunInContext("Base", () => { cppInterfaceBase = ParseInterface(xElementBase); });
if (string.IsNullOrEmpty(cppInterface.Base) && IsTypeBinded(xElementBase))
cppInterface.Base = cppInterfaceBase.Name;
offsetMethod += cppInterfaceBase.TotalMethodCount;
}
// Parse annotations
ParseAnnotations(xElement, cppInterface);
int offsetMethodBase = offsetMethod;
var methods = new List();
// Parse methods
foreach (var method in xElement.Elements())
{
// Parse method with pure virtual (=0) and that do not override any other methods
if (method.Name.LocalName == "Method" && !string.IsNullOrWhiteSpace(method.AttributeValue("pure_virtual"))
&& string.IsNullOrWhiteSpace(method.AttributeValue("overrides")))
{
CppMethod cppMethod = new(method.AttributeValue("name"));
ParseCallable(cppMethod, method);
methods.Add(cppMethod);
cppMethod.Offset = offsetMethod++;
}
}
SetMethodsWindowsOffset(methods, offsetMethodBase);
// Add the methods to the interface with the correct offsets
foreach (var cppMethod in methods)
{
cppInterface.Add(cppMethod);
}
cppInterface.TotalMethodCount = offsetMethod;
// Leave Interface
Logger.PopContext();
return cppInterface;
}
private static void SetMethodsWindowsOffset(IEnumerable nativeMethods, int vtableIndexStart)
{
List methods = new List(nativeMethods);
// The Visual C++ compiler breaks the rules of the COM ABI when overloaded methods are used.
// It will group the overloads together in memory and lay them out in the reverse of their declaration order.
// Since CastXML always lays them out in the order declared, we have to modify the order of the methods to match Visual C++.
for (int i = 0; i < methods.Count; i++)
{
var name = methods[i].Name;
// Look for overloads of this function
for (int j = i + 1; j < methods.Count; j++)
{
var nextMethod = methods[j];
if (nextMethod.Name == name)
{
// Remove this one from its current position further into the vtable
methods.RemoveAt(j);
// Put this one before all other overloads (aka reverse declaration order)
var k = i - 1;
while (k >= 0 && methods[k].Name == name)
k--;
methods.Insert(k + 1, nextMethod);
i++;
}
}
}
int methodOffset = vtableIndexStart;
foreach (var cppMethod in methods)
{
cppMethod.WindowsOffset = methodOffset++;
}
}
///
/// Parses a C++ field declaration.
///
/// The gccxml that describes a C++ structure field declaration.
/// A C++ field parsed
private CppField ParseField(XElement xElement, int fieldOffset)
{
var fieldName = xElement.AttributeValue("name");
var cppField = new CppField(string.IsNullOrEmpty(fieldName) ? $"field{fieldOffset}" : fieldName)
{
Offset = fieldOffset
};
Logger.PushContext("Field:[{0}]", cppField.Name);
// Handle bitfield info
var bitField = xElement.AttributeValue("bits");
if (!string.IsNullOrEmpty(bitField))
{
cppField.IsBitField = true;
// Todo, int.Parse could failed?
cppField.BitOffset = int.Parse(bitField);
}
ResolveAndFillType(xElement.AttributeValue("type"), cppField);
Logger.PopContext();
return cppField;
}
///
/// Parses a C++ struct or union declaration.
///
/// The gccxml that describes a C++ struct or union declaration.
/// The C++ parent object (valid for anonymous inner declaration) .
/// An index that counts the number of anonymous declaration in order to set a unique name
/// A C++ struct parsed
private CppStruct ParseStructOrUnion(XElement xElement, CppElement cppParent = null, int innerAnonymousIndex = 0)
{
var cppStruct = xElement.Annotation();
if (cppStruct != null)
return cppStruct;
// Build struct name directly from the struct name or based on the parent
var structName = GetStructName(xElement, cppParent, innerAnonymousIndex);
// Create struct
cppStruct = new CppStruct(structName);
xElement.AddAnnotation(cppStruct);
var isUnion = (xElement.Name.LocalName == CastXml.TagUnion);
cppStruct.IsUnion = isUnion;
// Enter struct/union description
Logger.PushContext("{0}:[{1}]", xElement.Name.LocalName, cppStruct.Name);
var basesValue = xElement.AttributeValue("bases");
var bases = basesValue != null ? basesValue.Split(' ') : Enumerable.Empty();
cppStruct.Base = GetStructDirectBase(bases);
// Parse all fields
var fieldOffset = 0;
var innerStructCount = 0;
foreach (var field in xElement.Elements())
{
if (field.Name.LocalName != CastXml.TagField)
continue;
// Parse the field
var cppField = ParseField(field, fieldOffset);
// Test if the field type is declared inside this struct or union
var fieldName = field.AttributeValue("name");
var fieldType = _mapIdToXElement[field.AttributeValue("type")];
if (fieldType.AttributeValue("context") == xElement.AttributeValue("id"))
{
var fieldSubStruct = ParseStructOrUnion(fieldType, cppStruct, innerStructCount++);
// If fieldName is empty, then we need to inline fields from the struct/union.
if (string.IsNullOrEmpty(fieldName))
{
// Make a copy in order to remove fields
var listOfSubFields = new List(fieldSubStruct.Fields);
// Copy the current field offset
var lastFieldOffset = fieldOffset;
foreach (var subField in listOfSubFields)
{
subField.Offset = subField.Offset + fieldOffset;
cppStruct.Add(subField);
lastFieldOffset = subField.Offset;
}
// Set the current field offset according to the inlined fields
if (!isUnion)
fieldOffset = lastFieldOffset;
// Don't add the current field, as it is actually an inline struct/union
cppField = null;
}
else
{
// Get the type name from the inner-struct and set it to the field
cppField.TypeName = fieldSubStruct.Name;
_currentCppInclude.Add(fieldSubStruct);
}
}
// Go to next field offset if not in union
var goToNextFieldOffset = !isUnion;
// Add the field if any
if (cppField != null)
{
cppStruct.Add(cppField);
// TODO managed multiple bitfield group
// Current implem is only working with a single set of consecutive bitfield in the same struct
goToNextFieldOffset = goToNextFieldOffset && !cppField.IsBitField;
}
if (goToNextFieldOffset)
fieldOffset++;
}
// Leave struct
Logger.PopContext();
return cppStruct;
}
private string GetStructDirectBase(IEnumerable bases)
{
string baseName = default;
foreach (var xElementBaseId in bases)
{
if (string.IsNullOrEmpty(xElementBaseId))
continue;
var xElementBase = _mapIdToXElement[xElementBaseId];
CppStruct cppStructBase = null;
Logger.RunInContext("Base", () => { cppStructBase = ParseStructOrUnion(xElementBase); });
if (string.IsNullOrEmpty(cppStructBase.Base))
baseName = cppStructBase.Name;
}
return baseName;
}
private static string GetStructName(XElement xElement, CppElement cppParent, int innerAnonymousIndex)
{
var structName = xElement.AttributeValue("name") ?? "";
if (cppParent != null)
{
if (string.IsNullOrEmpty(structName))
{
structName = cppParent.Name + "_INNER_" + innerAnonymousIndex;
}
else
{
structName = cppParent.Name + "_" + structName + "_INNER";
}
}
return structName;
}
///
/// Parses a C++ enum declaration.
///
/// The gccxml that describes a C++ enum declaration.
/// A C++ parsed enum
private CppEnum ParseEnum(XElement xElement)
{
var name = xElement.AttributeValue("name");
// Doh! Anonymous Enum, need to handle them!
if (string.IsNullOrEmpty(name) || name.StartsWith("$"))
{
var includeFrom = GetIncludeIdFromFileId(xElement.AttributeValue("file"));
if (!_mapIncludeToAnonymousEnumCount.TryGetValue(includeFrom, out int enumOffset))
_mapIncludeToAnonymousEnumCount.Add(includeFrom, enumOffset);
name = includeFrom.ToUpper() + "_ENUM_" + enumOffset;
_mapIncludeToAnonymousEnumCount[includeFrom]++;
}
CppEnum cppEnum = new(name);
foreach (var xEnumItems in xElement.Elements())
{
var enumItemName = xEnumItems.AttributeValue("name");
if (enumItemName.EndsWith(CppExtensionHeaderGenerator.EndTagCustomEnumItem))
enumItemName = enumItemName.Substring(0, enumItemName.Length - CppExtensionHeaderGenerator.EndTagCustomEnumItem.Length);
cppEnum.Add(new CppEnumItem(enumItemName, xEnumItems.AttributeValue("init")));
}
return cppEnum;
}
///
/// Parses a C++ variable declaration/definition.
///
/// The gccxml that describes a C++ variable declaration/definition.
/// A C++ parsed variable
private CppElement ParseVariable(XElement xElement)
{
var name = xElement.AttributeValue("name");
if (name.EndsWith(CppExtensionHeaderGenerator.EndTagCustomVariable))
name = name.Substring(0, name.Length - CppExtensionHeaderGenerator.EndTagCustomVariable.Length);
var typeName = ResolveType(xElement.AttributeValue("type"));
var value = xElement.AttributeValue("init") ?? string.Empty;
if (typeName == "GUID")
{
var guid = ParseGuid(value);
return guid.HasValue ? new CppGuid(name, guid.Value) : null;
}
// CastXML outputs initialization expressions. Cast to proper type.
var match = Regex.Match(value, @"\((?:\(.+\))?(.+)\)");
if (match.Success)
{
value = $"unchecked(({typeName}){match.Groups[1].Value})";
}
// Handle C++ floating point literals
value = value.Replace(".F", ".0F");
return new CppConstant(name, value);
}
///
/// Parses a C++ GUID definition string.
///
/// The text of a GUID gccxml initialization.
/// The parsed Guid
private static Guid? ParseGuid(string guidInitText)
{
// init="{-1135593225ul, 9184u, 18784u, {150u, 218u, 51u, 171u, 175u, 89u, 53u, 236u}}"
if (!guidInitText.StartsWith("{") && !guidInitText.EndsWith("}}"))
return null;
guidInitText = guidInitText.Replace("{", "");
guidInitText = guidInitText.TrimEnd('}');
guidInitText = guidInitText.Replace("u", "");
guidInitText = guidInitText.Replace("U", "");
guidInitText = guidInitText.Replace("l", "");
guidInitText = guidInitText.Replace("L", "");
guidInitText = guidInitText.Replace(" ", "");
var guidElements = guidInitText.Split(',');
if (guidElements.Length != 11)
return null;
var values = new int[guidElements.Length];
for (int i = 0; i < guidElements.Length; i++)
{
var guidElement = guidElements[i];
if (!long.TryParse(guidElement, out long value))
return null;
values[i] = unchecked((int)value);
}
return new Guid(values[0], (short)values[1], (short)values[2], (byte)values[3], (byte)values[4], (byte)values[5], (byte)values[6], (byte)values[7],
(byte)values[8], (byte)values[9], (byte)values[10]);
}
///
/// Parses all C++ elements. This is the main method that iterates on all types.
///
private void ParseAllElements()
{
foreach (var includeGccXmlId in _mapFileToXElement.Keys)
{
var includeId = GetIncludeIdFromFileId(includeGccXmlId);
// Process only files listed inside the config files
if (!_includeToProcess.Contains(includeId))
continue;
// Process only files attached (fully or partially) to an assembly/namespace
if (!_includeIsAttached.TryGetValue(includeId, out bool isIncludeFullyAttached))
continue;
// Log current include being processed
Logger.PushContext("Include:[{0}.h]", includeId);
_currentCppInclude = _group.FindInclude(includeId);
if (_currentCppInclude == null)
{
_currentCppInclude = new CppInclude(includeId);
_group.Add(_currentCppInclude);
}
ParseElementsInInclude(includeGccXmlId, includeId, isIncludeFullyAttached);
Logger.PopContext();
}
}
private void ParseElementsInInclude(string includeGccXmlId, string includeId, bool isIncludeFullyAttached)
{
foreach (var xElement in _mapFileToXElement[includeGccXmlId])
{
// If the element is not defined from a root namespace
// than skip it, as it might be an inner type
if (_mapIdToXElement[xElement.AttributeValue("context")].Name.LocalName != CastXml.TagNamespace)
continue;
// If incomplete flag, than element cannot be parsed
if (xElement.AttributeValue("incomplete") != null)
continue;
var elementName = xElement.AttributeValue("name");
// If this include is partially attached and the current type is not attached
// Than skip it, as we are not mapping it
if (!isIncludeFullyAttached && !_includeAttachedTypes[includeId].Contains(elementName))
continue;
// Ignore CastXML built-in functions
if (elementName.StartsWith("__builtin"))
continue;
var cppElement = ParseElement(xElement);
if (cppElement != null)
_currentCppInclude.Add(cppElement);
}
}
private CppElement ParseElement(XElement xElement)
{
switch (xElement.Name.LocalName)
{
case CastXml.TagEnumeration:
return ParseEnum(xElement);
case CastXml.TagFunction:
// TODO: Find better criteria for exclusion. In CastXML extern="1" only indicates an explicit external storage modifier.
// For now, exclude inline functions instead; may not be sensible since by default all functions have external linkage.
if (xElement.AttributeValue("inline") == null)
return ParseFunction(xElement);
break;
case CastXml.TagClass:
case CastXml.TagStruct:
return xElement.AttributeValue("abstract") != null ? (CppElement)ParseInterface(xElement) : ParseStructOrUnion(xElement);
case CastXml.TagUnion:
return ParseStructOrUnion(xElement);
case CastXml.TagVariable:
if (xElement.AttributeValue("init") != null)
return ParseVariable(xElement);
break;
}
return null;
}
///
/// Determines whether the specified type is a type included in the mapping process.
///
/// The type to check.
///
/// true if the specified type is included in the mapping process; otherwise, false.
///
private bool IsTypeFromIncludeToProcess(XElement type)
{
var fileId = type.AttributeValue("file");
if (fileId != null)
return _includeToProcess.Contains(GetIncludeIdFromFileId(fileId));
return false;
}
///
/// Determines whether the specified type is bound in the mapping process and will be represented in the C# model.
///
/// The type to check.
///
/// true if the specified type is bound in the mapping process; otherwise, false.
///
private bool IsTypeBinded(XElement type)
=> IsTypeFromIncludeToProcess(type) || _boundTypes.Contains(type.AttributeValue("name"));
///
/// Resolves a type to its fundamental type or a binded type.
/// This methods is going through the type declaration in order to return the most fundamental type
/// or to return a bind.
///
/// The id of the type to resolve.
/// The C++ type to fill.
private void ResolveAndFillType(string typeId, CppMarshallable type)
{
var xType = _mapIdToXElement[typeId];
var isTypeResolved = false;
while (!isTypeResolved)
{
var name = xType.AttributeValue("name");
var nextType = xType.AttributeValue("type");
switch (xType.Name.LocalName)
{
case CastXml.TagFundamentalType:
type.TypeName = ConvertFundamentalType(name);
isTypeResolved = true;
break;
case CastXml.TagClass:
case CastXml.TagEnumeration:
case CastXml.TagStruct:
case CastXml.TagUnion:
type.TypeName = name;
isTypeResolved = true;
break;
case CastXml.TagTypedef:
if (_boundTypes.Contains(name))
{
type.TypeName = name;
isTypeResolved = true;
}
xType = _mapIdToXElement[nextType];
break;
case CastXml.TagPointerType:
xType = _mapIdToXElement[nextType];
type.Pointer += "*";
break;
case CastXml.TagArrayType:
var maxArrayIndex = xType.AttributeValue("max");
var arrayDim = int.Parse(maxArrayIndex.TrimEnd('u')) + 1;
if (type.ArrayDimension == null)
type.ArrayDimension = arrayDim.ToString();
else
type.ArrayDimension += "," + arrayDim;
xType = _mapIdToXElement[nextType];
break;
case CastXml.TagReferenceType:
xType = _mapIdToXElement[nextType];
type.Pointer += "&";
break;
case CastXml.TagCvQualifiedType:
xType = _mapIdToXElement[nextType];
type.Const = true;
break;
case CastXml.TagFunctionType:
// TODO, handle different calling convention
type.TypeName = "__function__stdcall";
isTypeResolved = true;
break;
default:
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unexpected tag type [{0}]", xType.Name.LocalName));
}
}
}
private string ResolveType(string typeId)
{
var xType = _mapIdToXElement[typeId];
while (true)
{
var name = xType.AttributeValue("name");
var nextType = xType.AttributeValue("type");
switch (xType.Name.LocalName)
{
case CastXml.TagFundamentalType:
return ConvertFundamentalType(name);
case CastXml.TagClass:
case CastXml.TagEnumeration:
case CastXml.TagStruct:
case CastXml.TagUnion:
return name;
case CastXml.TagTypedef:
if (_boundTypes.Contains(name))
{
return name;
}
xType = _mapIdToXElement[nextType];
break;
case CastXml.TagPointerType:
case CastXml.TagArrayType:
case CastXml.TagReferenceType:
case CastXml.TagCvQualifiedType:
xType = _mapIdToXElement[nextType];
break;
case CastXml.TagFunctionType:
// TODO, handle different calling convention
return "__function__stdcall";
default:
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unexpected tag type [{0}]", xType.Name.LocalName));
}
}
}
///
/// Converts a gccxml FundamentalType to a shorter form:
/// signed char => char
/// long long int => long long
/// short unsigned int => unsigned short
/// char => char
/// long unsigned int => unsigned int
/// short int => short
/// int => int
/// long int => long
/// float => float
/// unsigned char => unsigned char
/// unsigned int => unsigned int
/// wchar_t => wchar_t
/// long long unsigned int => unsigned long long
/// double => double
/// void => void
/// long double => long double
///
/// Name of the gccxml fundamental type.
/// a shorten form
private string ConvertFundamentalType(string typeName)
{
var types = typeName.Split(' ');
var isUnsigned = false;
var outputType = "";
var shortCount = 0;
var longCount = 0;
foreach (var type in types)
{
switch (type)
{
case "unsigned":
isUnsigned = true;
break;
case "signed":
outputType = "int";
break;
case "long":
longCount++;
break;
case "short":
shortCount++;
break;
case "bool":
case "void":
case "char":
case "double":
case "int":
case "float":
case "wchar_t":
outputType = type;
break;
default:
Logger.Error(LoggingCodes.UnknownFundamentalType, "Unhandled partial type [{0}] from Fundamental type [{1}]", type, typeName);
break;
}
}
if (longCount == 1)
outputType = "long";
if (longCount == 1 && outputType == "double")
outputType = "long double"; // 96 bytes, unhandled
if (longCount == 2)
outputType = "long long";
if (shortCount == 1)
outputType = "short";
if (isUnsigned)
outputType = "unsigned " + outputType;
return outputType;
}
///
/// Gets the include id from the file id.
///
/// The file id.
/// A include id
private string GetIncludeIdFromFileId(string fileId)
{
var filePath = _mapIdToXElement[fileId].AttributeValue("name");
try
{
if (!File.Exists(filePath))
return "";
}
catch (ArgumentException)
{
return "";
}
return Path.GetFileNameWithoutExtension(filePath);
}
}
}