this repo has no description
at master 6.7 kB view raw
1using GDWeave; 2using GDWeave.Godot; 3using GDWeave.Modding; 4using static Teemaw.Calico.LexicalTransformer.Operation; 5 6namespace Teemaw.Calico.LexicalTransformer; 7 8/// <summary> 9/// An IScriptMod implementation that handles patching through the provided list of TransformationRules. 10/// </summary> 11/// <param name="mod">IModInterface of the current mod.</param> 12/// <param name="name">The name of this script mod. Used for logging.</param> 13/// <param name="scriptPath">The GD res:// path of the script which will be patched.</param> 14/// <param name="rules">A list of patches to perform. Multiple descriptors with overlapping checks is not supported.</param> 15public class TransformationRuleScriptMod( 16 IModInterface mod, 17 string name, 18 string scriptPath, 19 Func<bool> predicate, 20 TransformationRule[] rules) 21 : IScriptMod 22{ 23 public bool ShouldRun(string path) 24 { 25 if (path != scriptPath) return false; 26 if (!predicate.Invoke()) 27 { 28 mod.Logger.Information($"[{name}] Predicate failed, not patching."); 29 return false; 30 } 31 32 return true; 33 } 34 35 public IEnumerable<Token> Modify(string path, IEnumerable<Token> tokens) 36 { 37 var eligibleRules = rules.Where(rule => 38 { 39 var eligible = rule.Predicate(); 40 if (!eligible) 41 { 42 mod.Logger.Information($"[{name}] Skipping patch {rule.Name}..."); 43 } 44 45 return eligible; 46 }).ToList(); 47 var transformers = eligibleRules.Select(rule => 48 (Rule: rule, Waiter: rule.CreateMultiTokenWaiter(), Buffer: new List<Token>())) 49 .ToList(); 50 mod.Logger.Information($"[{name}] Patching {path}"); 51 52 var patchOccurrences = 53 eligibleRules.ToDictionary(r => r.Name, r => (Occurred: 0, Expected: r.Times)); 54 var bufferAfterChecks = true; 55 var stagingBuffer = new List<Token>(); 56 stagingBuffer.AddRange(tokens); 57 var transformedBuffer = new List<Token>(); 58 59 foreach (var transformer in transformers) 60 { 61 var hasScopePattern = transformer.Rule.ScopePattern.Length > 0; 62 var inScope = !hasScopePattern; 63 uint? scopeIndent = null; 64 var scopeWaiter = transformer.Rule.CreateMultiTokenWaiterForScope(); 65 66 foreach (var token in stagingBuffer) 67 { 68 if (inScope && hasScopePattern) 69 { 70 // Try to find the scope's base indentation 71 if (scopeIndent == null && token.Type == TokenType.Newline) 72 { 73 scopeIndent = token.AssociatedData ?? 0; 74 } 75 // Check if we should leave the scope 76 else if (scopeIndent != null && token.Type == TokenType.Newline) 77 { 78 inScope = (token.AssociatedData ?? 0) >= scopeIndent; 79 } 80 } 81 else if (hasScopePattern) 82 { 83 // We should never reach this case if the scope pattern has Length = 0. 84 if (scopeWaiter.Check(token)) 85 { 86 scopeWaiter.Reset(); 87 // Latch inScope to true if we match the scope pattern. 88 inScope = true; 89 } 90 } 91 92 if (!inScope) 93 { 94 transformedBuffer.Add(token); 95 continue; 96 } 97 98 transformer.Waiter.Check(token); 99 if (transformer.Waiter.Step == 0) 100 { 101 transformedBuffer.AddRange(transformer.Buffer); 102 transformer.Buffer.Clear(); 103 } 104 else 105 { 106 if (transformer.Rule.Operation.RequiresBuffer()) 107 { 108 transformer.Buffer.Add(token); 109 bufferAfterChecks = false; 110 } 111 112 if (transformer.Waiter.Matched) 113 { 114 transformer.Waiter.Reset(); 115 116 if (transformer.Rule.Operation.YieldTokenBeforeOperation()) 117 { 118 transformedBuffer.Add(token); 119 bufferAfterChecks = false; 120 } 121 else 122 { 123 bufferAfterChecks = 124 transformer.Rule.Operation.YieldTokenAfterOperation(); 125 } 126 127 switch (transformer.Rule.Operation) 128 { 129 case Prepend: 130 transformedBuffer.AddRange(transformer.Rule.Tokens); 131 transformedBuffer.AddRange(transformer.Buffer); 132 transformer.Buffer.Clear(); 133 break; 134 case ReplaceLast: 135 case Append: 136 case ReplaceAll: 137 transformer.Buffer.Clear(); 138 transformedBuffer.AddRange(transformer.Rule.Tokens); 139 break; 140 case None: 141 default: 142 break; 143 } 144 145 mod.Logger.Information( 146 $"[{name}] Patch {transformer.Rule.Name} OK!"); 147 patchOccurrences[transformer.Rule.Name] = 148 patchOccurrences[transformer.Rule.Name] with 149 { 150 Occurred = patchOccurrences[transformer.Rule.Name].Occurred + 1 151 }; 152 } 153 } 154 155 if (bufferAfterChecks) 156 { 157 transformedBuffer.Add(token); 158 } 159 else 160 { 161 bufferAfterChecks = true; 162 } 163 } 164 165 stagingBuffer.Clear(); 166 stagingBuffer.AddRange(transformedBuffer); 167 transformedBuffer.Clear(); 168 } 169 170 foreach (var result in patchOccurrences.Where(result => 171 result.Value.Occurred != result.Value.Expected)) 172 { 173 mod.Logger.Error( 174 $"[{name}] Patch {result.Key} FAILED! Times expected={result.Value.Expected}, actual={result.Value.Occurred}"); 175 } 176 177 return stagingBuffer; 178 } 179}