···11+using GDWeave.Godot;
22+using Teemaw.Calico.Util;
33+using static GDWeave.Godot.TokenType;
44+55+namespace Teemaw.Calico.LexicalTransformer;
66+77+using MultiTokenPattern = Func<Token, bool>[];
88+99+public static class TransformationPatternFactory
1010+{
1111+ /// <summary>
1212+ /// Creates a new pattern array which matches `extends <Identifier>\n`. Useful for patching into the global
1313+ /// scope.
1414+ /// </summary>
1515+ /// <returns></returns>
1616+ public static MultiTokenPattern CreateGlobalsPattern()
1717+ {
1818+ return
1919+ [
2020+ t => t.Type is PrExtends,
2121+ t => t.Type is Identifier,
2222+ t => t.Type is Newline
2323+ ];
2424+ }
2525+2626+ /// <summary>
2727+ /// Creates a new pattern array which matches a function definition. This will not match the preceding or trailing
2828+ /// Newline tokens.
2929+ /// </summary>
3030+ /// <param name="name">The name of the function to match.</param>
3131+ /// <param name="args">
3232+ /// An array of the names of arguments of the function to match. If null or empty, the returned checks will only
3333+ /// match a function which does not accept arguments.
3434+ /// </param>
3535+ /// <returns></returns>
3636+ public static MultiTokenPattern CreateFunctionDefinitionPattern(string name, string[]? args = null)
3737+ {
3838+ var checks = new List<Func<Token, bool>>();
3939+4040+ checks.Add(t => t.Type is PrFunction);
4141+ checks.Add(t => t is IdentifierToken token && token.Name == name);
4242+ checks.Add(t => t.Type is ParenthesisOpen);
4343+ if (args is { Length: > 0 })
4444+ {
4545+ foreach (var arg in args)
4646+ {
4747+ checks.Add(t => t is IdentifierToken token && token.Name == arg);
4848+ checks.Add(t => t.Type is Comma);
4949+ }
5050+5151+ checks.RemoveAt(checks.Count - 1);
5252+ }
5353+5454+ checks.Add(t => t.Type is ParenthesisClose);
5555+ checks.Add(t => t.Type is Colon);
5656+5757+ return checks.ToArray();
5858+ }
5959+6060+ /// <summary>
6161+ /// Creates a new pattern array which matches the provided GDScript snippet.
6262+ /// </summary>
6363+ /// <param name="snippet">A GDScript snippet to match.</param>
6464+ /// <param name="indent">The base indent of the snippet.</param>
6565+ /// <returns></returns>
6666+ public static MultiTokenPattern CreateGdSnippetPattern(string snippet, uint indent = 0)
6767+ {
6868+ var tokens = ScriptTokenizer.Tokenize(snippet, indent);
6969+7070+ return tokens.Select(snippetToken => (Func<Token, bool>)(t =>
7171+ {
7272+ if (t.Type == Identifier)
7373+ {
7474+ return snippetToken is IdentifierToken snippetIdentifier && t is IdentifierToken identifier &&
7575+ snippetIdentifier.Name == identifier.Name;
7676+ }
7777+7878+ if (t.Type == Constant)
7979+ {
8080+ return snippetToken is ConstantToken snippetConstant && t is ConstantToken constant &&
8181+ snippetConstant.Value.Equals(constant.Value);
8282+ }
8383+8484+ return t.Type == snippetToken.Type;
8585+ })).ToArray();
8686+ }
8787+}
···11+using GDWeave.Godot;
22+using GDWeave.Modding;
33+using ScriptTokenizer = Teemaw.Calico.Util.ScriptTokenizer;
44+55+namespace Teemaw.Calico.LexicalTransformer;
66+77+using MultiTokenPattern = Func<Token, bool>[];
88+99+public enum Operation
1010+{
1111+ /// <summary>
1212+ /// Do not patch.
1313+ /// </summary>
1414+ None,
1515+1616+ /// <summary>
1717+ /// Replace all tokens of the waiter. This is a buffered operation. Buffered rules do not support overlapping token
1818+ /// patterns with other buffered rules.
1919+ /// </summary>
2020+ ReplaceAll,
2121+2222+ /// <summary>
2323+ /// Replace the final token of the waiter.
2424+ /// </summary>
2525+ ReplaceLast,
2626+2727+ /// <summary>
2828+ /// Appends after the final token of the waiter.
2929+ /// </summary>
3030+ Append,
3131+3232+ /// <summary>
3333+ /// Prepends before the first token of the waiter. This is a buffered operation. Buffered rules do not support
3434+ /// overlapping token patterns with other buffered rules.
3535+ /// </summary>
3636+ Prepend,
3737+}
3838+3939+public static class OperationExtensions
4040+{
4141+ public static bool RequiresBuffer(this Operation operation)
4242+ {
4343+ return operation is Operation.ReplaceAll or Operation.Prepend;
4444+ }
4545+4646+ public static bool YieldTokenBeforeOperation(this Operation operation)
4747+ {
4848+ return operation is Operation.Append or Operation.None;
4949+ }
5050+5151+ public static bool YieldTokenAfterOperation(this Operation operation)
5252+ {
5353+ return !operation.RequiresBuffer() && !operation.YieldTokenBeforeOperation() &&
5454+ operation != Operation.ReplaceLast;
5555+ }
5656+}
5757+5858+/// <summary>
5959+/// This holds the information required to perform a patch at a single locus.
6060+/// </summary>
6161+/// <param name="Name">The name of this descriptor. Used for logging.</param>
6262+/// <param name="Pattern">A list of checks to be used in a MultiTokenWaiter.</param>
6363+/// <param name="ScopePattern">A list of checks to be used in a MultiTokenWaiter marking the .</param>
6464+/// <param name="Tokens">A list of GDScript tokens which will be patched in.</param>
6565+/// <param name="Operation">The type of patch.</param>
6666+/// <param name="Times">The number of times this rule is expected to match.</param>
6767+/// <param name="Predicate">A predicate which must return true for the rule to match.</param>
6868+public record TransformationRule(
6969+ string Name,
7070+ MultiTokenPattern Pattern,
7171+ MultiTokenPattern ScopePattern,
7272+ IEnumerable<Token> Tokens,
7373+ Operation Operation,
7474+ uint Times,
7575+ Func<bool> Predicate)
7676+{
7777+7878+ public MultiTokenWaiter CreateMultiTokenWaiter() => new(Pattern);
7979+8080+ public MultiTokenWaiter CreateMultiTokenWaiterForScope() => new(ScopePattern);
8181+}
8282+8383+/// <summary>
8484+/// Builder for TransformationRule. Times defaults to 1. Operation defaults to <see cref="Operation.Append"/>.
8585+/// </summary>
8686+public class TransformationRuleBuilder
8787+{
8888+ private string? _name;
8989+ private MultiTokenPattern? _pattern;
9090+ private MultiTokenPattern _scopePattern = [];
9191+ private IEnumerable<Token>? _tokens;
9292+ private uint _times = 1;
9393+ private Operation _operation = Operation.Append;
9494+ private Func<bool> _predicate = () => true;
9595+9696+ /// <summary>
9797+ /// Sets the name for the TransformationRule. Used for logging.
9898+ /// </summary>
9999+ /// <param name="name"></param>
100100+ /// <returns></returns>
101101+ public TransformationRuleBuilder Named(string name)
102102+ {
103103+ _name = name;
104104+ return this;
105105+ }
106106+107107+ /// <summary>
108108+ /// Sets the token pattern which will be matched by the TransformationRule.
109109+ /// </summary>
110110+ /// <param name="pattern"></param>
111111+ /// <returns></returns>
112112+ public TransformationRuleBuilder Matching(MultiTokenPattern pattern)
113113+ {
114114+ _pattern = pattern;
115115+ return this;
116116+ }
117117+118118+ /// <summary>
119119+ /// Sets the token content which will be patched in for the TransformationRule.
120120+ /// </summary>
121121+ /// <param name="tokens"></param>
122122+ /// <returns></returns>
123123+ public TransformationRuleBuilder With(IEnumerable<Token> tokens)
124124+ {
125125+ _tokens = tokens;
126126+ return this;
127127+ }
128128+129129+ /// <summary>
130130+ /// Sets the token content which will be patched in for the TransformationRule.
131131+ /// </summary>
132132+ /// <param name="token"></param>
133133+ /// <returns></returns>
134134+ public TransformationRuleBuilder With(Token token)
135135+ {
136136+ _tokens = [token];
137137+ return this;
138138+ }
139139+140140+ /// <summary>
141141+ /// Sets the token content which will be patched in for the TransformationRule with a GDScript snippet.
142142+ /// </summary>
143143+ /// <param name="snippet"></param>
144144+ /// <param name="indent">The base indentation level for the tokenizer.</param>
145145+ /// <returns></returns>
146146+ public TransformationRuleBuilder With(string snippet, uint indent = 0)
147147+ {
148148+ _tokens = ScriptTokenizer.Tokenize(snippet, indent);
149149+ return this;
150150+ }
151151+152152+ /// <summary>
153153+ /// Sets the <see cref="Operation"/> of the TransformationRule.
154154+ /// </summary>
155155+ /// <param name="operation"></param>
156156+ /// <returns></returns>
157157+ public TransformationRuleBuilder Do(Operation operation)
158158+ {
159159+ _operation = operation;
160160+ return this;
161161+ }
162162+163163+ /// <summary>
164164+ /// Sets the number of times the rule is expected to match.
165165+ /// </summary>
166166+ /// <param name="times"></param>
167167+ /// <returns></returns>
168168+ public TransformationRuleBuilder ExpectTimes(uint times)
169169+ {
170170+ _times = times;
171171+ return this;
172172+ }
173173+174174+ /// <summary>
175175+ /// Sets the scope of this rule.
176176+ /// </summary>
177177+ /// <param name="scopePattern"></param>
178178+ /// <returns></returns>
179179+ public TransformationRuleBuilder ScopedTo(MultiTokenPattern scopePattern)
180180+ {
181181+ _scopePattern = scopePattern;
182182+ return this;
183183+ }
184184+185185+ /// <summary>
186186+ /// Sets the predicate function whose return value will decide if this rule will be checked.
187187+ /// </summary>
188188+ /// <param name="predicate"></param>
189189+ /// <returns></returns>
190190+ public TransformationRuleBuilder When(Func<bool> predicate)
191191+ {
192192+ _predicate = predicate;
193193+ return this;
194194+ }
195195+196196+ /// <summary>
197197+ /// Sets a value which will decide if this rule will be checked.
198198+ /// </summary>
199199+ /// <param name="eligible">If true, this rule will be checked.</param>
200200+ /// <returns></returns>
201201+ public TransformationRuleBuilder When(bool eligible)
202202+ {
203203+ _predicate = () => eligible;
204204+ return this;
205205+ }
206206+207207+ /// <summary>
208208+ /// Builds the TransformationRule.
209209+ /// </summary>
210210+ /// <returns></returns>
211211+ /// <exception cref="ArgumentNullException">Thrown if any required fields were not set.</exception>
212212+ public TransformationRule Build()
213213+ {
214214+ if (string.IsNullOrEmpty(_name))
215215+ {
216216+ throw new ArgumentNullException(nameof(_name), "Name cannot be null or empty");
217217+ }
218218+219219+ if (_pattern == null)
220220+ {
221221+ throw new ArgumentNullException(nameof(_pattern), "Pattern cannot be null");
222222+ }
223223+224224+ if (_tokens == null)
225225+ {
226226+ throw new ArgumentNullException(nameof(_tokens), "Tokens cannot be null");
227227+ }
228228+229229+ return new TransformationRule(_name, _pattern, _scopePattern, _tokens, _operation, _times, _predicate);
230230+ }
231231+}
···11+using System.Text.Json.Serialization;
22+33+namespace Atproto;
44+55+public class Config {
66+ [JsonInclude] public string Handle = "";
77+ [JsonInclude] public string Password = "";
88+}
+17
Atproto/Mod.cs
···11+using GDWeave;
22+33+namespace Atproto;
44+55+public class Mod : IMod {
66+ public Config Config;
77+88+ public Mod(IModInterface modInterface) {
99+ Config = modInterface.ReadConfig<Config>();
1010+ modInterface.RegisterScriptMod(CatchFishFactory.Create(modInterface));
1111+ modInterface.RegisterScriptMod(CatptureFishFactory.Create(modInterface));
1212+ }
1313+1414+ public void Dispose() {
1515+ // Cleanup anything you do here
1616+ }
1717+}
···11+# ATProto Webfishing
22+33+A Webfishing mod that send data to your PDS
44+55+## Requirements
66+- [GDWeave](https://thunderstore.io/c/webfishing/p/NotNet/GDWeave/)
77+- [TackleBox](https://thunderstore.io/c/webfishing/p/PuppyGirl/TackleBox/)
88+99+## Configuration
1010+In game using the TackleBox mod menu, you can input your AtProto Handle as well as an App Password to login
1111+After it's done, simply save and quit the game, you'll be connected on the next startup
1212+1313+## Credits
1414+1515+- Thanks to [Tiany Ma](https://github.com/tma02) for its utils