| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- using System.Text;
- using Lua.Internal;
- using Lua.Runtime;
- using System.Globalization;
- using Lua.Standard.Internal;
- using System.Diagnostics;
- namespace Lua.Standard;
- public sealed class StringLibrary
- {
- public static readonly StringLibrary Instance = new();
- public StringLibrary()
- {
- var libraryName = "string";
- Functions =
- [
- new(libraryName, "byte", Byte),
- new(libraryName, "char", Char),
- new(libraryName, "dump", Dump),
- new(libraryName, "find", Find),
- new(libraryName, "format", Format),
- new(libraryName, "gmatch", GMatch),
- new(libraryName, "gsub", GSub),
- new(libraryName, "len", Len),
- new(libraryName, "lower", Lower),
- new(libraryName, "match", Match),
- new(libraryName, "rep", Rep),
- new(libraryName, "reverse", Reverse),
- new(libraryName, "sub", Sub),
- new(libraryName, "upper", Upper),
- ];
- }
- public readonly LibraryFunction[] Functions;
- public ValueTask<int> Byte(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- var i = context.HasArgument(1)
- ? context.GetArgument<double>(1)
- : 1;
- var j = context.HasArgument(2)
- ? context.GetArgument<double>(2)
- : i;
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 2, i);
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 3, j);
- var span = StringHelper.Slice(s, (int)i, (int)j);
- var buffer = context.GetReturnBuffer(span.Length);
- for (int k = 0; k < span.Length; k++)
- {
- buffer[k] = span[k];
- }
- return new(span.Length);
- }
- public ValueTask<int> Char(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- if (context.ArgumentCount == 0)
- {
- return new(context.Return(""));
- }
- var builder = new ValueStringBuilder(context.ArgumentCount);
- for (int i = 0; i < context.ArgumentCount; i++)
- {
- var arg = context.GetArgument<double>(i);
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, i + 1, arg);
- builder.Append((char)arg);
- }
- return new(context.Return(builder.ToString()));
- }
- public ValueTask<int> Dump(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- // stirng.dump is not supported (throw exception)
- throw new NotSupportedException("stirng.dump is not supported");
- }
- public ValueTask<int> Find(LuaFunctionExecutionContext context, CancellationToken cancellationToken) =>
- FindAux(context, true);
- public async ValueTask<int> Format(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var format = context.GetArgument<string>(0);
- var stack = context.Thread.Stack;
- // TODO: pooling StringBuilder
- var builder = new StringBuilder(format.Length * 2);
- var parameterIndex = 1;
- for (int i = 0; i < format.Length; i++)
- {
- if (format[i] == '%')
- {
- i++;
- // escape
- if (format[i] == '%')
- {
- builder.Append('%');
- continue;
- }
- var leftJustify = false;
- var plusSign = false;
- var zeroPadding = false;
- var alternateForm = false;
- var blank = false;
- var width = 0;
- var precision = -1;
- // Process flags
- while (true)
- {
- var c = format[i];
- switch (c)
- {
- case '-':
- if (leftJustify) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
- leftJustify = true;
- break;
- case '+':
- if (plusSign) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
- plusSign = true;
- break;
- case '0':
- if (zeroPadding) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
- zeroPadding = true;
- break;
- case '#':
- if (alternateForm) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
- alternateForm = true;
- break;
- case ' ':
- if (blank) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
- blank = true;
- break;
- default:
- goto PROCESS_WIDTH;
- }
- i++;
- }
- PROCESS_WIDTH:
- // Process width
- var start = i;
- if (char.IsDigit(format[i]))
- {
- i++;
- if (char.IsDigit(format[i])) i++;
- if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread, "invalid format (width or precision too long)");
- width = int.Parse(format.AsSpan()[start..i]);
- }
- // Process precision
- if (format[i] == '.')
- {
- i++;
- start = i;
- if (char.IsDigit(format[i])) i++;
- if (char.IsDigit(format[i])) i++;
- if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread, "invalid format (width or precision too long)");
- precision = int.Parse(format.AsSpan()[start..i]);
- }
- // Process conversion specifier
- var specifier = format[i];
- if (context.ArgumentCount <= parameterIndex)
- {
- throw new LuaRuntimeException(context.Thread, $"bad argument #{parameterIndex + 1} to 'format' (no value)");
- }
- var parameter = context.GetArgument(parameterIndex++);
- // TODO: reduce allocation
- string formattedValue = default!;
- switch (specifier)
- {
- case 'f':
- case 'e':
- case 'g':
- case 'G':
- if (!parameter.TryRead<double>(out var f))
- {
- LuaRuntimeException.BadArgument(context.Thread, parameterIndex + 1, LuaValueType.Number, parameter.Type);
- }
- switch (specifier)
- {
- case 'f':
- formattedValue = precision < 0
- ? f.ToString(CultureInfo.InvariantCulture)
- : f.ToString($"F{precision}", CultureInfo.InvariantCulture);
- break;
- case 'e':
- formattedValue = precision < 0
- ? f.ToString(CultureInfo.InvariantCulture)
- : f.ToString($"E{precision}", CultureInfo.InvariantCulture);
- break;
- case 'g':
- formattedValue = precision < 0
- ? f.ToString(CultureInfo.InvariantCulture)
- : f.ToString($"G{precision}", CultureInfo.InvariantCulture);
- break;
- case 'G':
- formattedValue = precision < 0
- ? f.ToString(CultureInfo.InvariantCulture).ToUpper()
- : f.ToString($"G{precision}", CultureInfo.InvariantCulture).ToUpper();
- break;
- }
- if (plusSign && f >= 0)
- {
- formattedValue = $"+{formattedValue}";
- }
- break;
- case 's':
- {
- await parameter.CallToStringAsync(context, cancellationToken);
- formattedValue = stack.Pop().Read<string>();
- }
- if (specifier is 's' && precision > 0 && precision <= formattedValue.Length)
- {
- formattedValue = formattedValue[..precision];
- }
- break;
- case 'q':
- switch (parameter.Type)
- {
- case LuaValueType.Nil:
- formattedValue = "nil";
- break;
- case LuaValueType.Boolean:
- formattedValue = parameter.Read<bool>() ? "true" : "false";
- break;
- case LuaValueType.String:
- formattedValue = $"\"{StringHelper.Escape(parameter.Read<string>())}\"";
- break;
- case LuaValueType.Number:
- formattedValue = DoubleToQFormat(parameter.Read<double>());
- static string DoubleToQFormat(double value)
- {
- if (MathEx.IsInteger(value))
- {
- return value.ToString(CultureInfo.InvariantCulture);
- }
- return HexConverter.FromDouble(value);
- }
- break;
- default:
- {
- var top = stack.Count;
- stack.Push(default);
- await parameter.CallToStringAsync(context with { ReturnFrameBase = top }, cancellationToken);
- formattedValue = stack.Pop().Read<string>();
- }
- break;
- }
- break;
- case 'i':
- case 'd':
- case 'u':
- case 'c':
- case 'x':
- case 'X':
- if (!parameter.TryRead<double>(out var x))
- {
- LuaRuntimeException.BadArgument(context.Thread, parameterIndex + 1, LuaValueType.Number, parameter.Type);
- }
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, parameterIndex + 1, x);
- switch (specifier)
- {
- case 'i':
- case 'd':
- {
- var integer = checked((long)x);
- formattedValue = precision < 0
- ? integer.ToString()
- : integer.ToString($"D{precision}");
- }
- break;
- case 'u':
- {
- var integer = checked((ulong)x);
- formattedValue = precision < 0
- ? integer.ToString()
- : integer.ToString($"D{precision}");
- }
- break;
- case 'c':
- formattedValue = ((char)(int)x).ToString();
- break;
- case 'x':
- {
- var integer = checked((ulong)x);
- formattedValue = alternateForm
- ? $"0x{integer:x}"
- : $"{integer:x}";
- }
- break;
- case 'X':
- {
- var integer = checked((ulong)x);
- formattedValue = alternateForm
- ? $"0X{integer:X}"
- : $"{integer:X}";
- }
- break;
- case 'o':
- {
- var integer = checked((long)x);
- formattedValue = Convert.ToString(integer, 8);
- }
- break;
- }
- if (plusSign && x >= 0)
- {
- formattedValue = $"+{formattedValue}";
- }
- break;
- default:
- throw new LuaRuntimeException(context.Thread, $"invalid option '%{specifier}' to 'format'");
- }
- // Apply blank (' ') flag for positive numbers
- if (specifier is 'd' or 'i' or 'f' or 'g' or 'G')
- {
- if (blank && !leftJustify && !zeroPadding && parameter.Read<double>() >= 0)
- {
- formattedValue = $" {formattedValue}";
- }
- }
- // Apply width and padding
- if (width > formattedValue.Length)
- {
- if (leftJustify)
- {
- formattedValue = formattedValue.PadRight(width);
- }
- else
- {
- formattedValue = zeroPadding ? formattedValue.PadLeft(width, '0') : formattedValue.PadLeft(width);
- }
- }
- builder.Append(formattedValue);
- }
- else
- {
- builder.Append(format[i]);
- }
- }
- return context.Return(builder.ToString());
- }
- public ValueTask<int> GMatch(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- var pattern = context.GetArgument<string>(1);
- return new(context.Return(new CSharpClosure("gmatch_iterator", [s, pattern, 0], static (context, cancellationToken) =>
- {
- var upValues = context.GetCsClosure()!.UpValues;
- var s = upValues[0].Read<string>();
- var pattern = upValues[1].Read<string>();
- var start = upValues[2].Read<int>();
- var matchState = new MatchState(context.Thread, s, pattern);
- var captures = matchState.Captures;
- // Check for anchor at start
- bool anchor = pattern.Length > 0 && pattern[0] == '^';
- int pIdx = anchor ? 1 : 0;
- // For empty patterns, we need to match at every position including after the last character
- var sEndIdx = s.Length + (pattern.Length == 0 || (anchor && pattern.Length == 1) ? 1 : 0);
- for (int sIdx = start; sIdx < sEndIdx; sIdx++)
- {
- // Reset match state for each attempt
- matchState.Level = 0;
- matchState.MatchDepth = MatchState.MaxCalls;
- // Clear captures to avoid stale data
- Array.Clear(captures, 0, captures.Length);
- var res = matchState.Match(sIdx, pIdx);
- if (res >= 0)
- {
- // If no captures were made, create one for the whole match
- if (matchState.Level == 0)
- {
- captures[0].Init = sIdx;
- captures[0].Len = res - sIdx;
- matchState.Level = 1;
- }
- var resultLength = matchState.Level;
- var buffer = context.GetReturnBuffer(resultLength);
- for (int i = 0; i < matchState.Level; i++)
- {
- var capture = captures[i];
- if (capture.IsPosition)
- {
- buffer[i] = capture.Init + 1; // 1-based position
- }
- else
- {
- buffer[i] = s.AsSpan(capture.Init, capture.Len).ToString();
- }
- }
- // Update start index for next iteration
- // Handle empty matches by advancing at least 1 position
- upValues[2] = res > sIdx ? res : sIdx + 1;
- return new(resultLength);
- }
- // For anchored patterns, only try once
- if (anchor) break;
- }
- return new(context.Return(LuaValue.Nil));
- })));
- }
- public async ValueTask<int> GSub(LuaFunctionExecutionContext context, 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)
- : s.Length + 1;
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 4, n_arg);
- var n = (int)n_arg;
- // Use MatchState instead of regex
- var matchState = new MatchState(context.Thread, s, pattern);
- var captures = matchState.Captures;
- var builder = new StringBuilder();
- StringBuilder? replacedBuilder = repl.Type == LuaValueType.String
- ? new StringBuilder(repl.UnsafeReadString().Length)
- : null;
- var lastIndex = 0;
- var replaceCount = 0;
- // Check for anchor at start
- bool anchor = pattern.Length > 0 && pattern[0] == '^';
- int sIdx = 0;
- // For empty patterns, we need to match at every position including after the last character
- var sEndIdx = s.Length + (pattern.Length == 0 || (anchor && pattern.Length == 1) ? 1 : 0);
- while ((sIdx < sEndIdx) && replaceCount < n)
- {
- // Reset match state for each attempt
- matchState.Level = 0;
- Debug.Assert(matchState.MatchDepth == MatchState.MaxCalls);
- // Clear captures array to avoid stale data
- for (int i = 0; i < captures.Length; i++)
- {
- captures[i] = default;
- }
- // Always start pattern from beginning (0 or 1 if anchored)
- int pIdx = anchor ? 1 : 0;
- var res = matchState.Match(sIdx, pIdx);
- if (res >= 0)
- {
- // Found a match
- builder.Append(s.AsSpan()[lastIndex..sIdx]);
- // If no captures were made, create one for the whole match
- if (matchState.Level == 0)
- {
- captures[0].Init = sIdx;
- captures[0].Len = res - sIdx;
- matchState.Level = 1;
- }
- LuaValue result;
- if (repl.TryRead<string>(out var str))
- {
- if (!str.Contains("%"))
- {
- result = str; // No special characters, use as is
- }
- else
- {
- // String replacement
- replacedBuilder!.Clear();
- replacedBuilder.Append(str);
- // Replace %% with %
- replacedBuilder.Replace("%%", "\0"); // Use null char as temporary marker
- // Replace %0 with whole match
- var wholeMatch = s.AsSpan(sIdx, res - sIdx).ToString();
- replacedBuilder.Replace("%0", wholeMatch);
- // Replace %1, %2, etc. with captures
- for (int k = 0; k < matchState.Level; k++)
- {
- var capture = captures[k];
- string captureText;
- if (capture.IsPosition)
- {
- captureText = (capture.Init + 1).ToString(); // 1-based position
- }
- else
- {
- captureText = s.AsSpan(capture.Init, capture.Len).ToString();
- }
- replacedBuilder.Replace($"%{k + 1}", captureText);
- }
- // Replace temporary marker back to %
- replacedBuilder.Replace('\0', '%');
- result = replacedBuilder.ToString();
- }
- }
- else if (repl.TryRead<LuaTable>(out var table))
- {
- // Table lookup - use first capture or whole match
- string key;
- if (matchState.Level > 0 && !captures[0].IsPosition)
- {
- key = s.AsSpan(captures[0].Init, captures[0].Len).ToString();
- }
- else
- {
- key = s.AsSpan(sIdx, res - sIdx).ToString();
- }
- result = table[key];
- }
- else if (repl.TryRead<LuaFunction>(out var func))
- {
- // Function call with captures as arguments
- var stack = context.Thread.Stack;
- if (matchState.Level == 0)
- {
- // No captures, pass whole match
- stack.Push(s.AsSpan(sIdx, res - sIdx).ToString());
- var retCount = await context.Access.RunAsync(func, 1, cancellationToken);
- using var results = context.Access.ReadTopValues(retCount);
- result = results.Count > 0 ? results[0] : LuaValue.Nil;
- }
- else
- {
- // Pass all captures
- for (int k = 0; k < matchState.Level; k++)
- {
- var capture = captures[k];
- if (capture.IsPosition)
- {
- stack.Push(capture.Init + 1); // 1-based position
- }
- else
- {
- stack.Push(s.AsSpan(capture.Init, capture.Len).ToString());
- }
- }
- var retCount = await context.Access.RunAsync(func, matchState.Level, cancellationToken);
- using var results = context.Access.ReadTopValues(retCount);
- result = results.Count > 0 ? results[0] : LuaValue.Nil;
- }
- }
- else
- {
- throw new LuaRuntimeException(context.Thread, "bad argument #3 to 'gsub' (string/function/table expected)");
- }
- // Handle replacement result
- 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())
- {
- // False or nil means don't replace
- builder.Append(s.AsSpan(sIdx, res - sIdx));
- }
- else
- {
- throw new LuaRuntimeException(context.Thread, $"invalid replacement value (a {result.Type})");
- }
- replaceCount++;
- lastIndex = res;
- // If empty match, advance by 1 to avoid infinite loop
- if (res == sIdx)
- {
- if (sIdx < s.Length)
- {
- builder.Append(s[sIdx]);
- lastIndex = sIdx + 1;
- }
- sIdx++;
- }
- else
- {
- sIdx = res;
- }
- }
- else
- {
- // No match at this position
- if (anchor)
- {
- // Anchored pattern only tries at start
- break;
- }
- sIdx++;
- }
- }
- // Append remaining part of string
- if (lastIndex < s.Length)
- {
- builder.Append(s.AsSpan()[lastIndex..]);
- }
- return context.Return(builder.ToString(), replaceCount);
- }
- public ValueTask<int> Len(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- return new(context.Return(s.Length));
- }
- public ValueTask<int> Lower(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- return new(context.Return(s.ToLower()));
- }
- public ValueTask<int> Match(LuaFunctionExecutionContext context, CancellationToken cancellationToken) =>
- FindAux(context, false);
- public ValueTask<int> FindAux(LuaFunctionExecutionContext context, bool find)
- {
- var s = context.GetArgument<string>(0);
- var pattern = context.GetArgument<string>(1);
- var init = context.HasArgument(2)
- ? context.GetArgument<int>(2)
- : 1;
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 3, init);
- // Convert to 0-based index
- if (init < 0)
- {
- init = s.Length + init + 1;
- }
- init--; // Convert from 1-based to 0-based
- // Check if init is beyond string bounds
- if (init > s.Length)
- {
- return new(context.Return(LuaValue.Nil));
- }
- init = Math.Max(0, init); // Clamp to 0 if negative
- // Check for plain search mode (4th parameter = true)
- if (find && context.GetArgumentOrDefault(3).ToBoolean())
- {
- return PlainSearch(context, s, pattern, init);
- }
- // Fast path for simple patterns without special characters
- if (find && MatchState.NoSpecials(pattern))
- {
- return SimplePatternSearch(context, s, pattern, init);
- }
- return PatternSearch(context, s, pattern, init, find);
- }
- private static ValueTask<int> PlainSearch(LuaFunctionExecutionContext context, string s, string pattern, int init)
- {
- var index = s.AsSpan(init).IndexOf(pattern);
- if (index == -1)
- {
- return new(context.Return(LuaValue.Nil));
- }
- var actualStart = init + index;
- return new(context.Return(actualStart + 1, actualStart + pattern.Length)); // Convert to 1-based
- }
- private static ValueTask<int> SimplePatternSearch(LuaFunctionExecutionContext context, string s, string pattern, int init)
- {
- var index = s.AsSpan(init).IndexOf(pattern);
- if (index == -1)
- {
- return new(context.Return(LuaValue.Nil));
- }
- var actualStart = init + index;
- return new(context.Return(actualStart + 1, actualStart + pattern.Length)); // Convert to 1-based
- }
- private static ValueTask<int> PatternSearch(LuaFunctionExecutionContext context, string s, string pattern, int init, bool find)
- {
- var matchState = new MatchState(context.Thread, s, pattern);
- var captures = matchState.Captures;
- // Check for anchor at start
- bool anchor = pattern.Length > 0 && pattern[0] == '^';
- int pIdx = anchor ? 1 : 0;
- // For empty patterns, we need to match at every position including after the last character
- var sEndIdx = s.Length + (pattern.Length == 0 ? 1 : 0);
- for (int sIdx = init; sIdx < sEndIdx; sIdx++)
- {
- // Reset match state for each attempt
- matchState.Level = 0;
- matchState.MatchDepth = MatchState.MaxCalls;
- Array.Clear(captures, 0, captures.Length);
- var res = matchState.Match(sIdx, pIdx);
- if (res >= 0)
- {
- // If no captures were made for string.match, create one for the whole match
- if (!find && matchState.Level == 0)
- {
- captures[0].Init = sIdx;
- captures[0].Len = res - sIdx;
- matchState.Level = 1;
- }
- var resultLength = matchState.Level + (find ? 2 : 0);
- var buffer = context.GetReturnBuffer(resultLength);
- if (find)
- {
- // Return start and end positions for string.find
- buffer[0] = sIdx + 1; // Convert to 1-based index
- buffer[1] = res; // Convert to 1-based index
- buffer = buffer[2..];
- }
- // Return captures
- for (int i = 0; i < matchState.Level; i++)
- {
- var capture = captures[i];
- if (capture.IsPosition)
- {
- buffer[i] = capture.Init + 1; // 1-based position
- }
- else
- {
- buffer[i] = s.AsSpan(capture.Init, capture.Len).ToString();
- }
- }
- return new(resultLength);
- }
- // For anchored patterns, only try once
- if (anchor) break;
- }
- return new(context.Return(LuaValue.Nil));
- }
- public ValueTask<int> Rep(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- var n_arg = context.GetArgument<double>(1);
- var sep = context.HasArgument(2)
- ? context.GetArgument<string>(2)
- : null;
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 2, n_arg);
- var n = (int)n_arg;
- var builder = new ValueStringBuilder(s.Length * n);
- for (int i = 0; i < n; i++)
- {
- builder.Append(s);
- if (i != n - 1 && sep != null)
- {
- builder.Append(sep);
- }
- }
- return new(context.Return(builder.ToString()));
- }
- public ValueTask<int> Reverse(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- using var strBuffer = new PooledArray<char>(s.Length);
- var span = strBuffer.AsSpan()[..s.Length];
- s.AsSpan().CopyTo(span);
- span.Reverse();
- return new(context.Return(span.ToString()));
- }
- public ValueTask<int> Sub(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- var i = context.GetArgument<double>(1);
- var j = context.HasArgument(2)
- ? context.GetArgument<double>(2)
- : -1;
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 2, i);
- LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, 3, j);
- return new(context.Return(StringHelper.Slice(s, (int)i, (int)j).ToString()));
- }
- public ValueTask<int> Upper(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
- {
- var s = context.GetArgument<string>(0);
- return new(context.Return(s.ToUpper()));
- }
- }
|