Skip to content

Commit 6c66320

Browse files
committed
adds RemoveSearchForward, RemoveSearchBackward, RemoveUntilForward, RemoveUntilBackward and Do to CodeMatcher + tests
1 parent f2775b4 commit 6c66320

3 files changed

Lines changed: 372 additions & 38 deletions

File tree

Harmony/Tools/CodeMatcher.cs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace HarmonyLib
99
{
1010
/// <summary>A CodeInstruction matcher</summary>
11-
///
11+
///
1212
public class CodeMatcher
1313
{
1414
private readonly ILGenerator generator;
@@ -69,7 +69,7 @@ public class CodeMatcher
6969
public ref List<ExceptionBlock> Blocks => ref codes[Pos].blocks;
7070

7171
/// <summary>Creates an empty code matcher</summary>
72-
///
72+
///
7373
public CodeMatcher()
7474
{
7575
}
@@ -255,6 +255,18 @@ public CodeMatcher ThrowIfFalse(string explanation, Func<CodeMatcher, bool> stat
255255
return this;
256256
}
257257

258+
/// <summary>Runs some code when chaining <see cref="CodeMatcher"/> at the current position</summary>
259+
/// <param name="action">The <see cref="Action{CodeMatcher}"/> to run</param>
260+
/// <returns>The same code matcher</returns>
261+
///
262+
public CodeMatcher Do(Action<CodeMatcher> action)
263+
{
264+
if (action == null)
265+
throw new ArgumentNullException(nameof(action));
266+
action(this);
267+
return this;
268+
}
269+
258270
/// <summary>Sets an instruction at current position</summary>
259271
/// <param name="instruction">The instruction to set</param>
260272
/// <returns>The same code matcher</returns>
@@ -657,13 +669,13 @@ public CodeMatcher End()
657669
}
658670

659671
/// <summary>Searches forward with a predicate and advances position</summary>
660-
/// <param name="predicate">The predicate</param>
672+
/// <param name="predicate">A function to test each instruction for a match</param>
661673
/// <returns>The same code matcher</returns>
662674
///
663675
public CodeMatcher SearchForward(Func<CodeInstruction, bool> predicate) => Search(predicate, 1);
664676

665677
/// <summary>Searches backwards with a predicate and reverses position</summary>
666-
/// <param name="predicate">The predicate</param>
678+
/// <param name="predicate">A function to test each instruction for a match</param>
667679
/// <returns>The same code matcher</returns>
668680
///
669681
public CodeMatcher SearchBackwards(Func<CodeInstruction, bool> predicate) => Search(predicate, -1);
@@ -725,6 +737,101 @@ private CodeMatcher Search(Func<CodeInstruction, bool> predicate, int direction)
725737
///
726738
public CodeMatcher PrepareMatchEndBackwards(params CodeMatch[] matches) => Match(matches, -1, true, true);
727739

740+
/// <summary>Removes instructions from the current position forward until a predicate is matched. The matched instruction is not removed</summary>
741+
/// <param name="predicate">A function to test each instruction for a match</param>
742+
/// <returns>The same code matcher</returns>
743+
public CodeMatcher RemoveSearchForward(Func<CodeInstruction, bool> predicate)
744+
{
745+
if (IsInvalid)
746+
throw new InvalidOperationException("Cannot remove instructions from an invalid position.");
747+
748+
var originalPos = Pos;
749+
var finder = Clone().SearchForward(predicate);
750+
if (finder.IsInvalid)
751+
{
752+
lastError = finder.lastError;
753+
SetOutOfBounds(1);
754+
return this;
755+
}
756+
757+
var end = finder.Pos - 1; // stop before the matching instruction
758+
if (end >= originalPos)
759+
_ = RemoveInstructionsInRange(originalPos, end);
760+
return this;
761+
}
762+
763+
/// <summary>Removes instructions from the current position backward until a predicate is matched. The matched instruction is not removed</summary>
764+
/// <param name="predicate">A function to test each instruction for a match</param>
765+
/// <returns>The same code matcher</returns>
766+
public CodeMatcher RemoveSearchBackward(Func<CodeInstruction, bool> predicate)
767+
{
768+
if (IsInvalid)
769+
throw new InvalidOperationException("Cannot remove instructions from an invalid position.");
770+
771+
var originalPos = Pos;
772+
var finder = Clone().SearchBackwards(predicate);
773+
if (finder.IsInvalid)
774+
{
775+
lastError = finder.lastError;
776+
SetOutOfBounds(-1);
777+
return this;
778+
}
779+
780+
var matchPos = finder.Pos;
781+
var start = matchPos + 1;
782+
if (originalPos >= start)
783+
_ = RemoveInstructionsInRange(start, originalPos);
784+
Pos = matchPos;
785+
return this;
786+
}
787+
788+
/// <summary>Removes instructions from the current position up to the next match (exclusive)</summary>
789+
/// <param name="matches">Some code matches</param>
790+
/// <returns>The same code matcher</returns>
791+
public CodeMatcher RemoveUntilForward(params CodeMatch[] matches)
792+
{
793+
if (IsInvalid)
794+
throw new InvalidOperationException("Cannot remove instructions from an invalid position.");
795+
796+
var originalPos = Pos;
797+
var finder = Clone().MatchStartForward(matches);
798+
if (finder.IsInvalid)
799+
{
800+
lastError = finder.lastError;
801+
SetOutOfBounds(1);
802+
return this;
803+
}
804+
805+
var end = finder.Pos - 1;
806+
if (end >= originalPos)
807+
_ = RemoveInstructionsInRange(originalPos, end);
808+
return this;
809+
}
810+
811+
/// <summary>Removes instructions backwards from the current position to the previous match (exclusive)</summary>
812+
/// <param name="matches">Some code matches</param>
813+
/// <returns>The same code matcher</returns>
814+
public CodeMatcher RemoveUntilBackward(params CodeMatch[] matches)
815+
{
816+
if (IsInvalid)
817+
throw new InvalidOperationException("Cannot remove instructions from an invalid position.");
818+
819+
var originalPos = Pos;
820+
var finder = Clone().MatchEndBackwards(matches);
821+
if (finder.IsInvalid)
822+
{
823+
lastError = finder.lastError;
824+
SetOutOfBounds(-1);
825+
return this;
826+
}
827+
828+
var start = finder.Pos;
829+
if (originalPos > start)
830+
_ = RemoveInstructionsInRange(start + 1, originalPos);
831+
Pos = start;
832+
return this;
833+
}
834+
728835
private CodeMatcher Match(CodeMatch[] matches, int direction, bool useEnd, bool prepareOnly)
729836
{
730837
lastMatchCall = delegate ()
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
1+
using System;
2+
13
namespace HarmonyTests.Tools.Assets
24
{
5+
// we keep this code pretty simple so the IL does not change
6+
// between runtim versions
7+
38
public class CodeMatcherClass
49
{
10+
public static void Foo() { }
11+
public static void Bar(string s) { }
12+
513
public static void Method()
614
{
715
Foo();
8-
Bar("hello");
9-
}
10-
11-
public static void Foo()
12-
{
13-
}
14-
15-
public static void Bar(string s)
16-
{
16+
Bar("A");
17+
Bar("B");
18+
Bar("C");
19+
Foo();
20+
Bar("D");
21+
Foo();
22+
Bar("E");
23+
Bar("F");
24+
Bar("G");
25+
Bar("H");
26+
Foo();
1727
}
1828
}
19-
}
29+
}

0 commit comments

Comments
 (0)