diff --git a/src/Swift.Bindings/src/Demangler/IReduction.cs b/src/Swift.Bindings/src/Demangler/IReduction.cs
new file mode 100644
index 00000000000..e5c30b52729
--- /dev/null
+++ b/src/Swift.Bindings/src/Demangler/IReduction.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace BindingsGeneration.Demangling;
+
+///
+/// Represents the result of reducing a Node or tree of nodes
+///
+public interface IReduction {
+ string Symbol { get; init; }
+}
+
+///
+/// Represents an error in an attempted reduction
+///
+public class ReductionError : IReduction {
+ public required string Symbol { get; init; }
+ ///
+ /// Returns an error message describing the error
+ ///
+ public required string Message { get; init; }
+}
+
+///
+/// Represents a reduction that reduces to a single type
+///
+public class TypeSpecReduction : IReduction {
+ public required string Symbol { get; init; }
+ ///
+ /// Returns a TypeSpec for the type that this node represents
+ ///
+ public required TypeSpec TypeSpec { get; init; }
+}
+
+///
+/// Represents a reduction that reduces to a Swift function
+///
+public class FunctionReduction : IReduction {
+ public required string Symbol { get; init; }
+ ///
+ /// Returns a function that this type represents
+ ///
+ public required SwiftFunction Function { get; init; }
+}
+
+public class ProtocolWitnessTableReduction : IReduction {
+ public required string Symbol { get; init; }
+ public required NamedTypeSpec ImplementingType { get; init; }
+ public required NamedTypeSpec ProtocolType { get; init;}
+}
\ No newline at end of file
diff --git a/src/Swift.Bindings/src/Demangler/MatchRule.cs b/src/Swift.Bindings/src/Demangler/MatchRule.cs
new file mode 100644
index 00000000000..ff67df170df
--- /dev/null
+++ b/src/Swift.Bindings/src/Demangler/MatchRule.cs
@@ -0,0 +1,137 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+
+namespace BindingsGeneration.Demangling;
+
+///
+/// MatchRule represents a tree reducing rule for a demangled Swift symbol.
+/// Given a tree of nodes, a match rule contains conditions for the match,
+/// which include:
+/// - If and how the node content should be matched
+/// - If the number of children should match
+/// - Rules to run on the children
+/// - One or more NodeKinds to match
+/// If a match occurs, a reducer function can be run on the node.
+///
+[DebuggerDisplay("{ToString()}")]
+internal class MatchRule {
+ ///
+ /// The name of this rule - useful for debugging
+ ///
+ public required string Name { get; init; }
+
+ ///
+ /// A list of node kinds to match.
+ ///
+ public List NodeKindList { get; set; } = new List();
+
+ ///
+ /// A convenience accessor to match on a single NodeKind
+ ///
+ public NodeKind NodeKind {
+ get {
+ if (NodeKindList.Count != 1) {
+ throw new InvalidOperationException($"NodeKind is invalid when NodeKindList has {NodeKindList.Count} entries.");
+ }
+ return NodeKindList[0];
+ }
+ set {
+ NodeKindList = new List { value };
+ }
+ }
+
+ ///
+ /// If and how to match the content of the Node
+ ///
+ public MatchNodeContentType MatchContentType { get; init; } = MatchNodeContentType.None;
+
+ ///
+ /// Rules to apply to children node.
+ ///
+ public List ChildRules { get; init; } = new List();
+
+ ///
+ /// Whether or not the total number of children should match
+ ///
+ public bool MatchChildCount { get; init; } = false;
+
+ ///
+ /// A reducer to apply if the node matches
+ ///
+ public required Func Reducer { get; init; }
+
+ ///
+ /// Returns true if and only if the given node matches this rule
+ ///
+ /// a node to match on
+ ///
+ public bool Matches(Node n)
+ {
+ return NodeKindMatches(n) && ContentTypeMatches(n) && ChildrenMatches(n);
+ }
+
+ ///
+ /// Returns true if and only if the NodeKind matches
+ ///
+ /// a node to match on
+ ///
+ bool NodeKindMatches(Node n)
+ {
+ return NodeKindList.Contains(n.Kind);
+ }
+
+ ///
+ /// Returns true if and only if the content of the given node matches
+ ///
+ /// a node to match on
+ ///
+ ///
+ bool ContentTypeMatches(Node n)
+ {
+ // Only care about the content type not its value
+ switch (MatchContentType)
+ {
+ case MatchNodeContentType.AlwaysMatch:
+ return true;
+ case MatchNodeContentType.Index:
+ return n.HasIndex;
+ case MatchNodeContentType.Text:
+ return n.HasText;
+ case MatchNodeContentType.None:
+ return !n.HasIndex && !n.HasText;
+ default:
+ throw new InvalidOperationException ($"Unknown match instruction {MatchContentType} in match rule.");
+ }
+ }
+
+ ///
+ /// Returns true if and only if the children rules matches the given node's children
+ ///
+ ///
+ ///
+ bool ChildrenMatches (Node n)
+ {
+ // if the rule says the child count matters, apply
+ if (MatchChildCount && n.Children.Count != ChildRules.Count)
+ return false;
+
+ // match up to the minimum of each list
+ // if MatchChileCount is true, min is the size of both lists
+ int minimumChildCount = Math.Min (n.Children.Count, ChildRules.Count);
+ for (var i = 0; i < minimumChildCount; i++) {
+ var childRule = ChildRules [i];
+ // recurse
+ if (!childRule.Matches (n.Children [i]))
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Creates a simple string representation of this rule
+ ///
+ /// a string representation of the rule
+ public override string ToString() => Name;
+}
diff --git a/src/Swift.Bindings/src/Demangler/RuleRunner.cs b/src/Swift.Bindings/src/Demangler/RuleRunner.cs
new file mode 100644
index 00000000000..49b05123404
--- /dev/null
+++ b/src/Swift.Bindings/src/Demangler/RuleRunner.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace BindingsGeneration.Demangling;
+
+///
+/// RuleRunner contains a a collection of rules that get run to reduce nodes
+///
+internal class RuleRunner {
+ string mangledName;
+ List rules = new List();
+
+ ///
+ /// Constructs a new rules runner initialized with the give rule set
+ ///
+ /// Rules to test against a node
+ public RuleRunner(IEnumerable rules, string mangledName)
+ {
+ this.mangledName = mangledName;
+ this.rules.AddRange(rules);
+ }
+
+ ///
+ /// Run a set of rules on the given node and return a reduction on that node. If there was no match
+ /// or there was an error, this will return a IReduction of type ReductionError
+ ///
+ /// A node to attempt to match
+ /// A name used for the reduction
+ ///
+ public IReduction RunRules(Node node, string? name)
+ {
+ var rule = rules.FirstOrDefault (r => r.Matches(node));
+
+ if (rule is null)
+ return new ReductionError() { Symbol = mangledName, Message = $"No rule for node {node.Kind}" };
+ return rule.Reducer(node, name);
+ }
+}
diff --git a/src/Swift.Bindings/src/Demangler/Swift5Demangler.cs b/src/Swift.Bindings/src/Demangler/Swift5Demangler.cs
index 9460764cf4d..a48e5af5c50 100644
--- a/src/Swift.Bindings/src/Demangler/Swift5Demangler.cs
+++ b/src/Swift.Bindings/src/Demangler/Swift5Demangler.cs
@@ -41,20 +41,35 @@ public Swift5Demangler (string mangledName, ulong offset = 0)
slice.Advance (GetManglingPrefixLength (originalIdentifier));
}
- public void Run ()
+ public IReduction Run ()
{
Node topLevelNode = DemangleType (null);
- // if (topLevelNode != null && topLevelNode.IsAttribute () && nodeStack.Count >= 1) {
- // var attribute = topLevelNode.ExtractAttribute ();
- // var tld = Run ();
- // if (tld != null && tld is TLFunction tlf) {
- // tlf.Signature.Attributes.Add (attribute);
- // }
- // return tld;
- // } else {
- // Swift5NodeToTLDefinition converter = new Swift5NodeToTLDefinition (originalIdentifier, offset);
- // return converter.Convert (topLevelNode);
- // }
+ if (topLevelNode is not null && topLevelNode.IsAttribute() && nodeStack.Count >= 1) {
+ var attribute = ExtractAttribute (topLevelNode);
+ var nextReduction = Run ();
+ if (nextReduction is not null && nextReduction is TypeSpecReduction ts) {
+ ts.TypeSpec.Attributes.Add (attribute);
+ }
+ return nextReduction;
+ } else if (topLevelNode is not null) {
+ var reducer = new Swift5Reducer (originalIdentifier);
+ return reducer.Convert (topLevelNode);
+ } else {
+ return new ReductionError () {Symbol = originalIdentifier, Message = $"Unable to demangle {originalIdentifier}" };
+ }
+ }
+
+ static TypeSpecAttribute ExtractAttribute (Node node)
+ {
+ switch (node.Kind) {
+ case NodeKind.ObjCAttribute: return new TypeSpecAttribute ("ObjC");
+ case NodeKind.DynamicAttribute: return new TypeSpecAttribute ("Dynamic");
+ case NodeKind.NonObjCAttribute: return new TypeSpecAttribute ("NonObjC");
+ case NodeKind.ImplFunctionAttribute: return new TypeSpecAttribute ("ImplFunction");
+ case NodeKind.DirectMethodReferenceAttribute: return new TypeSpecAttribute ("DirectMethodReference");
+ default:
+ throw new NotSupportedException ($"{node.Kind} is not a supported attribute.");
+ }
}
bool NextIf (string str)
diff --git a/src/Swift.Bindings/src/Demangler/Swift5Reducer.cs b/src/Swift.Bindings/src/Demangler/Swift5Reducer.cs
new file mode 100644
index 00000000000..93458db8102
--- /dev/null
+++ b/src/Swift.Bindings/src/Demangler/Swift5Reducer.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Net.Sockets;
+
+namespace BindingsGeneration.Demangling;
+
+///
+/// Swift5Reducer takes a tree of Node and attempts to reduce it completely to an easier to
+/// to manipulate data structure. It does this by matching patterns in the tree and on match
+/// applying a reduction function on the matched Node.
+///
+internal class Swift5Reducer {
+ string mangledName;
+ RuleRunner ruleRunner;
+ List rules;
+
+ ///
+ /// Construct Swift5Reducer to operate on a tree of nodes build from the given mangled name
+ ///
+ public Swift5Reducer (string mangledName)
+ {
+ this.mangledName = mangledName;
+ rules = BuildMatchRules ();
+ ruleRunner = new RuleRunner (rules, mangledName);
+ }
+
+ ///
+ /// Build a set of match rules for Node reduction
+ ///
+ List BuildMatchRules () =>
+ new List() {
+ new MatchRule() {
+ Name = "Global", NodeKind = NodeKind.Global, Reducer = ConvertFirstChild
+ },
+ new MatchRule() {
+ Name = "ProtocolWitnessTable", NodeKind = NodeKind.ProtocolWitnessTable, Reducer = ConvertProtocolWitnessTable
+ },
+ new MatchRule() {
+ Name = "Type", NodeKind = NodeKind.Type, Reducer = ConvertFirstChild
+ },
+ new MatchRule() {
+ Name = "Nominal", NodeKindList = new List() { NodeKind.Class, NodeKind.Structure, NodeKind.Protocol, NodeKind.Enum },
+ Reducer = ConvertNominal
+ }
+ };
+
+ ///
+ /// Convert a Node into an IReduction. On failure, the IReduction will be type ReductionError
+ ///
+ public IReduction Convert (Node node)
+ {
+ return ruleRunner.RunRules (node, null);
+ }
+
+ ///
+ /// Given a ProtocolWitnessTable node, convert to a ProtocolWitnessTable reduction
+ ///
+ IReduction ConvertProtocolWitnessTable (Node node, string? name)
+ {
+ // What to expect here:
+ // ProtocolConformance
+ // Type
+ // Type
+ var child = node.Children [0];
+ if (child.Kind != NodeKind.ProtocolConformance) {
+ return ReductionError (ExpectedButGot ("ProtocolConformance", node.Kind.ToString ()));
+ }
+ var grandchild = Convert (child.Children [0]);
+ if (grandchild is ReductionError) return grandchild;
+ var implementingType = grandchild as TypeSpecReduction;
+ if (implementingType is null) {
+ return ReductionError (ExpectedButGot ("Nominal type implementing protocol", grandchild.GetType().Name));
+ }
+
+ grandchild = Convert (child.Children [1]);
+ if (grandchild is ReductionError) return grandchild;
+ var protocolType = grandchild as TypeSpecReduction;
+ if (protocolType is null) {
+ return ReductionError (ExpectedButGot ("Nominal type protocol", grandchild.GetType().Name));
+ }
+
+ var impNamed = (NamedTypeSpec)implementingType.TypeSpec;
+ var protoNamed = (NamedTypeSpec)protocolType.TypeSpec;
+
+ return new ProtocolWitnessTableReduction() { Symbol = mangledName, ImplementingType = impNamed, ProtocolType = protoNamed };
+ }
+
+ ///
+ /// Convert a nominal node into TypeSpecReduction
+ ///
+ IReduction ConvertNominal (Node node, string? name)
+ {
+ // What to expect here:
+ // Class/Structure/Protocol/Enum
+ // Module Name
+ // Identifier Name
+ var kind = node.Kind;
+ if (kind == NodeKind.Class || kind == NodeKind.Structure || kind == NodeKind.Enum || kind == NodeKind.Protocol) {
+ var moduleName = node.Children [0].Text;
+ if (node.Children[1].Kind != NodeKind.Identifier)
+ return ReductionError(ExpectedButGot("Identifier", node.Children[1].Kind.ToString()));
+ var typeName = node.Children [1].Text;
+ return new TypeSpecReduction() { Symbol = mangledName, TypeSpec = new NamedTypeSpec ($"{moduleName}.{typeName}")};
+ }
+ return ReductionError(ExpectedButGot("Class/Struct/Enum/Protocol", kind.ToString()));
+ }
+
+ ///
+ /// Recurce on Convert with the first child
+ ///
+ IReduction ConvertFirstChild (Node node, string? name)
+ {
+ return Convert (node.Children [0]);
+ }
+
+ ///
+ /// Return a string in the form mangledName: expected xxxx but got yyyy
+ ///
+ string ExpectedButGot (string expected, string butGot)
+ {
+ return $"Demangling {mangledName}: expected {expected} but got {butGot}";
+ }
+
+ ///
+ /// Convenience factory for reduction errors
+ ///
+ ReductionError ReductionError (string message)
+ {
+ return new ReductionError() { Symbol = mangledName, Message = message };
+ }
+}
\ No newline at end of file
diff --git a/src/Swift.Bindings/src/Model/Provenance.cs b/src/Swift.Bindings/src/Model/Provenance.cs
new file mode 100644
index 00000000000..d3ff9616af7
--- /dev/null
+++ b/src/Swift.Bindings/src/Model/Provenance.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace BindingsGeneration;
+
+///
+/// Represents the origin of a particular function
+///
+public class Provenance {
+ private Provenance() { }
+
+ ///
+ /// If the provenance is in instance, return the type
+ ///
+ public NamedTypeSpec? InstanceType { get; private set; }
+
+ ///
+ /// If the provenance is an extension, return type it is an extension on
+ ///
+ public NamedTypeSpec? ExtensionOn { get; private set; }
+
+ ///
+ /// If the provenance is top level, return the module
+ ///
+ public string? Module {get; private set; }
+
+ ///
+ /// Construct a new instance provenance from the given NamedTypeSpec
+ ///
+ ///
+ ///
+ public static Provenance Instance (NamedTypeSpec ns) {
+ return new Provenance () { InstanceType = ns };
+ }
+
+ ///
+ /// Construct a new extension provenance from the given NamedTypeSpec
+ ///
+ ///
+ ///
+ public static Provenance Extension (NamedTypeSpec ns) {
+ return new Provenance () { ExtensionOn = ns };
+ }
+
+ ///
+ /// Construct a new top-level provenance from the given module name
+ ///
+ ///
+ ///
+ public static Provenance TopLevel (string module) {
+ return new Provenance () { Module = module };;
+ }
+
+ ///
+ /// Returns true if and only if the provenance is an instance
+ ///
+ [MemberNotNullWhen(true, nameof(InstanceType))]
+ public bool IsInstance => InstanceType is not null;
+
+ ///
+ /// Returns true if and only if the provenance is an extension
+ ///
+ [MemberNotNullWhen(true, nameof(ExtensionOn))]
+ public bool IsExtension => ExtensionOn is not null;
+
+ ///
+ /// Returns true if and only if the provenance is top level
+ ///
+ [MemberNotNullWhen(true, nameof(Module))]
+ public bool IsTopLevel => Module is not null;
+
+ ///
+ /// Returns true if o is a Provenance and equals this one
+ ///
+ ///
+ ///
+ public override bool Equals(object? o)
+ {
+ if (o is Provenance other) {
+ return (IsInstance && InstanceType.Equals (other.InstanceType)) ||
+ (IsExtension && ExtensionOn.Equals (other.ExtensionOn)) ||
+ (IsTopLevel && Module == other.Module);
+ }
+ return false;
+ }
+
+ ///
+ /// Returns a hashcode for this object
+ ///
+ ///
+ public override int GetHashCode () => ToString ().GetHashCode ();
+
+ ///
+ /// Returns a string representation of the provenance
+ ///
+ ///
+ ///
+ public override string ToString()
+ {
+ if (IsInstance) {
+ return InstanceType.ToString ();
+ } else if (IsExtension) {
+ return ExtensionOn.ToString ();
+ } else if (IsTopLevel) {
+ return Module.ToString ();
+ }
+ throw new NotImplementedException("Unknown provenance");
+ }
+}
\ No newline at end of file
diff --git a/src/Swift.Bindings/src/Model/SwiftFunction.cs b/src/Swift.Bindings/src/Model/SwiftFunction.cs
new file mode 100644
index 00000000000..c6a8895e67a
--- /dev/null
+++ b/src/Swift.Bindings/src/Model/SwiftFunction.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+
+namespace BindingsGeneration;
+
+///
+/// Represents a Swift function signature and its provenance.
+///
+[DebuggerDisplay("{ToString()}")]
+public class SwiftFunction {
+ ///
+ /// Gets the name of the function
+ ///
+ public required string Name { get; init; }
+
+ ///
+ /// Gets the provenance of the function
+ ///
+ public required Provenance Provenance { get; init; }
+
+ ///
+ /// Gets the parameter list of the function as a tuple
+ ///
+ public required TupleTypeSpec ParameterList { get; init; }
+
+ ///
+ /// Gets the return type of the function
+ ///
+ public required TypeSpec Return { get; init; }
+
+ ///
+ /// Returns true if the give object is a SwiftFunction and matches this
+ ///
+ ///
+ /// true if this equals the supplied object
+ public override bool Equals(object? o)
+ {
+ if (o is SwiftFunction other) {
+ return Name == other.Name && Provenance.Equals (other.Provenance) &&
+ ParameterList.Equals (other.ParameterList) && Return.Equals (other.Return);
+ } else {
+ return false;
+ }
+ }
+
+ ///
+ /// Returns a hashcode for the function
+ ///
+ ///
+ public override int GetHashCode() => ToString ().GetHashCode ();
+
+ ///
+ /// Returns a string representation of the function
+ ///
+ /// a string representation of the function
+ public override string ToString () => $"{Provenance}.{Name}{ParameterList} -> {Return}";
+}
\ No newline at end of file
diff --git a/src/Swift.Bindings/tests/DemanglerTests/BasicDemanglingTests.cs b/src/Swift.Bindings/tests/DemanglerTests/BasicDemanglingTests.cs
new file mode 100644
index 00000000000..3c9ba9e6b49
--- /dev/null
+++ b/src/Swift.Bindings/tests/DemanglerTests/BasicDemanglingTests.cs
@@ -0,0 +1,71 @@
+using BindingsGeneration.Demangling;
+using Xunit;
+
+namespace BindingsGeneration.Tests;
+
+public class BasicDemanglingTests : IClassFixture
+{
+ private readonly TestFixture _fixture;
+
+ public BasicDemanglingTests(TestFixture fixture)
+ {
+ _fixture = fixture;
+ }
+
+ public class TestFixture
+ {
+ static TestFixture()
+ {
+ }
+
+ private static void InitializeResources()
+ {
+ }
+ }
+
+ [Fact]
+ public void TestProtocolWitnessTable()
+ {
+ var symbol = "_$s20GenericTestFramework6ThingyCAA7StanleyAAWP";
+ var demangler = new Swift5Demangler (symbol);
+ var result = demangler.Run ();
+ var protoWitnessReduction = result as ProtocolWitnessTableReduction;
+ Assert.NotNull(protoWitnessReduction);
+ Assert.Equal("GenericTestFramework.Thingy", protoWitnessReduction.ImplementingType.Name);
+ Assert.Equal("GenericTestFramework.Stanley", protoWitnessReduction.ProtocolType.Name);
+ }
+
+ [Fact]
+ public void TestOtherProtocolWitnessTable()
+ {
+ var symbol = "_$s20GenericTestFramework6ThingyCAA8IsItRealAAWP";
+ var demangler = new Swift5Demangler (symbol);
+ var result = demangler.Run ();
+ var protoWitnessReduction = result as ProtocolWitnessTableReduction;
+ Assert.NotNull(protoWitnessReduction);
+ Assert.Equal("GenericTestFramework.Thingy", protoWitnessReduction.ImplementingType.Name);
+ Assert.Equal("GenericTestFramework.IsItReal", protoWitnessReduction.ProtocolType.Name);
+ }
+
+ [Fact]
+ public void TestFailDemangleNonsense()
+ {
+ var symbol = "_$ThisIsJustGarbage";
+ var demangler = new Swift5Demangler (symbol);
+ var result = demangler.Run ();
+ var err = result as ReductionError;
+ Assert.NotNull(err);
+ Assert.Equal("No rule for node FirstElementMarker", err.Message);
+ }
+
+ [Fact]
+ public void TestFailMetadataAccessor()
+ {
+ var symbol = "_$s20GenericTestFramework6ThingyCMa";
+ var demangler = new Swift5Demangler (symbol);
+ var result = demangler.Run ();
+ var err = result as ReductionError;
+ Assert.NotNull(err);
+ Assert.Equal("No rule for node TypeMetadataAccessFunction", err.Message);
+ }
+}
\ No newline at end of file