From 5c7c16cc9bcae62d9b1c50b33e2868b65b6027ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20H=C3=B8is=C3=A6ther=20Rasch?= Date: Tue, 28 May 2024 22:07:02 +0200 Subject: [PATCH] Add XML Serialization classes --- .../GlobalSuppressions.cs | 16 ++++ .../XML/BindingsElement.cs | 18 ++++ .../XML/CSharpCodeElement.cs | 15 +++ .../XML/ConstantDescElement.cs | 8 ++ .../XML/DelegateDescElement.cs | 8 ++ .../XML/EnumConstantDeclElement.cs | 10 ++ .../XML/EnumDescElement.cs | 19 ++++ .../XML/FieldDescElement.cs | 54 +++++++++++ .../XML/FieldTypeElement.cs | 15 +++ .../XML/FunctionBodyElement.cs | 12 +++ .../XML/FunctionDescElement.cs | 19 ++++ .../XML/FunctionOrDelegateDescElement.cs | 76 +++++++++++++++ .../XML/IidDescElement.cs | 12 +++ .../XML/IndexerDeclElement.cs | 29 ++++++ .../XML/InterfaceElement.cs | 15 +++ .../XML/MethodClassElement.cs | 32 +++++++ .../XML/NamedAccessDescElement.cs | 12 +++ .../XML/NamedDescElement.cs | 12 +++ .../XML/NamespaceElement.cs | 31 +++++++ .../XML/ParameterDescElement.cs | 19 ++++ .../XML/PropertyAccessorElement.cs | 15 +++ .../XML/StructDescElement.cs | 93 +++++++++++++++++++ .../TypeInitializationExpressionElement.cs | 15 +++ .../XML/TypeWithNativeTypeNameElement.cs | 37 ++++++++ .../XML/ValueDescValueElement.cs | 21 +++++ .../XML/VtblElement.cs | 13 +++ .../PInvokeGeneratorTest.cs | 88 +++++++++++++++++- 27 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 sources/ClangSharp.PInvokeGenerator/GlobalSuppressions.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/BindingsElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/CSharpCodeElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/ConstantDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/DelegateDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/EnumConstantDeclElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/EnumDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/FieldDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/FieldTypeElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/FunctionBodyElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/FunctionDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/FunctionOrDelegateDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/IidDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/IndexerDeclElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/InterfaceElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/MethodClassElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/NamedAccessDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/NamedDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/NamespaceElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/ParameterDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/PropertyAccessorElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/StructDescElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/TypeInitializationExpressionElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/TypeWithNativeTypeNameElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/ValueDescValueElement.cs create mode 100644 sources/ClangSharp.PInvokeGenerator/XML/VtblElement.cs diff --git a/sources/ClangSharp.PInvokeGenerator/GlobalSuppressions.cs b/sources/ClangSharp.PInvokeGenerator/GlobalSuppressions.cs new file mode 100644 index 00000000..8e466e2d --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/GlobalSuppressions.cs @@ -0,0 +1,16 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Design", + "CA1002: Do not expose generic lists", + Scope = "namespaceAndDescendants", + Target = "~N:ClangSharp.XML", + Justification = nameof(System.Xml.Serialization) + )] + diff --git a/sources/ClangSharp.PInvokeGenerator/XML/BindingsElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/BindingsElement.cs new file mode 100644 index 00000000..2767aed5 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/BindingsElement.cs @@ -0,0 +1,18 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +[XmlRoot("bindings")] +public sealed class BindingsElement +{ + [XmlElement("comment")] + public string? HeaderText { get; init; } + + [XmlElement("namespace")] + public List Namespaces { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/CSharpCodeElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/CSharpCodeElement.cs new file mode 100644 index 00000000..0cf41624 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/CSharpCodeElement.cs @@ -0,0 +1,15 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public sealed class CSharpCodeElement +{ + [XmlText] + [XmlAnyElement] + public List Segments { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/ConstantDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/ConstantDescElement.cs new file mode 100644 index 00000000..e17b5b5d --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/ConstantDescElement.cs @@ -0,0 +1,8 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class ConstantDescElement : FieldDescElement { } diff --git a/sources/ClangSharp.PInvokeGenerator/XML/DelegateDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/DelegateDescElement.cs new file mode 100644 index 00000000..9458ccf0 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/DelegateDescElement.cs @@ -0,0 +1,8 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class DelegateDescElement : FunctionOrDelegateDescElement { } diff --git a/sources/ClangSharp.PInvokeGenerator/XML/EnumConstantDeclElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/EnumConstantDeclElement.cs new file mode 100644 index 00000000..25984ca4 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/EnumConstantDeclElement.cs @@ -0,0 +1,10 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Diagnostics; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +[DebuggerDisplay($"{{{nameof(Name)},nq}}")] +public class EnumConstantDeclElement : FieldDescElement { } diff --git a/sources/ClangSharp.PInvokeGenerator/XML/EnumDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/EnumDescElement.cs new file mode 100644 index 00000000..f977a50c --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/EnumDescElement.cs @@ -0,0 +1,19 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class EnumDescElement : NamedAccessDescElement +{ + [XmlElement("attribute")] + public List Attributes { get; } = []; + + [XmlElement("type")] + public required string TypeName { get; init; } + + [XmlElement("enumerator")] + public List Enumerators { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/FieldDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/FieldDescElement.cs new file mode 100644 index 00000000..3f018a67 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/FieldDescElement.cs @@ -0,0 +1,54 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +[DebuggerDisplay($"{{{nameof(AccessSpecifier)},nq}} {{{nameof(Type)}}} {{{nameof(Name)},nq}}")] +public class FieldDescElement : NamedAccessDescElement +{ + [XmlAttribute("inherited")] + public string? InheritedFrom { get; init; } + + [XmlAttribute("offset")] + public string? OffsetValue { get; init; } + + [XmlIgnore] + public int? Offset + { + get + { + return int.TryParse( + OffsetValue, + CultureInfo.InvariantCulture, + out var offset + ) ? offset : null; + } + + init + { + OffsetValue = value.HasValue + ? value.Value.ToString(CultureInfo.InvariantCulture) + : null; + } + } + + [XmlElement("attribute")] + public List Attributes { get; } = []; + + [XmlElement("type")] + public required FieldTypeElement Type { get; init; } + + [XmlElement("value")] + public ValueDescValueElement? Initializer { get; init; } + + [XmlElement("get")] + public PropertyAccessorElement? Getter { get; init; } + + [XmlElement("set")] + public PropertyAccessorElement? Setter { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/FieldTypeElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/FieldTypeElement.cs new file mode 100644 index 00000000..28908889 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/FieldTypeElement.cs @@ -0,0 +1,15 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class FieldTypeElement : TypeWithNativeTypeNameElement +{ + [XmlAttribute("count")] + public string? Count { get; init; } + + [XmlAttribute("fixed")] + public string? FixedName { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/FunctionBodyElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/FunctionBodyElement.cs new file mode 100644 index 00000000..a50446ad --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/FunctionBodyElement.cs @@ -0,0 +1,12 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class FunctionBodyElement +{ + [XmlElement("code")] + public CSharpCodeElement? Code { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/FunctionDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/FunctionDescElement.cs new file mode 100644 index 00000000..63b1cd32 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/FunctionDescElement.cs @@ -0,0 +1,19 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class FunctionDescElement : FunctionOrDelegateDescElement +{ + [XmlElement("init")] + public List TypeInitializationExpressions { get; } = []; + + [XmlElement("body")] + public FunctionBodyElement? BlockBody { get; init; } + + [XmlElement("code")] + public CSharpCodeElement? ExpressionBody { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/FunctionOrDelegateDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/FunctionOrDelegateDescElement.cs new file mode 100644 index 00000000..f89f4d11 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/FunctionOrDelegateDescElement.cs @@ -0,0 +1,76 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public abstract class FunctionOrDelegateDescElement : NamedAccessDescElement +{ + [XmlAttribute("lib")] + public string? LibraryPath { get; init; } + + [XmlAttribute("convention")] + public string? CallingConvention { get; init; } + + [XmlAttribute("entrypoint")] + public string? EntryPoint { get; init; } + + [XmlAttribute("setlasterror")] + public bool SetLastError { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool SetLastErrorSpecified => SetLastError; + + [XmlAttribute("static")] + public bool IsStatic { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsStaticSpecified => IsStatic; + + [XmlAttribute("unsafe")] + public bool IsUnsafe { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsUnsafeSpecified => IsUnsafe; + + [XmlAttribute("vtblindex")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? VtblIndexValue { get; init; } + + [XmlIgnore] + public long? VtblIndex + { + get + { + return !string.IsNullOrEmpty(VtblIndexValue) + && long.TryParse( + VtblIndexValue, + CultureInfo.InvariantCulture, + out var vtblindex + ) ? vtblindex : null; + } + + init + { + VtblIndexValue = value.HasValue + ? value.Value.ToString(CultureInfo.InvariantCulture) + : null; + } + } + + [XmlElement("attribute")] + public List Attributes { get; } = []; + + [XmlElement("type")] + public required TypeWithNativeTypeNameElement ReturnType { get; init; } + + [XmlElement("param")] + public List Parameters { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/IidDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/IidDescElement.cs new file mode 100644 index 00000000..1eacc41e --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/IidDescElement.cs @@ -0,0 +1,12 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class IidDescElement : NamedDescElement +{ + [XmlAttribute("value")] + public required string Value { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/IndexerDeclElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/IndexerDeclElement.cs new file mode 100644 index 00000000..28bac16a --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/IndexerDeclElement.cs @@ -0,0 +1,29 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.ComponentModel; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class IndexerDeclElement +{ + [XmlAttribute("access")] + public required string AccessSpecifier { get; init; } + + [XmlAttribute("unsafe")] + public bool IsUnsafe { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsUnsafeSpecified => IsUnsafe; + + [XmlElement("type")] + public required string Type { get; init; } + + [XmlElement("param")] + public required ParameterDescElement IndexerParameter { get; init; } + + [XmlElement("get")] + public required PropertyAccessorElement Getter { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/InterfaceElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/InterfaceElement.cs new file mode 100644 index 00000000..78394fcb --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/InterfaceElement.cs @@ -0,0 +1,15 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class InterfaceElement +{ + [XmlElement("constant", typeof(ConstantDescElement))] + [XmlElement("field", typeof(FieldDescElement))] + [XmlElement("function", typeof(FunctionDescElement))] + public List Decls { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/MethodClassElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/MethodClassElement.cs new file mode 100644 index 00000000..8da6ca43 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/MethodClassElement.cs @@ -0,0 +1,32 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class MethodClassElement : NamedAccessDescElement +{ + [XmlAttribute("static")] + public bool IsStatic { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsStaticSpecified => IsStatic; + + [XmlAttribute("unsafe")] + public bool IsUnsafe { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsUnsafeSpecified => IsUnsafe; + + [XmlElement("constant", typeof(ConstantDescElement))] + [XmlElement("field", typeof(FieldDescElement))] + [XmlElement("function", typeof(FunctionDescElement))] + [XmlElement("delegate", typeof(DelegateDescElement))] + [XmlElement("iid", typeof(IidDescElement))] + public List Decls { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/NamedAccessDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/NamedAccessDescElement.cs new file mode 100644 index 00000000..50fca7bb --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/NamedAccessDescElement.cs @@ -0,0 +1,12 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +public abstract class NamedAccessDescElement : NamedDescElement +{ + [XmlAttribute("access")] + public required string AccessSpecifier { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/NamedDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/NamedDescElement.cs new file mode 100644 index 00000000..9e591006 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/NamedDescElement.cs @@ -0,0 +1,12 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +public abstract class NamedDescElement +{ + [XmlAttribute("name")] + public required string Name { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/NamespaceElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/NamespaceElement.cs new file mode 100644 index 00000000..6ea2ba02 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/NamespaceElement.cs @@ -0,0 +1,31 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +[DebuggerDisplay($"{{{nameof(Name)},nq}}")] +public sealed class NamespaceElement : NamedDescElement +{ + public NamespaceElement() + { + Structs = Types.OfType(); + Enums = Types.OfType(); + } + + [XmlElement("struct", typeof(StructDescElement))] + [XmlElement("enumeration", typeof(EnumDescElement))] + [XmlElement("delegate", typeof(DelegateDescElement))] + [XmlElement("class", typeof(MethodClassElement))] + public List Types { get; } = []; + + [XmlIgnore] + public IEnumerable Structs { get; } + + [XmlIgnore] + public IEnumerable Enums { get; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/ParameterDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/ParameterDescElement.cs new file mode 100644 index 00000000..2807ac53 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/ParameterDescElement.cs @@ -0,0 +1,19 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class ParameterDescElement : NamedDescElement +{ + [XmlElement("attribute")] + public List Attributes { get; } = []; + + [XmlElement("type")] + public required string Type { get; init; } + + [XmlElement("init")] + public ValueDescValueElement? DefaultValue { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/PropertyAccessorElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/PropertyAccessorElement.cs new file mode 100644 index 00000000..40ebfd0d --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/PropertyAccessorElement.cs @@ -0,0 +1,15 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class PropertyAccessorElement +{ + [XmlAttribute("inlining")] + public string? Inlining { get; init; } + + [XmlElement("code")] + public required CSharpCodeElement Code { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/StructDescElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/StructDescElement.cs new file mode 100644 index 00000000..eaa7a114 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/StructDescElement.cs @@ -0,0 +1,93 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class StructDescElement : NamedAccessDescElement +{ + [XmlAttribute("native")] + public string? NativeTypeName { get; init; } + + [XmlAttribute("parent")] + public string? NativeInheritance { get; init; } + + [XmlAttribute("uuid")] + public string? Uuid { get; init; } + + [XmlAttribute("vtbl")] + public bool HasVtbl { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool HasVtblSpecified => HasVtbl; + + [XmlAttribute("unsafe")] + public bool IsUnsafe { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsUnsafeSpecified => IsUnsafe; + + [XmlAttribute("layout")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? LayoutValue { get; init; } + + [XmlIgnore] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool LayoutValueSpecified => !string.IsNullOrEmpty(LayoutValue); + + [XmlIgnore] + public LayoutKind? LayoutKind + { + get + { + return LayoutValueSpecified + && Enum.TryParse(LayoutValue, out LayoutKind layout) + ? layout + : null; + } + init { LayoutValue = value.HasValue ? value.ToString() : null; } + } + + [XmlAttribute("pack")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? PackValue { get; init; } + + [XmlIgnore] + public int? Pack + { + get + { + return int.TryParse(PackValue, CultureInfo.InvariantCulture, out var pack) + ? pack + : null; + } + + init + { + PackValue = value.HasValue + ? value.Value.ToString(CultureInfo.InvariantCulture) + : null; + } + } + + [XmlElement("attribute")] + public List Attributes { get; } = []; + + [XmlElement("constant", typeof(ConstantDescElement))] + [XmlElement("field", typeof(FieldDescElement))] + [XmlElement("struct", typeof(StructDescElement))] + [XmlElement("indexer", typeof(IndexerDeclElement))] + [XmlElement("function", typeof(FunctionDescElement))] + [XmlElement("delegate", typeof(DelegateDescElement))] + [XmlElement("interface", typeof(InterfaceElement))] + [XmlElement("vtbl", typeof(VtblElement))] + public List Decls { get; } = []; +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/TypeInitializationExpressionElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/TypeInitializationExpressionElement.cs new file mode 100644 index 00000000..0c31efb8 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/TypeInitializationExpressionElement.cs @@ -0,0 +1,15 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class TypeInitializationExpressionElement : FunctionBodyElement +{ + [XmlAttribute("field")] + public required string Field { get; init; } + + [XmlAttribute("hint")] + public string? Hint { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/TypeWithNativeTypeNameElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/TypeWithNativeTypeNameElement.cs new file mode 100644 index 00000000..a37b634c --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/TypeWithNativeTypeNameElement.cs @@ -0,0 +1,37 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.ComponentModel; +using System.Diagnostics; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +[DebuggerDisplay($"{{{nameof(TypeName)},nq}}")] +public class TypeWithNativeTypeNameElement +{ + [XmlAttribute("primitive")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? PrimitiveValue { get; init; } + + [XmlIgnore] + public bool IsPrimitive + { + get + { + _ = bool.TryParse(PrimitiveValue, out var primitive); + return primitive; + } + + init + { + PrimitiveValue = value.ToString(); + } + } + + [XmlAttribute("native")] + public string? NativeTypeName { get; init; } + + [XmlText] + public required string TypeName { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/ValueDescValueElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/ValueDescValueElement.cs new file mode 100644 index 00000000..1b9164d1 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/ValueDescValueElement.cs @@ -0,0 +1,21 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class ValueDescValueElement +{ + [XmlElement("unchecked")] + public ValueDescValueElement? Unchecked { get; init; } + + [XmlElement("cast")] + public string? CastTargetTypeName { get; init; } + + [XmlElement("value")] + public ValueDescValueElement? NestedValue { get; init; } + + [XmlElement("code")] + public CSharpCodeElement? Code { get; init; } +} diff --git a/sources/ClangSharp.PInvokeGenerator/XML/VtblElement.cs b/sources/ClangSharp.PInvokeGenerator/XML/VtblElement.cs new file mode 100644 index 00000000..a25282f0 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/XML/VtblElement.cs @@ -0,0 +1,13 @@ +// Copyright © Tanner Gooding and Contributors. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace ClangSharp.XML; + +[XmlType] +public class VtblElement +{ + [XmlElement("field")] + public List Fields { get; } = []; +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/PInvokeGeneratorTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/PInvokeGeneratorTest.cs index 34fb1b26..a1a09125 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/PInvokeGeneratorTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/PInvokeGeneratorTest.cs @@ -8,10 +8,16 @@ using System.Xml.Linq; using ClangSharp.Interop; using static ClangSharp.Interop.CXTranslationUnit_Flags; +using System.Xml.Serialization; +using ClangSharp.XML; +using System.Xml; +using System.Text; +using System; +using System.Text.RegularExpressions; namespace ClangSharp.UnitTests; -public abstract class PInvokeGeneratorTest +public abstract partial class PInvokeGeneratorTest { protected const string DefaultInputFileName = "ClangUnsavedFile.h"; protected const string DefaultLibraryPath = "ClangSharpPInvokeGenerator"; @@ -145,5 +151,85 @@ private static async Task ValidateGeneratedBindingsAsync(string inputContents, s using var streamReader = new StreamReader(outputStream); var actualOutputContents = await streamReader.ReadToEndAsync().ConfigureAwait(false); Assert.That(actualOutputContents, Is.EqualTo(expectedOutputContents)); + if (outputMode == PInvokeGeneratorOutputMode.Xml && !string.IsNullOrEmpty(actualOutputContents)) + { + ValidateRoundTrippedSerializedXmlBindings(actualOutputContents); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Trimming", + "IL2026: Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = nameof(System.Xml.Serialization))] + private static void ValidateRoundTrippedSerializedXmlBindings(string generatedXmlOutput) + { + var generatedNsXml = GetNamespaceXml(generatedXmlOutput, true); + + var bindingsSerializer = new XmlSerializer(typeof(BindingsElement)); + XmlReaderSettings xmlReaderSettings = new() { + DtdProcessing = DtdProcessing.Ignore, + IgnoreWhitespace = false, + }; + using var generatedXmlTextReader = new StringReader(generatedXmlOutput); + using var generatedXmlReader = XmlReader.Create(generatedXmlTextReader, xmlReaderSettings); + var deserializedInstance = bindingsSerializer.Deserialize(generatedXmlReader); + Assert.That(deserializedInstance, Is.Not.Null); + Assert.That(deserializedInstance, Is.InstanceOf()); + + var serialiedXmlOutputBuilder = new StringBuilder(); + XmlWriterSettings xmlWriterSettings = new() { + OmitXmlDeclaration = true, + Indent = true, + IndentChars = new(' ', 2), + NewLineChars = "\n", + }; + using var serializedXmlWriter = XmlWriter.Create(serialiedXmlOutputBuilder, xmlWriterSettings); + bindingsSerializer.Serialize(serializedXmlWriter, deserializedInstance); + serializedXmlWriter.Close(); + var serializedNsXml = GetNamespaceXml(serialiedXmlOutputBuilder.ToString()); + + Assert.That(serializedNsXml, Is.EqualTo(generatedNsXml)); + + static string GetNamespaceXml(string xmlText, bool normalize = false) + { + const string NamespaceBegin = " + { + var tagName = emptyMatch.Groups[1]; + var attrs = emptyMatch.Groups[2]; + return $"<{tagName}{attrs} />"; + }); + return xmlNsText; + } } + + [GeneratedRegex("""<(\w+)(\s*[^>]*)>""")] + private static partial Regex GetEmptyXmlTagRegex(); }