-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathJsHelper.cs
362 lines (286 loc) · 12.1 KB
/
JsHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
using System.Reflection;
using Jint;
using Jint.Native;
using Jint.Native.Array;
using Jint.Native.ArrayBuffer;
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
namespace JintTest
{
internal static class JsHelper
{
private sealed class InternalPropertyDescriptor : PropertyDescriptor
{
internal InternalPropertyDescriptor(JsValue value, PropertyFlag flags) : base(value, flags)
{
}
}
private sealed class InternalGetSetPropertyDescriptor : PropertyDescriptor
{
public override JsValue Get { get; }
public override JsValue Set { get; }
internal InternalGetSetPropertyDescriptor(JsValue get, JsValue set, PropertyFlag flags) : base(flags)
{
Get = get;
Set = set;
}
}
private sealed class InstanceFunction<T> : FunctionInstance
where T : ObjectInstance
{
private readonly JsString _typeName;
private readonly Func<T, JsValue[], JsValue> _func;
internal InstanceFunction(Engine engine, Realm realm, JsString name, JsNumber length, JsString typeName, Func<T, JsValue[], JsValue> func) : base(engine, realm, name)
{
_length = new InternalPropertyDescriptor(length, PropertyFlag.Configurable);
_typeName = typeName;
_func = func;
}
public override JsValue Call(JsValue thisObject, JsValue[] arguments)
{
var thisObj = thisObject as T;
if (thisObj != null)
return _func.Invoke(thisObj, arguments);
throw _engine.TypeError("this is not a " + _typeName + " instance.");
}
}
private sealed class EnumInfo
{
public readonly JsValue Name;
public readonly JsValue[] Names;
public readonly JsValue[] Values;
public readonly int Length;
public (JsValue name, JsValue value) this[int index] => (Names[index], Values[index]);
public EnumInfo(string name, int length)
{
Name = name;
Names = new JsValue[length];
Values = new JsValue[length];
Length = length;
}
}
private static readonly char[] nsSplit = { '.' };
private const Types nullish = Types.Undefined | Types.Null;
public static readonly JsValue[] EmptyArgs = Array.Empty<JsValue>();
public static readonly JsNumber Zero = new(0);
public static readonly JsNumber One = new(1);
public static readonly JsNumber Two = new(2);
public static readonly JsNumber NaN = new(double.NaN);
private static readonly HashSet<char> vowels = new()
{
'a',
'e',
'i',
'o',
'u',
'A',
'E',
'I',
'O',
'U'
};
private static readonly Dictionary<Type, JsString> typeofLookup = new()
{
[typeof(JsUndefined)] = CommonNames.Undefined,
[typeof(JsNull)] = CommonNames.Object,
[typeof(JsString)] = CommonNames.String,
[typeof(JsNumber)] = CommonNames.Number,
[typeof(JsBoolean)] = CommonNames.Boolean,
[typeof(JsSymbol)] = CommonNames.Symbol,
[typeof(ObjectInstance)] = CommonNames.Object,
[typeof(FunctionInstance)] = CommonNames.Function
};
private static readonly Dictionary<Type, EnumInfo> enums = new();
private static Func<ArrayBufferInstance, byte[]>? byteArrayGettter;
internal static string ToJsName(string str)
=> ToJsName(str.AsMemory());
internal static string ToJsName(ReadOnlyMemory<char> str)
=> string.Create(str.Length, str, (span, str) =>
{
var src = str.Span;
span[0] = char.ToLower(src[0]);
src[1..].CopyTo(span[1..]);
});
public static JsString Typeof(this JsValue value)
{
return value.Type switch
{
Types.Undefined => CommonNames.Undefined,
Types.Boolean => CommonNames.Boolean,
Types.String => CommonNames.String,
Types.Number => CommonNames.Number,
Types.Symbol => CommonNames.Symbol,
Types.Object => value is ICallable ? CommonNames.Function : CommonNames.Object,
_ => CommonNames.Object,
};
}
public static JsString Typeof(this Type type, bool useClassName)
{
if (!typeof(JsValue).IsAssignableFrom(type))
throw new ArgumentException($"Type \"{type}\" does not extend Jint.Native.JsValue.");
if (typeofLookup.TryGetValue(type, out var t))
return t;
if (typeof(ICallable).IsAssignableFrom(type))
return CommonNames.Function;
if (useClassName)
return new(type.Name.EndsWith("Instance") ? type.Name[..^8] : type.Name);
return CommonNames.Object;
}
public const PropertyFlag DefaultValueFlags = PropertyFlag.ConfigurableEnumerableWritable;
public const PropertyFlag DefaultPropertyFlags = PropertyFlag.Enumerable | PropertyFlag.Configurable;
public const PropertyFlag DefaultFunctionFlags = PropertyFlag.Writable | PropertyFlag.Configurable;
public static PropertyDescriptor CreateDescriptor(JsValue value, PropertyFlag flags = DefaultValueFlags)
=> new InternalPropertyDescriptor(value, flags);
public static PropertyDescriptor CreateDescriptor(JsValue get, JsValue set, PropertyFlag flags = DefaultPropertyFlags)
=> new InternalGetSetPropertyDescriptor(get, set, flags);
public static PropertyDescriptor CreateInstanceFunctionDescriptor<T>(Engine engine, JsString name, JsString typeName, JsNumber length, Func<T, JsValue[], JsValue> func, PropertyFlag flags = DefaultFunctionFlags) where T : ObjectInstance
=> new InternalPropertyDescriptor(new InstanceFunction<T>(engine, engine.Realm, name, length, typeName, func), flags);
public static FunctionInstance CreateInstanceFunction<T>(Engine engine, JsString name, JsString typeName, JsNumber length, Func<T, JsValue[], JsValue> func) where T : ObjectInstance
=> new InstanceFunction<T>(engine, engine.Realm, name, length, typeName, func);
public static void FastSetProperty(this ObjectInstance @this, JsValue property, JsValue value, PropertyFlag flag = DefaultValueFlags)
=> @this.FastSetProperty(property, new InternalPropertyDescriptor(value, flag));
public static void FastSetProperty(this ObjectInstance @this, AbstractObjectConstructor value, PropertyFlag flag = DefaultFunctionFlags)
=> @this.FastSetProperty(value.Name, new InternalPropertyDescriptor(value, flag));
public static bool IsVowel(this char c)
=> vowels.Contains(c);
public static bool IsNullish(this JsValue value)
=> (value.Type & nullish) != 0;
public static T RequireThis<T>(this Engine engine, JsValue name, JsValue thisObject) where T : class
{
if (thisObject is T t)
return t;
throw TypeError(engine, $"{name} called on incompatible type.");
}
public static T GetArgument<T>(this JsValue[] arguments, Engine engine, int index, string? typeName = null)
{
var arg = arguments.At(index);
if (arg is T t)
return t;
if (string.IsNullOrEmpty(typeName))
{
typeName = typeof(T).Name;
if (typeName.EndsWith("Instance"))
typeName = typeName[..^8];
}
throw engine.TypeError($"Argument {index + 1} is not {(typeName[0].IsVowel() ? "an" : "a")} {typeName}");
}
public static ObjectInstance CreateEnum<T>(this Engine engine) where T : unmanaged, Enum
=> CreateEnumInternal(engine, typeof(T));
public static ObjectInstance CreateEnum(this Engine engine, Type enumType)
{
if (!enumType.IsEnum)
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum.", nameof(enumType));
return CreateEnumInternal(engine, enumType);
}
private static ObjectInstance CreateEnumInternal(Engine engine, Type enumType)
{
var obj = new ObjectInstance(engine);
AddEnumValuesInternal(obj, enumType, out _);
return obj;
}
public static void AddEnum<T>(this ObjectInstance target) where T : unmanaged, Enum
=> AddEnumInternal(target, typeof(T));
public static void AddEnum(this ObjectInstance target, Type enumType)
{
if (!enumType.IsEnum)
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum.", nameof(enumType));
AddEnumInternal(target, enumType);
}
public static void AddEnumInternal(this ObjectInstance target, Type enumType)
{
var obj = new ObjectInstance(target.Engine);
AddEnumValuesInternal(obj, enumType, out var info);
target.Set(info.Name, obj);
}
public static void AddEnumValues<T>(this ObjectInstance instance) where T : unmanaged, Enum
=> AddEnumValuesInternal(instance, typeof(T), out _);
public static void AddEnumValues(this ObjectInstance instance, Type enumType)
{
if (!enumType.IsEnum)
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum.", nameof(enumType));
AddEnumValuesInternal(instance, enumType, out _);
}
private static void AddEnumValuesInternal(ObjectInstance instance, Type enumType, out EnumInfo info)
{
if (!enums.TryGetValue(enumType, out info!))
{
var values = enumType.GetEnumValues();
var names = enumType.GetEnumNames();
info = new EnumInfo(enumType.Name, values.Length);
for (var i = 0; i < info.Length; i++)
{
var value = (IConvertible)values.GetValue(i)!;
info.Names[i] = names[i];
info.Values[i] = value.ToInt32(null);
}
enums[enumType] = info;
}
for (var i = 0; i < info.Length; i++)
{
var (name, value) = info[i];
instance.FastSetProperty(name, value);
instance.FastSetProperty(value, name);
}
}
public static ArrayInstance CreateArray(this Engine engine, PropertyDescriptor[] props)
{
var array = new ArrayInstance(engine, props);
array.SetPrototypeOf(engine.Realm.Intrinsics.Array.PrototypeObject);
return array;
}
public static ArrayInstance CreateReadOnlyArray(this Engine engine, IReadOnlyCollection<JsValue> values)
=> CreateReadOnlyArray(engine, values.Count, values);
public static ArrayInstance CreateReadOnlyArray(this Engine engine, IReadOnlyCollection<string> values)
=> CreateReadOnlyArray(engine, values.Count, values.Select(v => (JsValue)v));
public static ArrayInstance CreateReadOnlyArray(this Engine engine, IReadOnlyCollection<double> values)
=> CreateReadOnlyArray(engine, values.Count, values.Select(v => (JsValue)v));
private static ArrayInstance CreateReadOnlyArray(this Engine engine, int count, IEnumerable<JsValue> values)
{
const PropertyFlag flags = PropertyFlag.Enumerable;
var i = 0;
var props = new PropertyDescriptor[count];
foreach (var value in values)
props[i++] = CreateDescriptor(value, flags);
var array = new ArrayInstance(engine, props);
var lengthDesc = array.GetOwnProperty(CommonNames.Length);
lengthDesc.Writable = false;
lengthDesc.Configurable = false;
array.SetPrototypeOf(engine.Realm.Intrinsics.Array.PrototypeObject);
return array;
}
public static JavaScriptException TypeError(this JsValue value, string message)
=> value is ObjectInstance obj ? TypeError(obj.Engine, message) : new JavaScriptException(message);
public static JavaScriptException TypeError(this ObjectInstance value, string message)
=> TypeError(value.Engine, message);
public static JavaScriptException TypeError(this Engine engine, string message, Exception? cause = null)
=> new JavaScriptException(engine.Realm.Intrinsics.TypeError, message, cause);
public static JavaScriptException TypeError(this Engine engine, string format, object? arg0)
=> TypeError(engine, string.Format(format, arg0));
public static JavaScriptException TypeError(this Engine engine, string format, object? arg0, object? arg1)
=> TypeError(engine, string.Format(format, arg0, arg1));
public static JavaScriptException TypeError(this Engine engine, string format, params object?[] args)
=> TypeError(engine, string.Format(format, args));
public static JsValue GetValue(this ObjectInstance obj, PropertyDescriptor desc)
{
if (desc.Get is ICallable getter)
return getter.Call(obj, EmptyArgs);
return desc.Value;
}
/// <summary>
/// Get the underlying byte array of an <see cref="ArrayBufferInstance"/> instead of copying the data to a new instance, like <see cref="JsValueExtensions.AsUint8Array(JsValue)"/> does.
/// </summary>
public static byte[] GetData(this ArrayBufferInstance buffer)
{
if (byteArrayGettter == null)
{
#nullable disable
var prop = typeof(ArrayBufferInstance).GetProperty("ArrayBufferData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
byteArrayGettter = (Func<ArrayBufferInstance, byte[]>)Delegate.CreateDelegate(typeof(Func<ArrayBufferInstance, byte[]>), prop.GetMethod);
#nullable restore
}
return byteArrayGettter.Invoke(buffer);
}
}
}