Browse Source

Add: string.gsub

AnnulusGames 1 year ago
parent
commit
5833bf212b
2 changed files with 105 additions and 0 deletions
  1. 1 0
      src/Lua/Standard/OpenLibExtensions.cs
  2. 104 0
      src/Lua/Standard/Text/GSubFunction.cs

+ 1 - 0
src/Lua/Standard/OpenLibExtensions.cs

@@ -96,6 +96,7 @@ public static class OpenLibExtensions
         FindFunction.Instance,
         FindFunction.Instance,
         FormatFunction.Instance,
         FormatFunction.Instance,
         GMatchFunction.Instance,
         GMatchFunction.Instance,
+        GSubFunction.Instance,
         LenFunction.Instance,
         LenFunction.Instance,
         LowerFunction.Instance,
         LowerFunction.Instance,
         RepFunction.Instance,
         RepFunction.Instance,

+ 104 - 0
src/Lua/Standard/Text/GSubFunction.cs

@@ -0,0 +1,104 @@
+using System.Text;
+using System.Text.RegularExpressions;
+using Lua.Internal;
+
+namespace Lua.Standard.Text;
+
+public sealed class GSubFunction : LuaFunction
+{
+    public override string Name => "gsub";
+    public static readonly GSubFunction Instance = new();
+
+    protected override async ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var s = context.GetArgument<string>(0);
+        var pattern = context.GetArgument<string>(1);
+        var repl = context.GetArgument(2);
+        var n_arg = context.HasArgument(3)
+            ? context.GetArgument<double>(3)
+            : int.MaxValue;
+
+        LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, this, 4, n_arg);
+
+        var n = (int)n_arg;
+        var regex = StringHelper.ToRegex(pattern);
+        var matches = regex.Matches(s);
+
+        // TODO: reduce allocation
+        var builder = new StringBuilder();
+        var lastIndex = 0;
+        var replaceCount = 0;
+
+        for (int i = 0; i < matches.Count; i++)
+        {
+            if (replaceCount > n) break;
+
+            var match = matches[i];
+            builder.Append(s.AsSpan()[lastIndex..match.Index]);
+            replaceCount++;
+
+            LuaValue result;
+            if (repl.TryRead<string>(out var str))
+            {
+                result = str.Replace("%%", "%")
+                    .Replace("%0", match.Value);
+
+                for (int k = 1; k <= match.Groups.Count; k++)
+                {
+                    if (replaceCount > n) break;
+                    result = result.Read<string>().Replace($"%{k}", match.Groups[k].Value);
+                    replaceCount++;
+                }
+            }
+            else if (repl.TryRead<LuaTable>(out var table))
+            {
+                result = table[match.Groups[1].Value];
+            }
+            else if (repl.TryRead<LuaFunction>(out var func))
+            {
+                for (int k = 1; k <= match.Groups.Count; k++)
+                {
+                    context.State.Push(match.Groups[k].Value);
+                }
+
+                using var methodBuffer = new PooledArray<LuaValue>(1024);
+                await func.InvokeAsync(context with
+                {
+                    ArgumentCount = match.Groups.Count,
+                    StackPosition = null
+                }, methodBuffer.AsMemory(), cancellationToken);
+
+                result = methodBuffer[0];
+            }
+            else
+            {
+                throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #3 to 'gsub' (string/function/table expected)");
+            }
+
+            if (result.TryRead<string>(out var rs))
+            {
+                builder.Append(rs);
+            }
+            else if (result.TryRead<double>(out var rd))
+            {
+                builder.Append(rd);
+            }
+            else if (!result.ToBoolean())
+            {
+                builder.Append(match.Value);
+                replaceCount--;
+            }
+            else
+            {
+                throw new LuaRuntimeException(context.State.GetTraceback(), $"invalid replacement value (a {result.Type})");
+            }
+
+            lastIndex = match.Index + match.Length;
+        }
+
+        builder.Append(s.AsSpan()[lastIndex..s.Length]);
+
+        buffer.Span[0] = builder.ToString();
+        return 1;
+    }
+}