this repo has no description
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}