// Copyright (c) 2010-2014 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.IO;
using System.Linq;
using SharpGen.Logging;
using SharpGen.Config;
using SharpGen.CppModel;
using SharpGen.Model;
namespace SharpGen.Transform
{
///
/// This class is responsible for generating the C# model from C++ model.
///
public sealed class TransformManager
{
private IDocumentationLinker DocLinker => ioc.DocumentationLinker;
private TypeRegistry TypeRegistry => ioc.TypeRegistry;
private Logger Logger => ioc.Logger;
private NamespaceRegistry NamespaceRegistry { get; }
private readonly List includesToProcess = new();
private readonly ConstantManager constantManager;
private readonly GroupRegistry groupRegistry = new();
private readonly Ioc ioc;
///
/// Initializes a new instance of the class.
///
public TransformManager(NamingRulesManager namingRules, ConstantManager constantManager, Ioc ioc)
{
NamingRules = namingRules;
this.constantManager = constantManager;
this.ioc = ioc ?? throw new ArgumentNullException(nameof(ioc));
NamespaceRegistry = new NamespaceRegistry(ioc);
MarshalledElementFactory marshalledElementFactory = new(ioc);
InteropSignatureTransform interopSignatureTransform = new(ioc);
EnumTransform = new EnumTransform(namingRules, NamespaceRegistry, ioc);
StructTransform = new StructTransform(namingRules, NamespaceRegistry, marshalledElementFactory, ioc);
FunctionTransform = new MethodTransform(namingRules, groupRegistry, marshalledElementFactory, interopSignatureTransform, ioc);
InterfaceTransform = new InterfaceTransform(namingRules, FunctionTransform, FunctionTransform, NamespaceRegistry, interopSignatureTransform, ioc);
}
///
/// Gets the naming rules manager.
///
/// The naming rules manager.
private NamingRulesManager NamingRules { get; }
///
/// Gets or sets the enum transformer.
///
/// The enum transformer.
private EnumTransform EnumTransform { get; }
///
/// Gets or sets the struct transformer.
///
/// The struct transformer.
private StructTransform StructTransform { get; }
///
/// Gets or sets the method transformer.
///
/// The method transformer.
private MethodTransform FunctionTransform { get; }
///
/// Gets or sets the interface transformer.
///
/// The interface transformer.
private InterfaceTransform InterfaceTransform { get; }
///
/// Initializes this instance with the specified C++ module and config.
///
/// The module to transform after mapping rules have been applied.
private CppModule MapModule(CppModule cppModule, IReadOnlyCollection configFiles)
{
var numberOfConfigFilesToParse = configFiles.Count;
var indexFile = 0;
// Process each config file
foreach (var configFile in configFiles)
{
Logger.Progress(
30 + (indexFile * 30) / numberOfConfigFilesToParse,
"Processing mapping rules [{0}]",
configFile.Id
);
ProcessCppModuleWithConfig(cppModule, configFile);
indexFile++;
}
// Strip out includes we aren't processing from transformation
var moduleToTransform = new CppModule(cppModule.Name);
moduleToTransform.AddRange(
cppModule.Includes
.Where(cppInclude => includesToProcess.Contains(cppInclude.Name))
);
return moduleToTransform;
}
///
/// Adds the include to process.
///
/// The include id.
private void AddIncludeToProcess(string includeId)
{
if (!includesToProcess.Contains(includeId))
includesToProcess.Add(includeId);
}
///
/// Process the specified config file.
///
/// The file.
private void ProcessCppModuleWithConfig(CppModule cppModule, ConfigFile file)
{
Logger.PushLocation(file.AbsoluteFilePath);
try
{
Logger.Message($"Process rules for config [{file.Id}] namespace [{file.Namespace}]");
var elementFinder = new CppElementFinder(cppModule);
if (file.Namespace != null)
{
AttachIncludes(file);
ProcessExtensions(elementFinder, file);
}
ProcessMappings(elementFinder, file);
}
finally
{
Logger.PopLocation();
}
}
private void UpdateNamingRules(ConfigFile file)
{
foreach (var namingRule in file.Naming)
{
if (namingRule is NamingRuleShort)
NamingRules.AddShortNameRule(namingRule.Name, namingRule.Value);
}
}
private void RegisterBindings(ConfigFile file)
{
foreach (var bindingRule in file.Bindings)
{
TypeRegistry.BindType(
bindingRule.From,
TypeRegistry.ImportType(bindingRule.To),
string.IsNullOrEmpty(bindingRule.Marshal)
? null
: TypeRegistry.ImportType(bindingRule.Marshal),
file.Id
);
}
}
private void ProcessDefines(ConfigFile file)
{
foreach (var defineRule in file.Extension.OfType())
{
CsTypeBase defineType;
if (defineRule.Enum != null)
{
var underlyingType = string.IsNullOrWhiteSpace(defineRule.UnderlyingType)
? null
: TypeRegistry.ImportPrimitiveType(defineRule.UnderlyingType);
if (defineRule.SizeOf is {} size && underlyingType == null)
{
underlyingType = size switch
{
1 => TypeRegistry.UInt8,
2 => TypeRegistry.Int16,
4 => TypeRegistry.Int32,
_ => null
};
}
var newEnum = new CsEnum(null, defineRule.Enum, underlyingType);
defineType = newEnum;
}
else if (defineRule.Struct != null)
{
var newStruct = new CsStruct(null, defineRule.Struct);
defineType = newStruct;
if (defineRule.HasCustomMarshal is { } hasCustomMarshal)
newStruct.HasMarshalType = hasCustomMarshal;
if (defineRule.IsStaticMarshal is { } isStaticMarshal)
newStruct.IsStaticMarshal = isStaticMarshal;
if (defineRule.HasCustomNew is { } hasCustomNew)
newStruct.HasCustomNew = hasCustomNew;
if (defineRule.SizeOf is { } size)
newStruct.StructSize = checked((uint) size);
if (defineRule.Align is { } align)
newStruct.Align = align;
if (defineRule.IsNativePrimitive is { } isNativePrimitive)
newStruct.IsNativePrimitive = isNativePrimitive;
}
else if (defineRule.Interface != null)
{
var iface = new CsInterface(null, defineRule.Interface);
if (defineRule.ShadowName is {} shadowName)
iface.ShadowName = shadowName;
if (defineRule.VtblName is {} vtblName)
iface.VtblName = vtblName;
if (defineRule.NativeImplementation != null)
{
iface.NativeImplementation = new CsInterface(null, defineRule.NativeImplementation)
{
IsDualCallback = true
};
iface.IsCallback = true;
iface.IsDualCallback = true;
TypeRegistry.DefineType(iface.NativeImplementation);
}
defineType = iface;
}
else
{
Logger.Error(LoggingCodes.MissingElementInRule, "Invalid rule [{0}]. Requires one of enum, struct, or interface", defineRule);
continue;
}
// Define this type
TypeRegistry.DefineType(defineType);
}
}
private void ProcessExtensions(CppElementFinder elementFinder, ConfigFile file)
{
// Register defined Types from tag
foreach (var extensionRule in file.Extension)
{
if (extensionRule is CreateExtensionRule createRule)
{
if (createRule.NewClass != null)
{
var functionGroup = CreateCsGroup(file.Namespace, createRule.NewClass);
if (createRule.Visibility.HasValue)
functionGroup.Visibility = createRule.Visibility.Value;
}
else
Logger.Error(LoggingCodes.MissingElementInRule, "Invalid rule [{0}]. Requires class", createRule);
}
else if (extensionRule is ConstantRule constantRule)
{
HandleConstantRule(elementFinder, constantRule, file.Namespace);
}
else if (extensionRule is ContextRule contextRule)
{
HandleContextRule(elementFinder, file, contextRule);
}
}
}
private void AttachIncludes(ConfigFile file)
{
// Add all includes file
foreach (var includeRule in file.Includes)
{
if (includeRule.Attach.HasValue && includeRule.Attach.Value)
{
AddIncludeToProcess(includeRule.Id);
NamespaceRegistry.MapIncludeToNamespace(includeRule.Id, includeRule.Namespace ?? file.Namespace, includeRule.Output);
}
else
{
// include will be processed
if (includeRule.AttachTypes.Count > 0)
AddIncludeToProcess(includeRule.Id);
foreach (var attachType in includeRule.AttachTypes)
NamespaceRegistry.AttachTypeToNamespace($"^{attachType}$", includeRule.Namespace ?? file.Namespace, includeRule.Output);
}
}
// Add extensions if any
if (file.Extension.Count > 0)
{
AddIncludeToProcess(file.ExtensionId);
NamespaceRegistry.MapIncludeToNamespace(file.ExtensionId, file.Namespace, null);
}
}
private void ProcessMappings(CppElementFinder elementFinder, ConfigFile file)
{
// Perform all mappings from tag
foreach (var configRule in file.Mappings)
{
var ruleUsed = false;
switch (configRule)
{
case MappingRule {Enum: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Enum, mappingRule);
break;
case MappingRule {EnumItem: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.EnumItem, mappingRule);
break;
case MappingRule {Struct: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Struct, mappingRule);
break;
case MappingRule {Field: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Field, mappingRule);
break;
case MappingRule {Interface: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Interface, mappingRule);
break;
case MappingRule {Function: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Function, mappingRule);
break;
case MappingRule {Method: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Method, mappingRule);
break;
case MappingRule {Parameter: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Parameter, mappingRule);
break;
case MappingRule {Element: { }} mappingRule:
ruleUsed = elementFinder.ExecuteRule(mappingRule.Element, mappingRule);
break;
case MappingRule {DocItem: { }} mappingRule:
DocLinker.AddOrUpdateDocLink(mappingRule.DocItem, mappingRule.MappingNameFinal);
ruleUsed = true;
break;
case RemoveRule {Enum: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Enum);
break;
case RemoveRule {EnumItem: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.EnumItem);
break;
case RemoveRule {Struct: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Struct);
break;
case RemoveRule {Field: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Field);
break;
case RemoveRule {Interface: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Interface);
break;
case RemoveRule {Function: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Function);
break;
case RemoveRule {Method: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Method);
break;
case RemoveRule {Parameter: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Parameter);
break;
case RemoveRule {Element: { }} removeRule:
ruleUsed = RemoveElements(elementFinder, removeRule.Element);
break;
case ContextRule contextRule:
HandleContextRule(elementFinder, file, contextRule);
ruleUsed = true;
break;
case MoveRule moveRule:
{
if (moveRule.Struct != null)
StructTransform.MoveStructToInner(moveRule.Struct, moveRule.To);
else if (moveRule.Method != null)
InterfaceTransform.MoveMethodsToInnerInterface(moveRule.Method, moveRule.To, moveRule.Property, moveRule.Base);
ruleUsed = true;
break;
}
}
if (!ruleUsed)
{
Logger.Warning(LoggingCodes.UnusedMappingRule, "Mapping rule [{0}] did not match any elements.", configRule);
}
}
}
private static bool RemoveElements(CppElementFinder finder, string regex)
where T : CppElement
{
var matchedAny = false;
foreach (var item in finder.Find(regex).ToList())
{
matchedAny = true;
item.RemoveFromParent();
}
return matchedAny;
}
///
/// Handles the context rule.
///
/// The file.
/// The context rule.
/// The C++ Module we are handling the context rule for.
private void HandleContextRule(CppElementFinder moduleMapper, ConfigFile file, ContextRule contextRule)
{
if (contextRule is ClearContextRule)
moduleMapper.ClearCurrentContexts();
else
{
var contextIds = new List();
if (!string.IsNullOrEmpty(contextRule.ContextSetId))
{
var contextSet = file.FindContextSetById(contextRule.ContextSetId);
if (contextSet != null)
contextIds.AddRange(contextSet.Contexts);
}
contextIds.AddRange(contextRule.Ids);
moduleMapper.AddContexts(contextIds);
}
}
private void Init(IReadOnlyCollection configFiles)
{
// We have to do these steps first, otherwise we'll get undefined types for types we've mapped, which breaks the mapping.
foreach (var configFile in configFiles)
{
Logger.RunInContext(
configFile.AbsoluteFilePath,
() =>
{
// Update Naming Rules
UpdateNamingRules(configFile);
ProcessDefines(configFile);
});
}
foreach (var configFile in configFiles)
{
Logger.RunInContext(
configFile.AbsoluteFilePath,
() =>
{
RegisterBindings(configFile);
});
}
}
///
/// Maps all C++ types to C#
///
/// The C++ module to parse.
/// The config file to use to transform the C++ module into C# assemblies.
public (CsAssembly assembly, IEnumerable consumerExtensions) Transform(CppModule cppModule, ConfigFile configFile)
{
Init(configFile.ConfigFilesLoaded);
var moduleToTransform = MapModule(cppModule, configFile.ConfigFilesLoaded);
var selectedCSharpType = new List();
// Prepare transform by defining/registering all types to process
selectedCSharpType.AddRange(PrepareTransform(moduleToTransform, EnumTransform));
selectedCSharpType.AddRange(PrepareTransform(moduleToTransform, StructTransform));
selectedCSharpType.AddRange(PrepareTransform(moduleToTransform, InterfaceTransform));
selectedCSharpType.AddRange(PrepareTransform(moduleToTransform, FunctionTransform));
// Transform all types
Logger.Progress(65, "Transforming enums...");
ProcessTransform(EnumTransform, selectedCSharpType.OfType());
Logger.Progress(70, "Transforming structs...");
ProcessTransform(StructTransform, selectedCSharpType.OfType());
Logger.Progress(75, "Transforming interfaces...");
ProcessTransform(InterfaceTransform, selectedCSharpType.OfType());
Logger.Progress(80, "Transforming functions...");
ProcessTransform(FunctionTransform, selectedCSharpType.OfType());
CsAssembly asm = new();
foreach (var ns in NamespaceRegistry.Namespaces)
{
foreach (var group in ns.Classes)
{
constantManager.AttachConstants(group);
}
asm.Add(ns);
}
return (asm, configFile.ConfigFilesLoaded.SelectMany(file => file.Extension.OfType()));
}
///
/// Prepares a transformer from C++ to C# model.
///
/// The C++ type of data to process
/// The transform.
/// The type to process.
private IEnumerable PrepareTransform(CppModule cppModule, ITransformPreparer transform)
where TCppElement : CppElement
where TCsElement : CsBase
{
var csElements = new List();
// Predefine all structs, typedefs and interfaces
foreach (var cppInclude in cppModule.Includes)
{
foreach (var cppItem in cppInclude.Iterate())
{
Logger.RunInContext(
cppItem.ToString(),
() =>
{
// If already mapped, it means that there is already a predefined mapping
if (TypeRegistry.FindBoundType(cppItem.Name) == null)
{
var csElement = transform.Prepare(cppItem);
if (csElement != null)
csElements.Add(csElement);
}
});
}
}
return csElements;
}
///
/// Processes a transformer from C++ to C# model.
///
/// The C++ type of data to process
/// The transform.
/// The type to process.
private void ProcessTransform(ITransformer transform, IEnumerable typeToProcess)
where T : CsBase
{
foreach (var csItem in typeToProcess)
{
Logger.RunInContext(
csItem.CppElement.ToString(),
() =>
{
transform.Process(csItem);
constantManager.AttachConstants(csItem);
});
}
}
///
/// Creates the C# class container used to group together loose elements (i.e. functions, constants).
///
/// Name of the namespace.
/// Name of the class.
///
/// The C# class container
private CsGroup CreateCsGroup(string namespaceName, string className)
{
if (className == null) throw new ArgumentNullException(nameof(className));
if (className.Contains("."))
{
namespaceName = Path.GetFileNameWithoutExtension(className);
className = Path.GetExtension(className).Trim('.');
}
var csNameSpace = NamespaceRegistry.GetOrCreateNamespace(namespaceName);
foreach (var cSharpFunctionGroup in csNameSpace.Classes)
{
if (cSharpFunctionGroup.Name == className)
return cSharpFunctionGroup;
}
CsGroup group = new(className);
csNameSpace.Add(group);
groupRegistry.RegisterGroup(namespaceName + "." + className, group);
return group;
}
///
/// Handles the constant rule.
///
/// The constant rule.
private void HandleConstantRule(CppElementFinder elementFinder, ConstantRule constantRule, string nameSpace)
{
constantManager.AddConstantFromMacroToCSharpType(
elementFinder,
constantRule.Macro ?? constantRule.Guid,
constantRule.ClassName,
constantRule.Type,
constantRule.Name,
constantRule.Value,
constantRule.Visibility,
nameSpace);
}
public (IEnumerable bindings, IEnumerable defines) GenerateTypeBindingsForConsumers()
{
return (from record in TypeRegistry.GetTypeBindings()
select new BindRule(record.CppType, record.CSharpType.QualifiedName, record.MarshalType?.QualifiedName),
GenerateDefinesForMappedTypes());
}
private IEnumerable GenerateDefinesForMappedTypes()
{
foreach (var (_, CSharpType, _) in TypeRegistry.GetTypeBindings())
{
switch (CSharpType)
{
case CsEnum csEnum:
CsFundamentalType tempQualifier = csEnum.UnderlyingType;
yield return new DefineExtensionRule
{
Enum = csEnum.QualifiedName,
SizeOf = checked((int) csEnum.Size),
UnderlyingType = tempQualifier?.Name
};
break;
case CsStruct csStruct:
yield return new DefineExtensionRule
{
Struct = csStruct.QualifiedName,
SizeOf = checked((int) csStruct.Size),
Align = csStruct.Align,
HasCustomMarshal = csStruct.HasCustomMarshal,
HasCustomNew = csStruct.HasCustomNew,
IsStaticMarshal = csStruct.IsStaticMarshal,
IsNativePrimitive = csStruct.IsNativePrimitive
};
break;
case CsInterface csInterface:
yield return new DefineExtensionRule
{
Interface = csInterface.QualifiedName,
NativeImplementation = csInterface.NativeImplementation?.QualifiedName,
ShadowName = csInterface.ShadowName,
VtblName = csInterface.VtblName
};
break;
}
}
}
}
}