mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
9.4 KiB
266 lines
9.4 KiB
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team |
|
// |
|
// 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.Linq; |
|
|
|
using ICSharpCode.Decompiler.CSharp.Syntax; |
|
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; |
|
|
|
using NUnit.Framework; |
|
|
|
namespace ICSharpCode.Decompiler.Tests.PatternMatching |
|
{ |
|
/// <summary> |
|
/// Substrate-agnostic tests of the pattern-matching engine: single-node matching with captures, |
|
/// the special pattern nodes (AnyNode, Choice, Repeat, OptionalNode, the back-references), and the |
|
/// collection backtracking core. They are written against the public matching API so they hold |
|
/// across the engine's storage model. |
|
/// </summary> |
|
[TestFixture] |
|
public class PatternMatchingTests |
|
{ |
|
static ExpressionStatement Stmt(string identifier) |
|
{ |
|
return new ExpressionStatement(new IdentifierExpression(identifier)); |
|
} |
|
|
|
static BlockStatement Block(params Statement[] statements) |
|
{ |
|
var block = new BlockStatement(); |
|
foreach (var s in statements) |
|
block.Add(s); |
|
return block; |
|
} |
|
|
|
#region single node |
|
[Test] |
|
public void IdenticalExpressions_Match() |
|
{ |
|
Assert.That(new IdentifierExpression("x").IsMatch(new IdentifierExpression("x")), Is.True); |
|
} |
|
|
|
[Test] |
|
public void DifferentExpressions_DoNotMatch() |
|
{ |
|
Assert.That(new IdentifierExpression("x").IsMatch(new IdentifierExpression("y")), Is.False); |
|
} |
|
|
|
[Test] |
|
public void AnyNode_MatchesNonNull_RejectsNull() |
|
{ |
|
Assert.That(new AnyNode().IsMatch(new IdentifierExpression("x")), Is.True); |
|
Assert.That(new AnyNode().IsMatch(null), Is.False); |
|
} |
|
|
|
[Test] |
|
public void AnyNodeOrNull_MatchesNull() |
|
{ |
|
Assert.That(new AnyNodeOrNull().IsMatch(null), Is.True); |
|
Assert.That(new AnyNodeOrNull().IsMatch(new IdentifierExpression("x")), Is.True); |
|
} |
|
|
|
[Test] |
|
public void NamedNode_CapturesTheMatchedNode() |
|
{ |
|
var pattern = new NamedNode("id", new AnyNode()); |
|
var target = new IdentifierExpression("foo"); |
|
var m = pattern.Match(target); |
|
Assert.That(m.Success, Is.True); |
|
Assert.That(m.Get("id").Single(), Is.SameAs(target)); |
|
} |
|
#endregion |
|
|
|
#region Choice |
|
[Test] |
|
public void Choice_MatchesAnyAlternative() |
|
{ |
|
var choice = new Choice { new IdentifierExpression("a"), new IdentifierExpression("b") }; |
|
Assert.That(choice.IsMatch(new IdentifierExpression("a")), Is.True); |
|
Assert.That(choice.IsMatch(new IdentifierExpression("b")), Is.True); |
|
Assert.That(choice.IsMatch(new IdentifierExpression("c")), Is.False); |
|
} |
|
|
|
[Test] |
|
public void Choice_DoesNotLeakCapturesFromFailedAlternative() |
|
{ |
|
// The first alternative captures "g" then fails on the operator; the checkpoint must be |
|
// restored so the surviving match carries only the second alternative's capture. |
|
var choice = new Choice { |
|
new BinaryOperatorExpression { |
|
Left = new NamedNode("g", new IdentifierExpression("a")), |
|
Operator = BinaryOperatorType.Add, |
|
Right = new AnyNode() |
|
}, |
|
new NamedNode("g", new IdentifierExpression("z")) |
|
}; |
|
var m = choice.Match(new IdentifierExpression("z")); |
|
Assert.That(m.Success, Is.True); |
|
Assert.That(m.Get("g").Count(), Is.EqualTo(1)); |
|
Assert.That(((IdentifierExpression)m.Get("g").Single()).Identifier, Is.EqualTo("z")); |
|
} |
|
#endregion |
|
|
|
#region back-references |
|
[Test] |
|
public void Backreference_MatchesEqualNode() |
|
{ |
|
var pattern = new BinaryOperatorExpression { |
|
Left = new NamedNode("x", new AnyNode()), |
|
Operator = BinaryOperatorType.Any, |
|
Right = new Backreference("x") |
|
}; |
|
Assert.That(pattern.IsMatch(new BinaryOperatorExpression( |
|
new IdentifierExpression("a"), BinaryOperatorType.Add, new IdentifierExpression("a"))), Is.True); |
|
Assert.That(pattern.IsMatch(new BinaryOperatorExpression( |
|
new IdentifierExpression("a"), BinaryOperatorType.Add, new IdentifierExpression("b"))), Is.False); |
|
} |
|
|
|
[Test] |
|
public void IdentifierExpressionBackreference_MatchesSameName() |
|
{ |
|
var pattern = new BinaryOperatorExpression { |
|
Left = new NamedNode("v", new IdentifierExpression(Pattern.AnyString)), |
|
Operator = BinaryOperatorType.Any, |
|
Right = new IdentifierExpressionBackreference("v") |
|
}; |
|
Assert.That(pattern.IsMatch(new BinaryOperatorExpression( |
|
new IdentifierExpression("i"), BinaryOperatorType.Add, new IdentifierExpression("i"))), Is.True); |
|
Assert.That(pattern.IsMatch(new BinaryOperatorExpression( |
|
new IdentifierExpression("i"), BinaryOperatorType.Add, new IdentifierExpression("j"))), Is.False); |
|
} |
|
|
|
[Test] |
|
public void IdentifierExpressionBackreference_RejectsTypeArguments() |
|
{ |
|
var pattern = new BinaryOperatorExpression { |
|
Left = new NamedNode("v", new IdentifierExpression(Pattern.AnyString)), |
|
Operator = BinaryOperatorType.Any, |
|
Right = new IdentifierExpressionBackreference("v") |
|
}; |
|
var rightWithTypeArgs = new IdentifierExpression("i"); |
|
rightWithTypeArgs.TypeArguments.Add(new SimpleType("T")); |
|
Assert.That(pattern.IsMatch(new BinaryOperatorExpression( |
|
new IdentifierExpression("i"), BinaryOperatorType.Add, rightWithTypeArgs)), Is.False); |
|
} |
|
#endregion |
|
|
|
#region collections |
|
[Test] |
|
public void EmptyCollections_Match() |
|
{ |
|
Assert.That(Block().IsMatch(Block()), Is.True); |
|
} |
|
|
|
[Test] |
|
public void Repeat_GreedilyMatchesAll() |
|
{ |
|
var pattern = new BlockStatement { Statements = { new Repeat(new AnyNode("s")) } }; |
|
var m = pattern.Match(Block(Stmt("a"), Stmt("b"), Stmt("c"))); |
|
Assert.That(m.Success, Is.True); |
|
Assert.That(m.Get("s").Count(), Is.EqualTo(3)); |
|
} |
|
|
|
[Test] |
|
public void Repeat_MatchesEmptyWhenMinZero() |
|
{ |
|
var pattern = new BlockStatement { Statements = { new Repeat(new AnyNode()) } }; |
|
Assert.That(pattern.IsMatch(Block()), Is.True); |
|
} |
|
|
|
[Test] |
|
public void Repeat_MinCountNotSatisfied_DoesNotMatch() |
|
{ |
|
var pattern = new BlockStatement { Statements = { new Repeat(new AnyNode()) { MinCount = 2 } } }; |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"))), Is.False); |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"), Stmt("b"))), Is.True); |
|
} |
|
|
|
[Test] |
|
public void Repeat_MaxCountLeavesTrailingUnmatched_DoesNotMatch() |
|
{ |
|
var pattern = new BlockStatement { Statements = { new Repeat(new AnyNode()) { MaxCount = 1 } } }; |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"))), Is.True); |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"), Stmt("b"))), Is.False); |
|
} |
|
|
|
[Test] |
|
public void Repeat_BacktracksToSatisfyTrailingFixedNode() |
|
{ |
|
var pattern = new BlockStatement { |
|
Statements = { |
|
new Repeat(new AnyNode("body")), |
|
new NamedNode("last", Stmt("end")) |
|
} |
|
}; |
|
var m = pattern.Match(Block(Stmt("a"), Stmt("b"), Stmt("end"))); |
|
Assert.That(m.Success, Is.True); |
|
Assert.That(m.Get("body").Count(), Is.EqualTo(2)); |
|
Assert.That(((IdentifierExpression)((ExpressionStatement)m.Get("last").Single()).Expression).Identifier, |
|
Is.EqualTo("end")); |
|
} |
|
|
|
[Test] |
|
public void Optional_MatchesWhetherPresentOrAbsent() |
|
{ |
|
var pattern = new BlockStatement { |
|
Statements = { |
|
new OptionalNode(Stmt("opt")), |
|
new NamedNode("tail", Stmt("tail")) |
|
} |
|
}; |
|
Assert.That(pattern.IsMatch(Block(Stmt("opt"), Stmt("tail"))), Is.True); |
|
Assert.That(pattern.IsMatch(Block(Stmt("tail"))), Is.True); |
|
Assert.That(pattern.IsMatch(Block(Stmt("other"), Stmt("tail"))), Is.False); |
|
} |
|
|
|
[Test] |
|
public void FixedLengthCollection_LengthMismatch_DoesNotMatch() |
|
{ |
|
var pattern = Block(Stmt("a"), Stmt("b")); |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"), Stmt("b"))), Is.True); |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"), Stmt("b"), Stmt("c"))), Is.False); |
|
Assert.That(pattern.IsMatch(Block(Stmt("a"))), Is.False); |
|
} |
|
#endregion |
|
|
|
#region nesting |
|
[Test] |
|
public void NestedPattern_MatchesAndCaptures() |
|
{ |
|
var pattern = new BinaryOperatorExpression { |
|
Left = new BinaryOperatorExpression { |
|
Left = new NamedNode("a", new AnyNode()), |
|
Operator = BinaryOperatorType.Multiply, |
|
Right = new NamedNode("b", new AnyNode()) |
|
}, |
|
Operator = BinaryOperatorType.Add, |
|
Right = new NamedNode("c", new AnyNode()) |
|
}; |
|
var target = new BinaryOperatorExpression( |
|
new BinaryOperatorExpression(new IdentifierExpression("x"), BinaryOperatorType.Multiply, new IdentifierExpression("y")), |
|
BinaryOperatorType.Add, |
|
new IdentifierExpression("z")); |
|
var m = pattern.Match(target); |
|
Assert.That(m.Success, Is.True); |
|
Assert.That(((IdentifierExpression)m.Get("a").Single()).Identifier, Is.EqualTo("x")); |
|
Assert.That(((IdentifierExpression)m.Get("b").Single()).Identifier, Is.EqualTo("y")); |
|
Assert.That(((IdentifierExpression)m.Get("c").Single()).Identifier, Is.EqualTo("z")); |
|
} |
|
#endregion |
|
} |
|
}
|
|
|