diff --git a/Jint/Extensions/Character.cs b/Jint/Extensions/Character.cs
index d845fe9be..4d65aa1b4 100644
--- a/Jint/Extensions/Character.cs
+++ b/Jint/Extensions/Character.cs
@@ -6,11 +6,13 @@ namespace Jint.Extensions;
internal static class Character
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsInRange(this char c, ushort min, ushort max)
- {
- Debug.Assert(min <= max);
- return c - (uint) min <= max - (uint) min;
- }
+ public static bool IsInRange(this char c, ushort min, ushort max) => (uint)(c - min) <= (uint)(max - min);
+
+ ///
+ /// https://tc39.es/ecma262/#ASCII-word-characters
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsAsciiWordCharacter(this char c) => c == '_' || c.IsDecimalDigit() || c.IsInRange('a', 'z') || c.IsInRange('A', 'Z');
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOctalDigit(this char c) => c.IsInRange('0', '7');
diff --git a/Jint/Native/Global/GlobalObject.cs b/Jint/Native/Global/GlobalObject.cs
index ae0859ae7..d64b5c84c 100644
--- a/Jint/Native/Global/GlobalObject.cs
+++ b/Jint/Native/Global/GlobalObject.cs
@@ -1,6 +1,5 @@
using System.Buffers;
using System.Globalization;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Jint.Extensions;
@@ -274,7 +273,7 @@ private static JsValue IsFinite(JsValue thisObject, JsValue[] arguments)
}
private const string UriReservedString = ";/?:@&=+$,";
- private const string UriUnescapedString = "-_.!~*'()";
+ private const string UriUnescapedString = "-.!~*'()";
private static readonly SearchValues UriUnescaped = SearchValues.Create(UriUnescapedString);
private static readonly SearchValues UnescapedUriSet = SearchValues.Create(UriReservedString + UriUnescapedString + '#');
private static readonly SearchValues ReservedUriSet = SearchValues.Create(UriReservedString + '#');
@@ -303,20 +302,16 @@ private JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments)
private JsValue Encode(string uriString, SearchValues unescapedUriSet)
{
- const string HexaMap = "0123456789ABCDEF";
-
var strLen = uriString.Length;
-
- _stringBuilder.EnsureCapacity(uriString.Length);
- _stringBuilder.Clear();
+ var builder = new ValueStringBuilder(uriString.Length);
Span buffer = stackalloc byte[4];
for (var k = 0; k < strLen; k++)
{
var c = uriString[k];
- if (c is >= 'a' and <= 'z' || c is >= 'A' and <= 'Z' || c is >= '0' and <= '9' || unescapedUriSet.Contains(c))
+ if (c.IsAsciiWordCharacter() || unescapedUriSet.Contains(c))
{
- _stringBuilder.Append(c);
+ builder.Append(c);
}
else
{
@@ -386,15 +381,13 @@ private JsValue Encode(string uriString, SearchValues unescapedUriSet)
for (var i = 0; i < length; i++)
{
- var octet = buffer[i];
- var x1 = HexaMap[octet / 16];
- var x2 = HexaMap[octet % 16];
- _stringBuilder.Append('%').Append(x1).Append(x2);
+ builder.Append('%');
+ builder.AppendHex(buffer[i]);
}
}
}
- return _stringBuilder.ToString();
+ return builder.ToString();
uriError:
_engine.SignalError(ExceptionHelper.CreateUriError(_realm, "URI malformed"));
@@ -583,10 +576,8 @@ private static bool IsDigit(char c, int radix, out int result)
return tmp < radix;
}
- private static readonly SearchValues EscapeAllowList = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./");
-
///
- /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.1
+ /// https://tc39.es/ecma262/#sec-escape-string
///
private JsValue Escape(JsValue thisObject, JsValue[] arguments)
{
@@ -600,7 +591,7 @@ private JsValue Escape(JsValue thisObject, JsValue[] arguments)
for (var k = 0; k < strLen; k++)
{
var c = uriString[k];
- if (EscapeAllowList.Contains(c))
+ if (c.IsAsciiWordCharacter() || c == '@' || c == '*' || c == '+' || c == '-' || c == '.' || c == '/')
{
_stringBuilder.Append(c);
}
@@ -636,19 +627,14 @@ private JsValue Unescape(JsValue thisObject, JsValue[] arguments)
{
if (k <= strLen - 6
&& uriString[k + 1] == 'u'
- && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar))
+ && AreValidHexChars(uriString.AsSpan(k + 2, 4)))
{
- var joined = string.Join(string.Empty, uriString.Skip(k + 2).Take(4));
- c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
-
+ c = ParseHexString(uriString.AsSpan(k + 2, 4));
k += 5;
}
- else if (k <= strLen - 3
- && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar))
+ else if (k <= strLen - 3 && AreValidHexChars(uriString.AsSpan(k + 1, 2)))
{
- var joined = string.Join(string.Empty, uriString.Skip(k + 1).Take(2));
- c = (char) int.Parse(joined, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
-
+ c = ParseHexString(uriString.AsSpan(k + 1, 2));
k += 2;
}
}
@@ -656,6 +642,30 @@ private JsValue Unescape(JsValue thisObject, JsValue[] arguments)
}
return _stringBuilder.ToString();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static bool AreValidHexChars(ReadOnlySpan input)
+ {
+ foreach (var c in input)
+ {
+ if (!IsValidHexaChar(c))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static char ParseHexString(ReadOnlySpan input)
+ {
+#if NET6_0_OR_GREATER
+ return (char) int.Parse(input, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+#else
+ return (char) int.Parse(input.ToString(), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+#endif
+ }
}
// optimized versions with string parameter and without virtual dispatch for global environment usage
diff --git a/Jint/Native/Json/JsonSerializer.cs b/Jint/Native/Json/JsonSerializer.cs
index ca7c41369..d9d7e28fd 100644
--- a/Jint/Native/Json/JsonSerializer.cs
+++ b/Jint/Native/Json/JsonSerializer.cs
@@ -184,7 +184,7 @@ private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref V
if (value.IsInteger())
{
- json.Append(((long) doubleValue).ToString(CultureInfo.InvariantCulture));
+ json.Append((long) doubleValue);
return SerializeResult.NotUndefined;
}
@@ -193,7 +193,7 @@ private SerializeResult SerializeJSONProperty(JsValue key, JsValue holder, ref V
{
if (TypeConverter.CanBeStringifiedAsLong(doubleValue))
{
- json.Append(((long) doubleValue).ToString(CultureInfo.InvariantCulture));
+ json.Append((long) doubleValue);
return SerializeResult.NotUndefined;
}
diff --git a/Jint/Native/Number/NumberPrototype.cs b/Jint/Native/Number/NumberPrototype.cs
index 8697b60c6..43d9d84d0 100644
--- a/Jint/Native/Number/NumberPrototype.cs
+++ b/Jint/Native/Number/NumberPrototype.cs
@@ -353,7 +353,7 @@ private static string CreateExponentialRepresentation(
sb.Append('e');
sb.Append(negativeExponent ? '-' : '+');
- sb.Append(exponent.ToString(CultureInfo.InvariantCulture));
+ sb.Append(exponent);
return sb.ToString();
}
@@ -528,7 +528,7 @@ internal static string ToNumberString(double m)
exponent = -exponent;
}
- stringBuilder.Append(exponent.ToString(CultureInfo.InvariantCulture));
+ stringBuilder.Append(exponent);
}
return stringBuilder.ToString();
diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs
index fe8343912..c1a13ec06 100644
--- a/Jint/Native/String/StringPrototype.cs
+++ b/Jint/Native/String/StringPrototype.cs
@@ -700,11 +700,7 @@ static int StringIndexOf(string s, string search, int fromIndex)
if (endOfLastMatch < thisString.Length)
{
-#if NETFRAMEWORK
result.Append(thisString.AsSpan(endOfLastMatch));
-#else
- result.Append(thisString[endOfLastMatch..]);
-#endif
}
return result.ToString();
diff --git a/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs
index 08ad54437..64e98a2be 100644
--- a/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs
+++ b/Jint/Native/TypedArray/TypedArrayConstructor.Uint8Array.cs
@@ -92,8 +92,6 @@ internal static JsString GetAndValidateAlphabetOption(Engine engine, ObjectInsta
internal readonly record struct FromEncodingResult(byte[] Bytes, JavaScriptException? Error, int Read);
- private static readonly SearchValues Base64Alphabet = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
-
internal static FromEncodingResult FromBase64(Engine engine, string input, string alphabet, string lastChunkHandling, uint maxLength = uint.MaxValue)
{
if (maxLength == 0)
@@ -202,7 +200,10 @@ internal static FromEncodingResult FromBase64(Engine engine, string input, strin
}
}
- if (!Base64Alphabet.Contains(currentChar))
+ if (!currentChar.IsDecimalDigit()
+ && !char.ToLowerInvariant(currentChar).IsInRange('a', 'z')
+ && currentChar != '+'
+ && currentChar != '/')
{
return new FromEncodingResult(bytes.ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid base64 character."), read);
}
@@ -308,8 +309,6 @@ private JsTypedArray FromHex(JsValue thisObject, JsValue[] arguments)
return ta;
}
- private static readonly SearchValues HexAlphabet = SearchValues.Create("0123456789abcdefABCDEF");
-
internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLength = int.MaxValue)
{
var length = s.Length;
@@ -325,7 +324,7 @@ internal static FromEncodingResult FromHex(Engine engine, string s, uint maxLeng
while (read < length && byteIndex < maxLength)
{
var hexits = s.AsSpan(read, 2);
- if (!HexAlphabet.Contains(hexits[0]) || !HexAlphabet.Contains(hexits[1]))
+ if (!hexits[0].IsHexDigit() || !hexits[1].IsHexDigit())
{
return new FromEncodingResult(bytes.AsSpan(0, byteIndex).ToArray(), ExceptionHelper.CreateSyntaxError(engine.Realm, "Invalid hex value"), read);
}
diff --git a/Jint/Pooling/ValueStringBuilder.cs b/Jint/Pooling/ValueStringBuilder.cs
index f5bd67932..4252c3518 100644
--- a/Jint/Pooling/ValueStringBuilder.cs
+++ b/Jint/Pooling/ValueStringBuilder.cs
@@ -188,6 +188,18 @@ public void Append(char c)
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AppendHex(byte b)
+ {
+ const string Map = "0123456789ABCDEF";
+ Span data = stackalloc char[]
+ {
+ Map[b / 16],
+ Map[b % 16],
+ };
+ Append(data);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string? s)
{
@@ -208,6 +220,33 @@ public void Append(string? s)
}
}
+#if NET6_0_OR_GREATER
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(T value) where T : ISpanFormattable
+ {
+ if (value.TryFormat(_chars.Slice(_pos), out var charsWritten, format: default, provider: null))
+ {
+ _pos += charsWritten;
+ }
+ else
+ {
+ Append(value.ToString());
+ }
+ }
+#else
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(int value)
+ {
+ Append(value.ToString(System.Globalization.CultureInfo.InvariantCulture));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(long value)
+ {
+ Append(value.ToString(System.Globalization.CultureInfo.InvariantCulture));
+ }
+#endif
+
private void AppendSlow(string s)
{
int pos = _pos;
diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs
index 087ed0074..b52fedaa8 100644
--- a/Jint/Runtime/CallStack/JintCallStack.cs
+++ b/Jint/Runtime/CallStack/JintCallStack.cs
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
using System.Linq;
using System.Text;
using Jint.Collections;
@@ -151,9 +150,9 @@ static void AppendLocation(
sb.Append(' ');
sb.Append(loc.SourceFile);
sb.Append(':');
- sb.Append(loc.End.Line.ToString(CultureInfo.InvariantCulture));
+ sb.Append(loc.End.Line);
sb.Append(':');
- sb.Append((loc.Start.Column + 1).ToString(CultureInfo.InvariantCulture)); // report column number instead of index
+ sb.Append(loc.Start.Column + 1); // report column number instead of index
sb.Append(System.Environment.NewLine);
}
diff --git a/Jint/Runtime/Interop/DefaultTypeConverter.cs b/Jint/Runtime/Interop/DefaultTypeConverter.cs
index 48b8ae53c..5cbcec842 100644
--- a/Jint/Runtime/Interop/DefaultTypeConverter.cs
+++ b/Jint/Runtime/Interop/DefaultTypeConverter.cs
@@ -201,7 +201,7 @@ private bool TryConvert(
foreach (var constructor in constructors)
{
var parameterInfos = constructor.GetParameters();
- if (parameterInfos.All(static p => p.IsOptional) && constructor.IsPublic)
+ if (Array.TrueForAll(parameterInfos, static p => p.IsOptional) && constructor.IsPublic)
{
constructorParameters = new object[parameterInfos.Length];
found = true;