this repo has no description
1using GDWeave.Godot;
2using GDWeave.Modding;
3using ScriptTokenizer = Teemaw.Calico.Util.ScriptTokenizer;
4
5namespace Teemaw.Calico.LexicalTransformer;
6
7using MultiTokenPattern = Func<Token, bool>[];
8
9public enum Operation
10{
11 /// <summary>
12 /// Do not patch.
13 /// </summary>
14 None,
15
16 /// <summary>
17 /// Replace all tokens of the waiter. This is a buffered operation. Buffered rules do not support overlapping token
18 /// patterns with other buffered rules.
19 /// </summary>
20 ReplaceAll,
21
22 /// <summary>
23 /// Replace the final token of the waiter.
24 /// </summary>
25 ReplaceLast,
26
27 /// <summary>
28 /// Appends after the final token of the waiter.
29 /// </summary>
30 Append,
31
32 /// <summary>
33 /// Prepends before the first token of the waiter. This is a buffered operation. Buffered rules do not support
34 /// overlapping token patterns with other buffered rules.
35 /// </summary>
36 Prepend,
37}
38
39public static class OperationExtensions
40{
41 public static bool RequiresBuffer(this Operation operation)
42 {
43 return operation is Operation.ReplaceAll or Operation.Prepend;
44 }
45
46 public static bool YieldTokenBeforeOperation(this Operation operation)
47 {
48 return operation is Operation.Append or Operation.None;
49 }
50
51 public static bool YieldTokenAfterOperation(this Operation operation)
52 {
53 return !operation.RequiresBuffer() && !operation.YieldTokenBeforeOperation() &&
54 operation != Operation.ReplaceLast;
55 }
56}
57
58/// <summary>
59/// This holds the information required to perform a patch at a single locus.
60/// </summary>
61/// <param name="Name">The name of this descriptor. Used for logging.</param>
62/// <param name="Pattern">A list of checks to be used in a MultiTokenWaiter.</param>
63/// <param name="ScopePattern">A list of checks to be used in a MultiTokenWaiter marking the .</param>
64/// <param name="Tokens">A list of GDScript tokens which will be patched in.</param>
65/// <param name="Operation">The type of patch.</param>
66/// <param name="Times">The number of times this rule is expected to match.</param>
67/// <param name="Predicate">A predicate which must return true for the rule to match.</param>
68public record TransformationRule(
69 string Name,
70 MultiTokenPattern Pattern,
71 MultiTokenPattern ScopePattern,
72 IEnumerable<Token> Tokens,
73 Operation Operation,
74 uint Times,
75 Func<bool> Predicate)
76{
77
78 public MultiTokenWaiter CreateMultiTokenWaiter() => new(Pattern);
79
80 public MultiTokenWaiter CreateMultiTokenWaiterForScope() => new(ScopePattern);
81}
82
83/// <summary>
84/// Builder for TransformationRule. Times defaults to 1. Operation defaults to <see cref="Operation.Append"/>.
85/// </summary>
86public class TransformationRuleBuilder
87{
88 private string? _name;
89 private MultiTokenPattern? _pattern;
90 private MultiTokenPattern _scopePattern = [];
91 private IEnumerable<Token>? _tokens;
92 private uint _times = 1;
93 private Operation _operation = Operation.Append;
94 private Func<bool> _predicate = () => true;
95
96 /// <summary>
97 /// Sets the name for the TransformationRule. Used for logging.
98 /// </summary>
99 /// <param name="name"></param>
100 /// <returns></returns>
101 public TransformationRuleBuilder Named(string name)
102 {
103 _name = name;
104 return this;
105 }
106
107 /// <summary>
108 /// Sets the token pattern which will be matched by the TransformationRule.
109 /// </summary>
110 /// <param name="pattern"></param>
111 /// <returns></returns>
112 public TransformationRuleBuilder Matching(MultiTokenPattern pattern)
113 {
114 _pattern = pattern;
115 return this;
116 }
117
118 /// <summary>
119 /// Sets the token content which will be patched in for the TransformationRule.
120 /// </summary>
121 /// <param name="tokens"></param>
122 /// <returns></returns>
123 public TransformationRuleBuilder With(IEnumerable<Token> tokens)
124 {
125 _tokens = tokens;
126 return this;
127 }
128
129 /// <summary>
130 /// Sets the token content which will be patched in for the TransformationRule.
131 /// </summary>
132 /// <param name="token"></param>
133 /// <returns></returns>
134 public TransformationRuleBuilder With(Token token)
135 {
136 _tokens = [token];
137 return this;
138 }
139
140 /// <summary>
141 /// Sets the token content which will be patched in for the TransformationRule with a GDScript snippet.
142 /// </summary>
143 /// <param name="snippet"></param>
144 /// <param name="indent">The base indentation level for the tokenizer.</param>
145 /// <returns></returns>
146 public TransformationRuleBuilder With(string snippet, uint indent = 0)
147 {
148 _tokens = ScriptTokenizer.Tokenize(snippet, indent);
149 return this;
150 }
151
152 /// <summary>
153 /// Sets the <see cref="Operation"/> of the TransformationRule.
154 /// </summary>
155 /// <param name="operation"></param>
156 /// <returns></returns>
157 public TransformationRuleBuilder Do(Operation operation)
158 {
159 _operation = operation;
160 return this;
161 }
162
163 /// <summary>
164 /// Sets the number of times the rule is expected to match.
165 /// </summary>
166 /// <param name="times"></param>
167 /// <returns></returns>
168 public TransformationRuleBuilder ExpectTimes(uint times)
169 {
170 _times = times;
171 return this;
172 }
173
174 /// <summary>
175 /// Sets the scope of this rule.
176 /// </summary>
177 /// <param name="scopePattern"></param>
178 /// <returns></returns>
179 public TransformationRuleBuilder ScopedTo(MultiTokenPattern scopePattern)
180 {
181 _scopePattern = scopePattern;
182 return this;
183 }
184
185 /// <summary>
186 /// Sets the predicate function whose return value will decide if this rule will be checked.
187 /// </summary>
188 /// <param name="predicate"></param>
189 /// <returns></returns>
190 public TransformationRuleBuilder When(Func<bool> predicate)
191 {
192 _predicate = predicate;
193 return this;
194 }
195
196 /// <summary>
197 /// Sets a value which will decide if this rule will be checked.
198 /// </summary>
199 /// <param name="eligible">If true, this rule will be checked.</param>
200 /// <returns></returns>
201 public TransformationRuleBuilder When(bool eligible)
202 {
203 _predicate = () => eligible;
204 return this;
205 }
206
207 /// <summary>
208 /// Builds the TransformationRule.
209 /// </summary>
210 /// <returns></returns>
211 /// <exception cref="ArgumentNullException">Thrown if any required fields were not set.</exception>
212 public TransformationRule Build()
213 {
214 if (string.IsNullOrEmpty(_name))
215 {
216 throw new ArgumentNullException(nameof(_name), "Name cannot be null or empty");
217 }
218
219 if (_pattern == null)
220 {
221 throw new ArgumentNullException(nameof(_pattern), "Pattern cannot be null");
222 }
223
224 if (_tokens == null)
225 {
226 throw new ArgumentNullException(nameof(_tokens), "Tokens cannot be null");
227 }
228
229 return new TransformationRule(_name, _pattern, _scopePattern, _tokens, _operation, _times, _predicate);
230 }
231}