-
Notifications
You must be signed in to change notification settings - Fork 44
/
CodeMatcher.cs
714 lines (627 loc) · 23.9 KB
/
CodeMatcher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics.CodeAnalysis;
namespace HarmonyLib;
/// <summary>A CodeInstruction matcher</summary>
public class CodeMatcher
{
private readonly ILGenerator generator;
private readonly List<CodeInstruction> codes = [];
/// <summary>The current position</summary>
/// <value>The index or -1 if out of bounds</value>
///
public int Pos { get; private set; } = -1;
private Dictionary<string, CodeInstruction> lastMatches = [];
private string lastError;
private delegate CodeMatcher MatchDelegate();
private MatchDelegate lastMatchCall;
private void FixStart() => Pos = Math.Max(0, Pos);
private void SetOutOfBounds(int direction) => Pos = direction > 0 ? Length : -1;
private int PosOrThrow()
{
if (IsInvalid) throw new InvalidOperationException("Current position is out of bounds");
return Pos;
}
/// <summary>Gets the number of code instructions in this matcher</summary>
/// <value>The count</value>
///
public int Length => codes.Count;
/// <summary>Checks whether the position of this CodeMatcher is within bounds</summary>
/// <value>True if this CodeMatcher is valid</value>
///
public bool IsValid => Pos >= 0 && Pos < Length;
/// <summary>Checks whether the position of this CodeMatcher is outside its bounds</summary>
/// <value>True if this CodeMatcher is invalid</value>
///
public bool IsInvalid => Pos < 0 || Pos >= Length;
/// <summary>Gets the remaining code instructions</summary>
/// <value>The remaining count</value>
///
public int Remaining => Length - PosOrThrow();
/// <summary>Gets the opcode at the current position</summary>
/// <value>The opcode</value>
///
public ref OpCode Opcode => ref codes[PosOrThrow()].opcode;
/// <summary>Gets the operand at the current position</summary>
/// <value>The operand</value>
///
public ref object Operand => ref codes[PosOrThrow()].operand;
/// <summary>Gets the labels at the current position</summary>
/// <value>The labels</value>
///
public ref List<Label> Labels => ref codes[PosOrThrow()].labels;
/// <summary>Gets the exception blocks at the current position</summary>
/// <value>The blocks</value>
///
public ref List<ExceptionBlock> Blocks => ref codes[PosOrThrow()].blocks;
/// <summary>Creates an empty code matcher</summary>
public CodeMatcher()
{
}
/// <summary>Creates a code matcher from an enumeration of instructions</summary>
/// <param name="instructions">The instructions (transpiler argument)</param>
/// <param name="generator">An optional IL generator</param>
///
public CodeMatcher(IEnumerable<CodeInstruction> instructions, ILGenerator generator = null)
{
if (instructions == null) throw new ArgumentNullException(nameof(instructions));
this.generator = generator;
codes = instructions.Select(c => new CodeInstruction(c)).ToList();
}
/// <summary>Makes a clone of this instruction matcher</summary>
/// <returns>A copy of this matcher</returns>
///
public CodeMatcher Clone()
{
return new CodeMatcher(codes, generator)
{
Pos = Pos,
lastMatches = lastMatches,
lastError = lastError,
lastMatchCall = lastMatchCall
};
}
/// <summary>Gets instructions at the current position</summary>
/// <value>The instruction</value>
///
public CodeInstruction Instruction => codes[PosOrThrow()];
/// <summary>Gets instructions at the current position with offset</summary>
/// <param name="offset">The offset</param>
/// <returns>The instruction</returns>
///
public CodeInstruction InstructionAt(int offset)
{
var pos = PosOrThrow();
var newPos = pos + offset;
if (newPos < 0 || newPos >= Length) throw new ArgumentOutOfRangeException(nameof(offset), "offset causes position to go out of bounds");
return codes[newPos];
}
/// <summary>Gets all instructions</summary>
/// <returns>A list of instructions</returns>
///
public List<CodeInstruction> Instructions() => codes;
/// <summary>Gets all instructions as an enumeration</summary>
/// <returns>A list of instructions</returns>
///
public IEnumerable<CodeInstruction> InstructionEnumeration() => codes.AsEnumerable();
/// <summary>Gets some instructions counting from current position</summary>
/// <param name="count">Number of instructions</param>
/// <returns>A list of instructions</returns>
///
public List<CodeInstruction> Instructions(int count) => codes.GetRange(PosOrThrow(), count).Select(c => new CodeInstruction(c)).ToList();
/// <summary>Gets all instructions within a range</summary>
/// <param name="start">The start index</param>
/// <param name="end">The end index</param>
/// <returns>A list of instructions</returns>
///
public List<CodeInstruction> InstructionsInRange(int start, int end)
{
var instructions = codes;
if (start > end)
(end, start) = (start, end);
instructions = instructions.GetRange(start, end - start + 1);
return instructions.Select(c => new CodeInstruction(c)).ToList();
}
/// <summary>Gets all instructions within a range (relative to current position)</summary>
/// <param name="startOffset">The start offset</param>
/// <param name="endOffset">The end offset</param>
/// <returns>A list of instructions</returns>
///
public List<CodeInstruction> InstructionsWithOffsets(int startOffset, int endOffset)
{
var pos = PosOrThrow();
var startPos = pos + startOffset;
if (startPos < 0 || startPos >= Length) throw new ArgumentOutOfRangeException(nameof(startOffset), "startOffset causes position to go out of bounds");
var endPos = pos + endOffset;
if (endPos < 0 || endPos >= Length) throw new ArgumentOutOfRangeException(nameof(endOffset), "endOffset causes position to go out of bounds");
return InstructionsInRange(startPos, endPos);
}
/// <summary>Gets a list of all distinct labels</summary>
/// <param name="instructions">The instructions (transpiler argument)</param>
/// <returns>A list of Labels</returns>
///
public List<Label> DistinctLabels(IEnumerable<CodeInstruction> instructions) => instructions.SelectMany(instruction => instruction.labels).Distinct().ToList();
/// <summary>Reports a failure</summary>
/// <param name="method">The method involved</param>
/// <param name="logger">The logger</param>
/// <returns>True if current position is invalid and error was logged</returns>
///
public bool ReportFailure(MethodBase method, Action<string> logger)
{
if (IsValid) return false;
var err = lastError ?? "Unexpected code";
logger($"{err} in {method}");
return true;
}
/// <summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed)</summary>
/// <param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher ThrowIfInvalid(string explanation)
{
if (explanation == null) throw new ArgumentNullException(nameof(explanation));
if (IsInvalid)
{
var errMsg = explanation + " - Current state is invalid.";
if (lastError != null) errMsg += " Details: " + lastError;
throw new InvalidOperationException(errMsg);
}
return this;
}
/// <summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
/// or if the matches do not match at current position</summary>
/// <param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher ThrowIfNotMatch(string explanation, params CodeMatch[] matches)
{
_ = ThrowIfInvalid(explanation);
if (!MatchSequence(Pos, matches)) throw new InvalidOperationException(explanation + " - Match failed");
return this;
}
private void ThrowIfNotMatch(string explanation, int direction, CodeMatch[] matches)
{
_ = ThrowIfInvalid(explanation);
var tempPos = Pos;
try
{
if (Match(matches, direction, false).IsInvalid)
throw new InvalidOperationException(explanation + " - Match failed");
}
finally
{
Pos = tempPos;
}
}
/// <summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
/// or if the matches do not match at any point between current position and the end</summary>
/// <param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher ThrowIfNotMatchForward(string explanation, params CodeMatch[] matches)
{
ThrowIfNotMatch(explanation, 1, matches);
return this;
}
/// <summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
/// or if the matches do not match at any point between current position and the start</summary>
/// <param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher ThrowIfNotMatchBack(string explanation, params CodeMatch[] matches)
{
ThrowIfNotMatch(explanation, -1, matches);
return this;
}
/// <summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
/// or if the check function returns false</summary>
/// <param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
/// <param name="stateCheckFunc">Function that checks validity of current state. If it returns false, an exception is thrown</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher ThrowIfFalse(string explanation, Func<CodeMatcher, bool> stateCheckFunc)
{
if (stateCheckFunc == null) throw new ArgumentNullException(nameof(stateCheckFunc));
_ = ThrowIfInvalid(explanation);
if (!stateCheckFunc(this)) throw new InvalidOperationException(explanation + " - Check function returned false");
return this;
}
/// <summary>Sets an instruction at current position</summary>
/// <param name="instruction">The instruction to set</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetInstruction(CodeInstruction instruction)
{
codes[PosOrThrow()] = instruction;
return this;
}
/// <summary>Sets instruction at current position and advances</summary>
/// <param name="instruction">The instruction</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetInstructionAndAdvance(CodeInstruction instruction)
{
_ = SetInstruction(instruction);
Pos++;
return this;
}
/// <summary>Sets opcode and operand at current position</summary>
/// <param name="opcode">The opcode</param>
/// <param name="operand">The operand</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Set(OpCode opcode, object operand)
{
Opcode = opcode;
Operand = operand;
return this;
}
/// <summary>Sets opcode and operand at current position and advances</summary>
/// <param name="opcode">The opcode</param>
/// <param name="operand">The operand</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetAndAdvance(OpCode opcode, object operand)
{
_ = Set(opcode, operand);
Pos++;
return this;
}
/// <summary>Sets opcode at current position and advances</summary>
/// <param name="opcode">The opcode</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetOpcodeAndAdvance(OpCode opcode)
{
Opcode = opcode;
Pos++;
return this;
}
/// <summary>Sets operand at current position and advances</summary>
/// <param name="operand">The operand</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetOperandAndAdvance(object operand)
{
Operand = operand;
Pos++;
return this;
}
/// <summary>Declares a local variable but does not add it</summary>
/// <param name="variableType">The variable type</param>
/// <param name="localVariable">[out] The new local variable</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher DeclareLocal(Type variableType, out LocalBuilder localVariable)
{
localVariable = generator.DeclareLocal(variableType);
return this;
}
/// <summary>Declares a new label but does not add it</summary>
/// <param name="label">[out] The new label</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher DefineLabel(out Label label)
{
label = generator.DefineLabel();
return this;
}
/// <summary>Creates a label at current position</summary>
/// <param name="label">[out] The label</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher CreateLabel(out Label label)
{
label = generator.DefineLabel();
Labels.Add(label);
return this;
}
/// <summary>Creates a label at a position</summary>
/// <param name="position">The position</param>
/// <param name="label">[out] The new label</param>
/// <returns>The same code matcher</returns>
///
[SuppressMessage("Style", "IDE0300")]
public CodeMatcher CreateLabelAt(int position, out Label label)
{
label = generator.DefineLabel();
_ = AddLabelsAt(position, new[] {label});
return this;
}
/// <summary>Creates a label at a position</summary>
/// <param name="offset">The offset</param>
/// <param name="label">[out] The new label</param>
/// <returns>The same code matcher</returns>
///
[SuppressMessage("Style", "IDE0300")]
public CodeMatcher CreateLabelWithOffsets(int offset, out Label label)
{
label = generator.DefineLabel();
return AddLabelsAt(Pos + offset, new[] { label });
}
/// <summary>Adds an enumeration of labels to current position</summary>
/// <param name="labels">The labels</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher AddLabels(IEnumerable<Label> labels)
{
Labels.AddRange(labels);
return this;
}
/// <summary>Adds an enumeration of labels at a position</summary>
/// <param name="position">The position</param>
/// <param name="labels">The labels</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher AddLabelsAt(int position, IEnumerable<Label> labels)
{
if (position < 0 || position >= Length) throw new ArgumentOutOfRangeException(nameof(position), "position is out of bounds");
codes[position].labels.AddRange(labels);
return this;
}
/// <summary>Sets jump to</summary>
/// <param name="opcode">Branch instruction</param>
/// <param name="destination">Destination for the jump</param>
/// <param name="label">[out] The created label</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SetJumpTo(OpCode opcode, int destination, out Label label)
{
_ = CreateLabelAt(destination, out label);
return Set(opcode, label);
}
/// <summary>Inserts some instructions</summary>
/// <param name="instructions">The instructions</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Insert(params CodeInstruction[] instructions)
{
if (Pos == Length)
codes.AddRange(instructions);
else
codes.InsertRange(PosOrThrow(), instructions);
return this;
}
/// <summary>Inserts an enumeration of instructions</summary>
/// <param name="instructions">The instructions</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Insert(IEnumerable<CodeInstruction> instructions)
{
if (Pos == Length)
codes.AddRange(instructions);
else
codes.InsertRange(PosOrThrow(), instructions);
return this;
}
/// <summary>Inserts a branch</summary>
/// <param name="opcode">The branch opcode</param>
/// <param name="destination">Branch destination</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher InsertBranch(OpCode opcode, int destination)
{
var pos = PosOrThrow();
_ = CreateLabelAt(destination, out var label);
codes.Insert(pos, new CodeInstruction(opcode, label));
return this;
}
/// <summary>Inserts some instructions and advances the position</summary>
/// <param name="instructions">The instructions</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher InsertAndAdvance(params CodeInstruction[] instructions)
{
foreach (var instruction in instructions)
{
_ = Insert(instruction);
Pos++;
}
return this;
}
/// <summary>Inserts an enumeration of instructions and advances the position</summary>
/// <param name="instructions">The instructions</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher InsertAndAdvance(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
_ = InsertAndAdvance(instruction);
return this;
}
/// <summary>Inserts a branch and advances the position</summary>
/// <param name="opcode">The branch opcode</param>
/// <param name="destination">Branch destination</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher InsertBranchAndAdvance(OpCode opcode, int destination)
{
_ = InsertBranch(opcode, destination);
Pos++;
return this;
}
/// <summary>Removes current instruction</summary>
/// <returns>The same code matcher</returns>
///
public CodeMatcher RemoveInstruction()
{
codes.RemoveAt(PosOrThrow());
return this;
}
/// <summary>Removes some instruction from current position by count</summary>
/// <param name="count">Number of instructions</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher RemoveInstructions(int count)
{
codes.RemoveRange(PosOrThrow(), count);
return this;
}
/// <summary>Removes the instructions in a range</summary>
/// <param name="start">The start</param>
/// <param name="end">The end</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher RemoveInstructionsInRange(int start, int end)
{
if (start < 0 || start >= Length) throw new ArgumentOutOfRangeException(nameof(start), "start is out of bounds");
if (end < 0 || end >= Length) throw new ArgumentOutOfRangeException(nameof(end), "end is out of bounds");
if (start > end)
(end, start) = (start, end);
codes.RemoveRange(start, end - start + 1);
return this;
}
/// <summary>Removes the instructions in a offset range</summary>
/// <param name="startOffset">The start offset</param>
/// <param name="endOffset">The end offset</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher RemoveInstructionsWithOffsets(int startOffset, int endOffset) => RemoveInstructionsInRange(Pos + startOffset, Pos + endOffset);
/// <summary>Advances the current position</summary>
/// <param name="offset">The offset</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Advance(int offset)
{
Pos += offset;
if (IsValid == false) SetOutOfBounds(offset);
return this;
}
/// <summary>Moves the current position to the start</summary>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Start()
{
Pos = 0;
return this;
}
/// <summary>Moves the current position to the end</summary>
/// <returns>The same code matcher</returns>
///
public CodeMatcher End()
{
Pos = Length - 1;
return this;
}
/// <summary>Searches forward with a predicate and advances position</summary>
/// <param name="predicate">The predicate</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SearchForward(Func<CodeInstruction, bool> predicate) => Search(predicate, 1);
/// <summary>Searches backwards with a predicate and reverses position</summary>
/// <param name="predicate">The predicate</param>
/// <returns>The same code matcher</returns>
///
[Obsolete("Use SearchBackwards instead")]
public CodeMatcher SearchBack(Func<CodeInstruction, bool> predicate) => Search(predicate, -1);
/// <summary>Searches backwards with a predicate and reverses position</summary>
/// <param name="predicate">The predicate</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher SearchBackwards(Func<CodeInstruction, bool> predicate) => Search(predicate, -1);
private CodeMatcher Search(Func<CodeInstruction, bool> predicate, int direction)
{
FixStart(); //todo: Should invalid position throw instead? Breaking change
while (IsValid && predicate(Instruction) == false)
Pos += direction;
lastError = IsInvalid ? $"Cannot find {predicate}" : null;
return this;
}
/// <summary>Matches forward and advances position</summary>
/// <param name="useEnd">True to set position to end of match, false to set it to the beginning of the match</param>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchForward(bool useEnd, params CodeMatch[] matches) => Match(matches, 1, useEnd);
/// <summary>Matches backwards and reverses position</summary>
/// <param name="useEnd">True to set position to end of match, false to set it to the beginning of the match</param>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchBack(bool useEnd, params CodeMatch[] matches) => Match(matches, -1, useEnd);
/// <summary>Matches forward and advances position to beginning of matching sequence</summary>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchStartForward(params CodeMatch[] matches) => Match(matches, 1, false);
/// <summary>Matches forward and advances position to ending of matching sequence</summary>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchEndForward(params CodeMatch[] matches) => Match(matches, 1, true);
/// <summary>Matches backwards and reverses position to beginning of matching sequence</summary>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchStartBackwards(params CodeMatch[] matches) => Match(matches, -1, false);
/// <summary>Matches backwards and reverses position to ending of matching sequence</summary>
/// <param name="matches">Some code matches</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher MatchEndBackwards(params CodeMatch[] matches) => Match(matches, -1, true);
private CodeMatcher Match(CodeMatch[] matches, int direction, bool useEnd)
{
lastMatchCall = delegate ()
{
FixStart(); //todo: Should invalid position throw instead? Breaking change
while (IsValid)
{
if (MatchSequence(Pos, matches))
{
if (useEnd) Pos += matches.Length - 1;
break;
}
Pos += direction;
}
lastError = IsInvalid ? $"Cannot find {matches.Join()}" : null;
return this;
};
return lastMatchCall();
}
/// <summary>Repeats a match action until boundaries are met</summary>
/// <param name="matchAction">The match action</param>
/// <param name="notFoundAction">An optional action that is executed when no match is found</param>
/// <returns>The same code matcher</returns>
///
public CodeMatcher Repeat(Action<CodeMatcher> matchAction, Action<string> notFoundAction = null)
{
var count = 0;
if (lastMatchCall == null)
throw new InvalidOperationException("No previous Match operation - cannot repeat");
while (IsValid)
{
matchAction(this);
_ = lastMatchCall();
count++;
}
lastMatchCall = null;
if (count == 0 && notFoundAction != null)
notFoundAction(lastError);
return this;
}
/// <summary>Gets a match by its name</summary>
/// <param name="name">The match name</param>
/// <returns>An instruction</returns>
///
public CodeInstruction NamedMatch(string name) => lastMatches[name];
private bool MatchSequence(int start, CodeMatch[] matches)
{
if (start < 0) return false;
lastMatches = [];
foreach (var match in matches)
{
if (start >= Length || match.Matches(codes, codes[start]) == false)
return false;
if (match.name != null)
lastMatches.Add(match.name, codes[start]);
start++;
}
return true;
}
}