-
Notifications
You must be signed in to change notification settings - Fork 198
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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;} | ||
} |
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 { | ||
/// <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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we rename it to |
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouold it help to have a DebuggerDisplayAttribute that surfaces this value? |
||
} |
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); | ||
} | ||
} |
There was a problem hiding this comment.
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.