progress
This commit is contained in:
parent
16e76d6b31
commit
484dbfc9d9
529 changed files with 113694 additions and 0 deletions
348
AspClassic.Scripting/Utils/ArrayUtils.cs
Normal file
348
AspClassic.Scripting/Utils/ArrayUtils.cs
Normal file
|
@ -0,0 +1,348 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class ArrayUtils
|
||||
{
|
||||
internal sealed class FunctorComparer<T> : IComparer<T>
|
||||
{
|
||||
private readonly Comparison<T> _comparison;
|
||||
|
||||
public FunctorComparer(Comparison<T> comparison)
|
||||
{
|
||||
_comparison = comparison;
|
||||
}
|
||||
|
||||
public int Compare(T x, T y)
|
||||
{
|
||||
return _comparison(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly string[] EmptyStrings = new string[0];
|
||||
|
||||
public static readonly object[] EmptyObjects = new object[0];
|
||||
|
||||
public static IComparer<T> ToComparer<T>(Comparison<T> comparison)
|
||||
{
|
||||
return new FunctorComparer<T>(comparison);
|
||||
}
|
||||
|
||||
public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] input, Converter<TInput, TOutput> conv)
|
||||
{
|
||||
return Array.ConvertAll(input, conv);
|
||||
}
|
||||
|
||||
public static T[] FindAll<T>(T[] array, Predicate<T> match)
|
||||
{
|
||||
return Array.FindAll(array, match);
|
||||
}
|
||||
|
||||
public static void PrintTable(StringBuilder output, string[,] table)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(output, "output");
|
||||
ContractUtils.RequiresNotNull(table, "table");
|
||||
int num = 0;
|
||||
for (int i = 0; i < table.GetLength(0); i++)
|
||||
{
|
||||
if (table[i, 0].Length > num)
|
||||
{
|
||||
num = table[i, 0].Length;
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < table.GetLength(0); j++)
|
||||
{
|
||||
output.Append(" ");
|
||||
output.Append(table[j, 0]);
|
||||
for (int k = table[j, 0].Length; k < num + 1; k++)
|
||||
{
|
||||
output.Append(' ');
|
||||
}
|
||||
output.AppendLine(table[j, 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public static T[] Copy<T>(T[] array)
|
||||
{
|
||||
if (array.Length <= 0)
|
||||
{
|
||||
return array;
|
||||
}
|
||||
return (T[])array.Clone();
|
||||
}
|
||||
|
||||
public static T[] MakeArray<T>(ICollection<T> list)
|
||||
{
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return new T[0];
|
||||
}
|
||||
T[] array = new T[list.Count];
|
||||
list.CopyTo(array, 0);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] MakeArray<T>(ICollection<T> elements, int reservedSlotsBefore, int reservedSlotsAfter)
|
||||
{
|
||||
if (reservedSlotsAfter < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("reservedSlotsAfter");
|
||||
}
|
||||
if (reservedSlotsBefore < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("reservedSlotsBefore");
|
||||
}
|
||||
if (elements == null)
|
||||
{
|
||||
return new T[reservedSlotsBefore + reservedSlotsAfter];
|
||||
}
|
||||
T[] array = new T[reservedSlotsBefore + elements.Count + reservedSlotsAfter];
|
||||
elements.CopyTo(array, reservedSlotsBefore);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] RotateRight<T>(T[] array, int count)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
if (count < 0 || count > array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
}
|
||||
T[] array2 = new T[array.Length];
|
||||
int num = array.Length - count;
|
||||
Array.Copy(array, 0, array2, count, num);
|
||||
Array.Copy(array, num, array2, 0, count);
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] ShiftRight<T>(T[] array, int count)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
}
|
||||
T[] array2 = new T[array.Length + count];
|
||||
Array.Copy(array, 0, array2, count, array.Length);
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] ShiftLeft<T>(T[] array, int count)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
}
|
||||
T[] array2 = new T[array.Length - count];
|
||||
Array.Copy(array, count, array2, 0, array2.Length);
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] Insert<T>(T item, IList<T> list)
|
||||
{
|
||||
T[] array = new T[list.Count + 1];
|
||||
array[0] = item;
|
||||
list.CopyTo(array, 1);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] Insert<T>(T item1, T item2, IList<T> list)
|
||||
{
|
||||
T[] array = new T[list.Count + 2];
|
||||
array[0] = item1;
|
||||
array[1] = item2;
|
||||
list.CopyTo(array, 2);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] Insert<T>(T item, T[] array)
|
||||
{
|
||||
T[] array2 = ShiftRight(array, 1);
|
||||
array2[0] = item;
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] Insert<T>(T item1, T item2, T[] array)
|
||||
{
|
||||
T[] array2 = ShiftRight(array, 2);
|
||||
array2[0] = item1;
|
||||
array2[1] = item2;
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] Append<T>(T[] array, T item)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
Array.Resize(ref array, array.Length + 1);
|
||||
array[array.Length - 1] = item;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] AppendRange<T>(T[] array, IList<T> items)
|
||||
{
|
||||
return AppendRange(array, items, 0);
|
||||
}
|
||||
|
||||
public static T[] AppendRange<T>(T[] array, IList<T> items, int additionalItemCount)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
if (additionalItemCount < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("additionalItemCount");
|
||||
}
|
||||
int num = array.Length;
|
||||
Array.Resize(ref array, array.Length + items.Count + additionalItemCount);
|
||||
int num2 = 0;
|
||||
while (num2 < items.Count)
|
||||
{
|
||||
array[num] = items[num2];
|
||||
num2++;
|
||||
num++;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[,] Concatenate<T>(T[,] array1, T[,] array2)
|
||||
{
|
||||
int length = array1.GetLength(1);
|
||||
int length2 = array1.GetLength(0);
|
||||
int length3 = array2.GetLength(0);
|
||||
int num = length2 + length3;
|
||||
T[,] array3 = new T[num, length];
|
||||
for (int i = 0; i < length2; i++)
|
||||
{
|
||||
for (int j = 0; j < length; j++)
|
||||
{
|
||||
array3[i, j] = array1[i, j];
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < length3; k++)
|
||||
{
|
||||
for (int l = 0; l < length; l++)
|
||||
{
|
||||
array3[k + length2, l] = array2[k, l];
|
||||
}
|
||||
}
|
||||
return array3;
|
||||
}
|
||||
|
||||
public static void SwapLastTwo<T>(T[] array)
|
||||
{
|
||||
T val = array[array.Length - 1];
|
||||
array[array.Length - 1] = array[array.Length - 2];
|
||||
array[array.Length - 2] = val;
|
||||
}
|
||||
|
||||
public static T[] RemoveFirst<T>(IList<T> list)
|
||||
{
|
||||
return ShiftLeft(MakeArray(list), 1);
|
||||
}
|
||||
|
||||
public static T[] RemoveFirst<T>(T[] array)
|
||||
{
|
||||
return ShiftLeft(array, 1);
|
||||
}
|
||||
|
||||
public static T[] RemoveLast<T>(T[] array)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
Array.Resize(ref array, array.Length - 1);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static T[] RemoveAt<T>(IList<T> list, int indexToRemove)
|
||||
{
|
||||
return RemoveAt(MakeArray(list), indexToRemove);
|
||||
}
|
||||
|
||||
public static T[] RemoveAt<T>(T[] array, int indexToRemove)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
ContractUtils.Requires(indexToRemove >= 0 && indexToRemove < array.Length, "index");
|
||||
T[] array2 = new T[array.Length - 1];
|
||||
if (indexToRemove > 0)
|
||||
{
|
||||
Array.Copy(array, 0, array2, 0, indexToRemove);
|
||||
}
|
||||
int num = array.Length - indexToRemove - 1;
|
||||
if (num > 0)
|
||||
{
|
||||
Array.Copy(array, array.Length - num, array2, array2.Length - num, num);
|
||||
}
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] InsertAt<T>(IList<T> list, int index, params T[] items)
|
||||
{
|
||||
return InsertAt(MakeArray(list), index, items);
|
||||
}
|
||||
|
||||
public static T[] InsertAt<T>(T[] array, int index, params T[] items)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(array, "array");
|
||||
ContractUtils.RequiresNotNull(items, "items");
|
||||
ContractUtils.Requires(index >= 0 && index <= array.Length, "index");
|
||||
if (items.Length == 0)
|
||||
{
|
||||
return Copy(array);
|
||||
}
|
||||
T[] array2 = new T[array.Length + items.Length];
|
||||
if (index > 0)
|
||||
{
|
||||
Array.Copy(array, 0, array2, 0, index);
|
||||
}
|
||||
Array.Copy(items, 0, array2, index, items.Length);
|
||||
int num = array.Length - index;
|
||||
if (num > 0)
|
||||
{
|
||||
Array.Copy(array, array.Length - num, array2, array2.Length - num, num);
|
||||
}
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static T[] ToArray<T>(ICollection<T> list)
|
||||
{
|
||||
if (!(list is T[] result))
|
||||
{
|
||||
T[] array = new T[list.Count];
|
||||
int num = 0;
|
||||
{
|
||||
foreach (T item in list)
|
||||
{
|
||||
array[num++] = item;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool ValueEquals<T>(this T[] array, T[] other)
|
||||
{
|
||||
if (other.Length != array.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (!object.Equals(array[i], other[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static T[] Reverse<T>(this T[] array)
|
||||
{
|
||||
T[] array2 = new T[array.Length];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
array2[array.Length - i - 1] = array[i];
|
||||
}
|
||||
return array2;
|
||||
}
|
||||
}
|
100
AspClassic.Scripting/Utils/AssemblyQualifiedTypeName.cs
Normal file
100
AspClassic.Scripting/Utils/AssemblyQualifiedTypeName.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
[Serializable]
|
||||
internal struct AssemblyQualifiedTypeName : IEquatable<AssemblyQualifiedTypeName>
|
||||
{
|
||||
public readonly string TypeName;
|
||||
|
||||
public readonly AssemblyName AssemblyName;
|
||||
|
||||
public AssemblyQualifiedTypeName(string typeName, AssemblyName assemblyName)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(typeName, "typeName");
|
||||
ContractUtils.RequiresNotNull(assemblyName, "assemblyName");
|
||||
TypeName = typeName;
|
||||
AssemblyName = assemblyName;
|
||||
}
|
||||
|
||||
public AssemblyQualifiedTypeName(Type type)
|
||||
{
|
||||
TypeName = type.FullName;
|
||||
AssemblyName = type.Assembly.GetName();
|
||||
}
|
||||
|
||||
public AssemblyQualifiedTypeName(string assemblyQualifiedTypeName)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(assemblyQualifiedTypeName, "assemblyQualifiedTypeName");
|
||||
int num = assemblyQualifiedTypeName.IndexOf(",");
|
||||
if (num != -1)
|
||||
{
|
||||
TypeName = assemblyQualifiedTypeName.Substring(0, num).Trim();
|
||||
string text = assemblyQualifiedTypeName.Substring(num + 1).Trim();
|
||||
if (TypeName.Length > 0 && text.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyName = new AssemblyName(text);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException($"Invalid assembly qualified name '{assemblyQualifiedTypeName}': {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ArgumentException($"Invalid assembly qualified name '{assemblyQualifiedTypeName}'");
|
||||
}
|
||||
|
||||
internal static AssemblyQualifiedTypeName ParseArgument(string str, string argumentName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new AssemblyQualifiedTypeName(str);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
throw new ArgumentException(ex.Message, argumentName, ex.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(AssemblyQualifiedTypeName other)
|
||||
{
|
||||
if (TypeName == other.TypeName)
|
||||
{
|
||||
return AssemblyName.FullName == other.AssemblyName.FullName;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is AssemblyQualifiedTypeName)
|
||||
{
|
||||
return Equals((AssemblyQualifiedTypeName)obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return TypeName.GetHashCode() ^ AssemblyName.FullName.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return TypeName + ", " + AssemblyName.FullName;
|
||||
}
|
||||
|
||||
public static bool operator ==(AssemblyQualifiedTypeName name, AssemblyQualifiedTypeName other)
|
||||
{
|
||||
return name.Equals(other);
|
||||
}
|
||||
|
||||
public static bool operator !=(AssemblyQualifiedTypeName name, AssemblyQualifiedTypeName other)
|
||||
{
|
||||
return !name.Equals(other);
|
||||
}
|
||||
}
|
42
AspClassic.Scripting/Utils/Assert.cs
Normal file
42
AspClassic.Scripting/Utils/Assert.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
#define DEBUG
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class Assert
|
||||
{
|
||||
[Conditional("DEBUG")]
|
||||
public static void NotNull(object var)
|
||||
{
|
||||
Debug.Assert(var != null);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void NotNull(object var1, object var2)
|
||||
{
|
||||
Debug.Assert(var1 != null && var2 != null);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void NotNull(object var1, object var2, object var3)
|
||||
{
|
||||
Debug.Assert(var1 != null && var2 != null && var3 != null);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void NotNullItems<T>(IEnumerable<T> items) where T : class
|
||||
{
|
||||
Debug.Assert(items != null);
|
||||
foreach (T item in items)
|
||||
{
|
||||
Debug.Assert(item != null);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void NotEmpty(string str)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(str));
|
||||
}
|
||||
}
|
97
AspClassic.Scripting/Utils/CheckedDictionaryEnumerator.cs
Normal file
97
AspClassic.Scripting/Utils/CheckedDictionaryEnumerator.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal abstract class CheckedDictionaryEnumerator : IDictionaryEnumerator, IEnumerator<KeyValuePair<object, object>>, IDisposable, IEnumerator
|
||||
{
|
||||
private enum EnumeratorState
|
||||
{
|
||||
NotStarted,
|
||||
Started,
|
||||
Ended
|
||||
}
|
||||
|
||||
private EnumeratorState _enumeratorState;
|
||||
|
||||
public DictionaryEntry Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckEnumeratorState();
|
||||
return new DictionaryEntry(Key, Value);
|
||||
}
|
||||
}
|
||||
|
||||
public object Key
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckEnumeratorState();
|
||||
return GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckEnumeratorState();
|
||||
return GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
public object Current => Entry;
|
||||
|
||||
KeyValuePair<object, object> IEnumerator<KeyValuePair<object, object>>.Current => new KeyValuePair<object, object>(Key, Value);
|
||||
|
||||
private void CheckEnumeratorState()
|
||||
{
|
||||
if (_enumeratorState == EnumeratorState.NotStarted)
|
||||
{
|
||||
throw Error.EnumerationNotStarted();
|
||||
}
|
||||
if (_enumeratorState == EnumeratorState.Ended)
|
||||
{
|
||||
throw Error.EnumerationFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_enumeratorState == EnumeratorState.Ended)
|
||||
{
|
||||
throw Error.EnumerationFinished();
|
||||
}
|
||||
bool flag = DoMoveNext();
|
||||
if (flag)
|
||||
{
|
||||
_enumeratorState = EnumeratorState.Started;
|
||||
}
|
||||
else
|
||||
{
|
||||
_enumeratorState = EnumeratorState.Ended;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
DoReset();
|
||||
_enumeratorState = EnumeratorState.NotStarted;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected abstract object GetKey();
|
||||
|
||||
protected abstract object GetValue();
|
||||
|
||||
protected abstract bool DoMoveNext();
|
||||
|
||||
protected abstract void DoReset();
|
||||
}
|
92
AspClassic.Scripting/Utils/CollectionExtensions.cs
Normal file
92
AspClassic.Scripting/Utils/CollectionExtensions.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class CollectionExtensions
|
||||
{
|
||||
internal static ReadOnlyCollection<T> ToReadOnly<T>(this IEnumerable<T> enumerable)
|
||||
{
|
||||
if (enumerable == null)
|
||||
{
|
||||
return EmptyReadOnlyCollection<T>.Instance;
|
||||
}
|
||||
if (enumerable is ReadOnlyCollection<T> result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (enumerable is ICollection<T> collection)
|
||||
{
|
||||
int count = collection.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
return EmptyReadOnlyCollection<T>.Instance;
|
||||
}
|
||||
T[] array = new T[count];
|
||||
collection.CopyTo(array, 0);
|
||||
return new ReadOnlyCollection<T>(array);
|
||||
}
|
||||
return new ReadOnlyCollection<T>(new List<T>(enumerable).ToArray());
|
||||
}
|
||||
|
||||
internal static T[] ToArray<T>(this IEnumerable<T> enumerable)
|
||||
{
|
||||
if (enumerable is ICollection<T> collection)
|
||||
{
|
||||
T[] array = new T[collection.Count];
|
||||
collection.CopyTo(array, 0);
|
||||
return array;
|
||||
}
|
||||
return new List<T>(enumerable).ToArray();
|
||||
}
|
||||
|
||||
internal static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
||||
{
|
||||
foreach (T item in source)
|
||||
{
|
||||
if (predicate(item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
|
||||
{
|
||||
using IEnumerator<TSource> enumerator = source.GetEnumerator();
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
throw new ArgumentException("Collection is empty", "source");
|
||||
}
|
||||
TSource val = enumerator.Current;
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
val = func(val, enumerator.Current);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
internal static T[] AddFirst<T>(this IList<T> list, T item)
|
||||
{
|
||||
T[] array = new T[list.Count + 1];
|
||||
array[0] = item;
|
||||
list.CopyTo(array, 1);
|
||||
return array;
|
||||
}
|
||||
|
||||
internal static bool TrueForAll<T>(this IEnumerable<T> collection, Predicate<T> predicate)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(collection, "collection");
|
||||
ContractUtils.RequiresNotNull(predicate, "predicate");
|
||||
foreach (T item in collection)
|
||||
{
|
||||
if (!predicate(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
107
AspClassic.Scripting/Utils/ConsoleInputStream.cs
Normal file
107
AspClassic.Scripting/Utils/ConsoleInputStream.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
public sealed class ConsoleInputStream : Stream
|
||||
{
|
||||
private const int MinimalBufferSize = 4096;
|
||||
|
||||
public static readonly ConsoleInputStream Instance = new ConsoleInputStream();
|
||||
|
||||
private readonly Stream _input;
|
||||
|
||||
private readonly object _lock = new object();
|
||||
|
||||
private readonly byte[] _buffer = new byte[4096];
|
||||
|
||||
private int _bufferPos;
|
||||
|
||||
private int _bufferSize;
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private ConsoleInputStream()
|
||||
{
|
||||
_input = Console.OpenStandardInput();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int num;
|
||||
if (_bufferSize > 0)
|
||||
{
|
||||
num = Math.Min(count, _bufferSize);
|
||||
Buffer.BlockCopy(_buffer, _bufferPos, buffer, offset, num);
|
||||
_bufferPos += num;
|
||||
_bufferSize -= num;
|
||||
offset += num;
|
||||
count -= num;
|
||||
}
|
||||
else
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
if (count < 4096)
|
||||
{
|
||||
int num2 = _input.Read(_buffer, 0, 4096);
|
||||
int num3 = Math.Min(num2, count);
|
||||
Buffer.BlockCopy(_buffer, 0, buffer, offset, num3);
|
||||
_bufferSize = num2 - num3;
|
||||
_bufferPos = num3;
|
||||
return num + num3;
|
||||
}
|
||||
return num + _input.Read(buffer, offset, count);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
8
AspClassic.Scripting/Utils/ConsoleStreamType.cs
Normal file
8
AspClassic.Scripting/Utils/ConsoleStreamType.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
public enum ConsoleStreamType
|
||||
{
|
||||
Input,
|
||||
Output,
|
||||
ErrorOutput
|
||||
}
|
113
AspClassic.Scripting/Utils/ContractUtils.cs
Normal file
113
AspClassic.Scripting/Utils/ContractUtils.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class ContractUtils
|
||||
{
|
||||
public static void RequiresNotNull(object value, string paramName)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Requires(bool precondition)
|
||||
{
|
||||
if (!precondition)
|
||||
{
|
||||
throw new ArgumentException(Strings.MethodPreconditionViolated);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Requires(bool precondition, string paramName)
|
||||
{
|
||||
if (!precondition)
|
||||
{
|
||||
throw new ArgumentException(Strings.InvalidArgumentValue, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Requires(bool precondition, string paramName, string message)
|
||||
{
|
||||
if (!precondition)
|
||||
{
|
||||
throw new ArgumentException(message, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresNotEmpty(string str, string paramName)
|
||||
{
|
||||
RequiresNotNull(str, paramName);
|
||||
if (str.Length == 0)
|
||||
{
|
||||
throw new ArgumentException(Strings.NonEmptyStringRequired, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresNotEmpty<T>(ICollection<T> collection, string paramName)
|
||||
{
|
||||
RequiresNotNull(collection, paramName);
|
||||
if (collection.Count == 0)
|
||||
{
|
||||
throw new ArgumentException(Strings.NonEmptyCollectionRequired, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresArrayRange<T>(IList<T> array, int offset, int count, string offsetName, string countName)
|
||||
{
|
||||
RequiresArrayRange(array.Count, offset, count, offsetName, countName);
|
||||
}
|
||||
|
||||
public static void RequiresArrayRange(int arraySize, int offset, int count, string offsetName, string countName)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(countName);
|
||||
}
|
||||
if (offset < 0 || arraySize - offset < count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(offsetName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresNotNullItems<T>(IList<T> array, string arrayName)
|
||||
{
|
||||
RequiresNotNull(array, arrayName);
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
if (array[i] == null)
|
||||
{
|
||||
throw ExceptionUtils.MakeArgumentItemNullException(i, arrayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresNotNullItems<T>(IEnumerable<T> collection, string collectionName)
|
||||
{
|
||||
RequiresNotNull(collection, collectionName);
|
||||
int num = 0;
|
||||
foreach (T item in collection)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw ExceptionUtils.MakeArgumentItemNullException(num, collectionName);
|
||||
}
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RequiresListRange(IList array, int offset, int count, string offsetName, string countName)
|
||||
{
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(countName);
|
||||
}
|
||||
if (offset < 0 || array.Count - offset < count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(offsetName);
|
||||
}
|
||||
}
|
||||
}
|
49
AspClassic.Scripting/Utils/DictionaryUnionEnumerator.cs
Normal file
49
AspClassic.Scripting/Utils/DictionaryUnionEnumerator.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal class DictionaryUnionEnumerator : CheckedDictionaryEnumerator
|
||||
{
|
||||
private IList<IDictionaryEnumerator> _enums;
|
||||
|
||||
private int _current;
|
||||
|
||||
public DictionaryUnionEnumerator(IList<IDictionaryEnumerator> enums)
|
||||
{
|
||||
_enums = enums;
|
||||
}
|
||||
|
||||
protected override object GetKey()
|
||||
{
|
||||
return _enums[_current].Key;
|
||||
}
|
||||
|
||||
protected override object GetValue()
|
||||
{
|
||||
return _enums[_current].Value;
|
||||
}
|
||||
|
||||
protected override bool DoMoveNext()
|
||||
{
|
||||
if (_current == _enums.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_enums[_current].MoveNext())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_current++;
|
||||
return DoMoveNext();
|
||||
}
|
||||
|
||||
protected override void DoReset()
|
||||
{
|
||||
for (int i = 0; i < _enums.Count; i++)
|
||||
{
|
||||
_enums[i].Reset();
|
||||
}
|
||||
_current = 0;
|
||||
}
|
||||
}
|
6
AspClassic.Scripting/Utils/EmptyArray.cs
Normal file
6
AspClassic.Scripting/Utils/EmptyArray.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class EmptyArray<T>
|
||||
{
|
||||
internal static T[] Instance = new T[0];
|
||||
}
|
8
AspClassic.Scripting/Utils/EmptyReadOnlyCollection.cs
Normal file
8
AspClassic.Scripting/Utils/EmptyReadOnlyCollection.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class EmptyReadOnlyCollection<T>
|
||||
{
|
||||
internal static ReadOnlyCollection<T> Instance = new ReadOnlyCollection<T>(new T[0]);
|
||||
}
|
13
AspClassic.Scripting/Utils/EnumBounds.cs
Normal file
13
AspClassic.Scripting/Utils/EnumBounds.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
public static class EnumBounds
|
||||
{
|
||||
public static bool IsValid(this SourceCodeKind value)
|
||||
{
|
||||
if (value > SourceCodeKind.Unspecified)
|
||||
{
|
||||
return value <= SourceCodeKind.AutoDetect;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
11
AspClassic.Scripting/Utils/ExceptionUtils.cs
Normal file
11
AspClassic.Scripting/Utils/ExceptionUtils.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class ExceptionUtils
|
||||
{
|
||||
public static ArgumentNullException MakeArgumentItemNullException(int index, string arrayName)
|
||||
{
|
||||
return new ArgumentNullException($"{arrayName}[{index}]");
|
||||
}
|
||||
}
|
16
AspClassic.Scripting/Utils/ExpressionUtils.cs
Normal file
16
AspClassic.Scripting/Utils/ExpressionUtils.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal sealed class ExpressionUtils
|
||||
{
|
||||
internal static Expression Convert(Expression expression, Type type)
|
||||
{
|
||||
if (!(expression.Type != type))
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
return Expression.Convert(expression, type);
|
||||
}
|
||||
}
|
10
AspClassic.Scripting/Utils/NativeMethods.cs
Normal file
10
AspClassic.Scripting/Utils/NativeMethods.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SetEnvironmentVariable(string name, string value);
|
||||
}
|
165
AspClassic.Scripting/Utils/ReadOnlyDictionary.cs
Normal file
165
AspClassic.Scripting/Utils/ReadOnlyDictionary.cs
Normal file
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
[Serializable]
|
||||
internal sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
|
||||
{
|
||||
private sealed class ReadOnlyWrapper<T> : ICollection<T>, IEnumerable<T>, IEnumerable
|
||||
{
|
||||
private readonly ICollection<T> _collection;
|
||||
|
||||
public int Count => _collection.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
internal ReadOnlyWrapper(ICollection<T> collection)
|
||||
{
|
||||
_collection = collection;
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return _collection.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
_collection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _collection.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _collection.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IDictionary<TKey, TValue> _dict;
|
||||
|
||||
public ICollection<TKey> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
ICollection<TKey> keys = _dict.Keys;
|
||||
if (!keys.IsReadOnly)
|
||||
{
|
||||
return new ReadOnlyWrapper<TKey>(keys);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<TValue> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
ICollection<TValue> values = _dict.Values;
|
||||
if (!values.IsReadOnly)
|
||||
{
|
||||
return new ReadOnlyWrapper<TValue>(values);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
public TValue this[TKey key] => _dict[key];
|
||||
|
||||
TValue IDictionary<TKey, TValue>.this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dict[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _dict.Count;
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public ReadOnlyDictionary(IDictionary<TKey, TValue> dict)
|
||||
{
|
||||
ReadOnlyDictionary<TKey, TValue> readOnlyDictionary = dict as ReadOnlyDictionary<TKey, TValue>;
|
||||
_dict = ((readOnlyDictionary != null) ? readOnlyDictionary._dict : dict);
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return _dict.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _dict.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.Remove(TKey key)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return _dict.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
_dict.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return _dict.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _dict.GetEnumerator();
|
||||
}
|
||||
}
|
91
AspClassic.Scripting/Utils/ReflectionUtils.cs
Normal file
91
AspClassic.Scripting/Utils/ReflectionUtils.cs
Normal file
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class ReflectionUtils
|
||||
{
|
||||
public const char GenericArityDelimiter = '`';
|
||||
|
||||
private static AssemblyBuilder _assembly;
|
||||
|
||||
private static ModuleBuilder _modBuilder;
|
||||
|
||||
private static int _typeCount;
|
||||
|
||||
private static readonly Type[] _DelegateCtorSignature = new Type[2]
|
||||
{
|
||||
typeof(object),
|
||||
typeof(IntPtr)
|
||||
};
|
||||
|
||||
private static TypeBuilder DefineDelegateType(string name)
|
||||
{
|
||||
if (_assembly == null)
|
||||
{
|
||||
Interlocked.CompareExchange(ref _assembly, AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicDelegates"), AssemblyBuilderAccess.Run), null);
|
||||
lock (_assembly)
|
||||
{
|
||||
if (_modBuilder == null)
|
||||
{
|
||||
_modBuilder = _assembly.DefineDynamicModule("DynamicDelegates");
|
||||
}
|
||||
}
|
||||
}
|
||||
return _modBuilder.DefineType(name + Interlocked.Increment(ref _typeCount), TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AutoClass, typeof(MulticastDelegate));
|
||||
}
|
||||
|
||||
public static Type GetObjectCallSiteDelegateType(int paramCnt)
|
||||
{
|
||||
switch (paramCnt)
|
||||
{
|
||||
case 0:
|
||||
return typeof(Func<CallSite, object, object>);
|
||||
case 1:
|
||||
return typeof(Func<CallSite, object, object, object>);
|
||||
case 2:
|
||||
return typeof(Func<CallSite, object, object, object, object>);
|
||||
case 3:
|
||||
return typeof(Func<CallSite, object, object, object, object, object>);
|
||||
case 4:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object>);
|
||||
case 5:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object>);
|
||||
case 6:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object>);
|
||||
case 7:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object>);
|
||||
case 8:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 9:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 10:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 11:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 12:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 13:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
case 14:
|
||||
return typeof(Func<CallSite, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object, object>);
|
||||
default:
|
||||
{
|
||||
Type[] array = new Type[paramCnt + 2];
|
||||
array[0] = typeof(CallSite);
|
||||
array[1] = typeof(object);
|
||||
for (int i = 0; i < paramCnt; i++)
|
||||
{
|
||||
array[i + 2] = typeof(object);
|
||||
}
|
||||
TypeBuilder typeBuilder = DefineDelegateType("Delegate");
|
||||
typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName, CallingConventions.Standard, _DelegateCtorSignature).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
|
||||
typeBuilder.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.VtableLayoutMask, typeof(object), array).SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
|
||||
return typeBuilder.CreateType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
AspClassic.Scripting/Utils/StringUtils.cs
Normal file
15
AspClassic.Scripting/Utils/StringUtils.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace AspClassic.Scripting.Utils;
|
||||
|
||||
internal static class StringUtils
|
||||
{
|
||||
public static Encoding DefaultEncoding => Encoding.Default;
|
||||
|
||||
public static string[] Split(string str, char[] separators, int maxComponents, StringSplitOptions options)
|
||||
{
|
||||
ContractUtils.RequiresNotNull(str, "str");
|
||||
return str.Split(separators, maxComponents, options);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue