Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Swift] Add first piece of rule reduction #2692

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/Swift.Bindings/src/Demangler/IReduction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace BindingsGeneration.Demangling;

/// <summary>
/// Represents the result of reducing a Node or tree of nodes
/// </summary>
public interface IReduction {
string Symbol { get; init; }
}

/// <summary>
/// Represents an error in an attempted reduction
/// </summary>
public class ReductionError : IReduction {
public required string Symbol { get; init; }
/// <summary>
/// Returns an error message describing the error
/// </summary>
public required string Message { get; init; }
}

/// <summary>
/// Represents a reduction that reduces to a single type
/// </summary>
public class TypeSpecReduction : IReduction {
public required string Symbol { get; init; }
/// <summary>
/// Returns a TypeSpec for the type that this node represents
/// </summary>
public required TypeSpec TypeSpec { get; init; }
}

/// <summary>
/// Represents a reduction that reduces to a Swift function
/// </summary>
public class FunctionReduction : IReduction {
public required string Symbol { get; init; }
/// <summary>
/// Returns a function that this type represents
/// </summary>
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;}
}
137 changes: 137 additions & 0 deletions src/Swift.Bindings/src/Demangler/MatchRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;

namespace BindingsGeneration.Demangling;

/// <summary>
/// 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.
/// </summary>
[DebuggerDisplay("{ToString()}")]
internal class MatchRule {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would be beneficial to ask someone from the Roslyn team to review this (and maybe the previously introduced Parser)?

I think this is an area commonly found in the frontend of compilers and they may have some interesting insights for us if we plan to grow this further.

/// <summary>
/// The name of this rule - useful for debugging
/// </summary>
public required string Name { get; init; }

/// <summary>
/// A list of node kinds to match.
/// </summary>
public List<NodeKind> NodeKindList { get; set; } = new List<NodeKind>();

/// <summary>
/// A convenience accessor to match on a single NodeKind
/// </summary>
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<NodeKind> { value };
}
}

/// <summary>
/// If and how to match the content of the Node
/// </summary>
public MatchNodeContentType MatchContentType { get; init; } = MatchNodeContentType.None;

/// <summary>
/// Rules to apply to children node.
/// </summary>
public List<MatchRule> ChildRules { get; init; } = new List<MatchRule>();

/// <summary>
/// Whether or not the total number of children should match
/// </summary>
public bool MatchChildCount { get; init; } = false;

/// <summary>
/// A reducer to apply if the node matches
/// </summary>
public required Func<Node, string?, IReduction> Reducer { get; init; }

/// <summary>
/// Returns true if and only if the given node matches this rule
/// </summary>
/// <param name="n">a node to match on</param>
/// <returns></returns>
public bool Matches(Node n)
{
return NodeKindMatches(n) && ContentTypeMatches(n) && ChildrenMatches(n);
}

/// <summary>
/// Returns true if and only if the NodeKind matches
/// </summary>
/// <param name="n">a node to match on</param>
/// <returns></returns>
bool NodeKindMatches(Node n)
{
return NodeKindList.Contains(n.Kind);
}

/// <summary>
/// Returns true if and only if the content of the given node matches
/// </summary>
/// <param name="n">a node to match on</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
bool ContentTypeMatches(Node n)
{
// Only care about the content type not its value
Copy link
Member

@matouskozak matouskozak Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we rename it to ContentTypeMatches if we only care about the content type?

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.");
}
}

/// <summary>
/// Returns true if and only if the children rules matches the given node's children
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
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;
}

/// <summary>
/// Creates a simple string representation of this rule
/// </summary>
/// <returns>a string representation of the rule</returns>
public override string ToString() => Name;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouold it help to have a DebuggerDisplayAttribute that surfaces this value?

}
38 changes: 38 additions & 0 deletions src/Swift.Bindings/src/Demangler/RuleRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace BindingsGeneration.Demangling;

/// <summary>
/// RuleRunner contains a a collection of rules that get run to reduce nodes
/// </summary>
internal class RuleRunner {
string mangledName;
List<MatchRule> rules = new List<MatchRule>();

/// <summary>
/// Constructs a new rules runner initialized with the give rule set
/// </summary>
/// <param name="rules">Rules to test against a node</param>
public RuleRunner(IEnumerable<MatchRule> rules, string mangledName)
{
this.mangledName = mangledName;
this.rules.AddRange(rules);
}

/// <summary>
/// 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
/// </summary>
/// <param name="node">A node to attempt to match</param>
/// <param name="name">A name used for the reduction</param>
/// <returns></returns>
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);
}
}
39 changes: 27 additions & 12 deletions src/Swift.Bindings/src/Demangler/Swift5Demangler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading