forked from EvilBeaver/OneScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReflector.cs
More file actions
456 lines (390 loc) · 20.1 KB
/
Reflector.cs
File metadata and controls
456 lines (390 loc) · 20.1 KB
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
/*----------------------------------------------------------
This Source Code Form is subject to the terms of the
Mozilla Public License, v.2.0. If a copy of the MPL
was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using ScriptEngine.Machine;
using ScriptEngine.Machine.Contexts;
using ScriptEngine.HostedScript.Library.ValueTable;
using ScriptEngine.Machine.Reflection;
using MethodInfo = ScriptEngine.Machine.MethodInfo;
namespace ScriptEngine.HostedScript.Library
{
/// <summary>
/// Рефлектор предназначен для получения метаданных объектов во время выполнения.
/// Как правило, рефлексия используется для проверки наличия у объекта определенных свойств/методов.
/// В OneScript рефлексию можно применять для вызова методов объектов по именам методов.
/// </summary>
[ContextClass("Рефлектор","Reflector")]
public class ReflectorContext : AutoContext<ReflectorContext>
{
/// <summary>
/// Вызывает метод по его имени.
/// </summary>
/// <param name="target">Объект, метод которого нужно вызвать.</param>
/// <param name="methodName">Имя метода для вызова</param>
/// <param name="arguments">Массив аргументов, передаваемых методу. Следует учесть, что все параметры нужно передавать явно, в том числе необязательные.</param>
/// <returns>Если вызывается функция, то возвращается ее результат. В противном случае возвращается Неопределено.</returns>
[ContextMethod("ВызватьМетод", "CallMethod")]
public IValue CallMethod(IRuntimeContextInstance target, string methodName, ArrayImpl arguments = null)
{
var methodIdx = target.FindMethod(methodName);
var methInfo = target.GetMethodInfo(methodIdx);
var argsToPass = GetArgsToPass(arguments, methInfo);
IValue retValue = ValueFactory.Create();
if (methInfo.IsFunction)
{
target.CallAsFunction(methodIdx, argsToPass, out retValue);
}
else
{
target.CallAsProcedure(methodIdx, argsToPass);
}
if (arguments != null)
{
for (int i = 0; i < argsToPass.Length; i++)
{
if (i < arguments.Count())
{
arguments.Set(i, argsToPass[i]?.GetRawValue());
}
}
}
return retValue;
}
private static IValue[] GetArgsToPass(ArrayImpl arguments, MethodInfo methInfo)
{
var argsToPass = new List<IValue>();
if (arguments != null)
{
argsToPass.AddRange(arguments);
}
if (methInfo.ArgCount < argsToPass.Count)
throw RuntimeException.TooManyArgumentsPassed();
for (int i = 0; i < argsToPass.Count; i++)
{
if (!methInfo.Params[i].IsByValue)
argsToPass[i] = Variable.Create(argsToPass[i], $"reflectorArg{i}");
}
while (argsToPass.Count < methInfo.ArgCount)
{
argsToPass.Add(null);
}
return argsToPass.ToArray();
}
/// <summary>
/// Проверяет существование указанного метода у переданного объекта..
/// </summary>
/// <param name="target">Объект, из которого получаем таблицу методов.</param>
/// <param name="methodName">Имя метода для вызова</param>
/// <returns>Истину, если метод существует, и Ложь в обратном случае. </returns>
[ContextMethod("МетодСуществует", "MethodExists")]
public bool MethodExists(IValue target, string methodName)
{
if(target.DataType == DataType.Object)
return MethodExistsForObject(target.AsObject(), methodName);
if (target.DataType == DataType.Type)
return MethodExistsForType(target.GetRawValue() as TypeTypeValue, methodName);
throw RuntimeException.InvalidArgumentType("target");
}
private static bool MethodExistsForObject(IRuntimeContextInstance target, string methodName)
{
try
{
var idx = target.FindMethod(methodName);
return idx >= 0;
}
catch (RuntimeException)
{
return false;
}
}
private static ValueTable.ValueTable EmptyAnnotationsTable()
{
var annotationsTable = new ValueTable.ValueTable();
annotationsTable.Columns.Add("Имя");
annotationsTable.Columns.Add("Параметры");
return annotationsTable;
}
private static ValueTable.ValueTable CreateAnnotationTable(AnnotationDefinition[] annotations)
{
var annotationsTable = EmptyAnnotationsTable();
var annotationNameColumn = annotationsTable.Columns.FindColumnByName("Имя");
var annotationParamsColumn = annotationsTable.Columns.FindColumnByName("Параметры");
foreach (var annotation in annotations)
{
var annotationRow = annotationsTable.Add();
if (annotation.Name != null)
{
annotationRow.Set(annotationNameColumn, ValueFactory.Create(annotation.Name));
}
if (annotation.ParamCount != 0)
{
var parametersTable = new ValueTable.ValueTable();
var parameterNameColumn = parametersTable.Columns.Add("Имя");
var parameterValueColumn = parametersTable.Columns.Add("Значение");
annotationRow.Set(annotationParamsColumn, parametersTable);
foreach (var annotationParameter in annotation.Parameters)
{
var parameterRow = parametersTable.Add();
if (annotationParameter.Name != null)
{
parameterRow.Set(parameterNameColumn, ValueFactory.Create(annotationParameter.Name));
}
parameterRow.Set(parameterValueColumn, annotationParameter.RuntimeValue);
}
}
}
return annotationsTable;
}
private static bool MethodExistsForType(TypeTypeValue type, string methodName)
{
var clrType = GetReflectableClrType(type);
return clrType.GetMethod(methodName) != null;
}
private static object CreateMethodsMapper(Type clrType)
{
var mapperType = typeof(ContextMethodsMapper<>).MakeGenericType(clrType);
var instance = Activator.CreateInstance(mapperType);
return instance;
}
private static object CreatePropertiesMapper(Type clrType)
{
var mapperType = typeof(ContextPropertyMapper<>).MakeGenericType(clrType);
var instance = Activator.CreateInstance(mapperType);
return instance;
}
private static Type GetReflectableClrType(TypeTypeValue type)
{
Type clrType;
try
{
clrType = TypeManager.GetImplementingClass(type.Value.ID);
}
catch (InvalidOperationException)
{
throw NonReflectableType();
}
Type reflectableType;
if (clrType == typeof(AttachedScriptsFactory))
reflectableType = ReflectUserType(type.Value.Name);
else
reflectableType = ReflectContext(clrType);
return reflectableType;
}
private static RuntimeException NonReflectableType()
{
return RuntimeException.InvalidArgumentValue("Тип не может быть отражен.");
}
/// <summary>
/// Получает таблицу методов для переданного объекта..
/// </summary>
/// <param name="target">Объект, из которого получаем таблицу методов.</param>
/// <returns>Таблица значений колонками: Имя, Количество, ЭтоФункция, Аннотации</returns>
[ContextMethod("ПолучитьТаблицуМетодов", "GetMethodsTable")]
public ValueTable.ValueTable GetMethodsTable(IValue target)
{
var result = new ValueTable.ValueTable();
if(target.DataType == DataType.Object)
FillMethodsTableForObject(target.AsObject(), result);
else if (target.DataType == DataType.Type)
FillMethodsTableForType(target.GetRawValue() as TypeTypeValue, result);
else
throw RuntimeException.InvalidArgumentType();
return result;
}
private static void FillMethodsTableForObject(IRuntimeContextInstance target, ValueTable.ValueTable result)
{
FillMethodsTable(result, target.GetMethods());
}
private static void FillMethodsTableForType(TypeTypeValue type, ValueTable.ValueTable result)
{
var clrType = GetReflectableClrType(type);
var clrMethods = clrType.GetMethods(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public);
FillMethodsTable(result, ConvertToOsMethods(clrMethods));
}
private static IEnumerable<MethodInfo> ConvertToOsMethods(IEnumerable<System.Reflection.MethodInfo> source)
{
var dest = new List<MethodInfo>();
foreach (var methodInfo in source)
{
var osMethod = new MethodInfo();
osMethod.Name = methodInfo.Name;
osMethod.Alias = null;
osMethod.IsExport = methodInfo.IsPublic;
osMethod.IsFunction = methodInfo.ReturnType != typeof(void);
osMethod.Annotations = GetAnnotations(methodInfo.GetCustomAttributes<UserAnnotationAttribute>());
var methodParameters = methodInfo.GetParameters();
var osParams = new ParameterDefinition[methodParameters.Length];
osMethod.Params = osParams;
for (int i = 0; i < osParams.Length; i++)
{
var parameterInfo = methodParameters[i];
var osParam = new ParameterDefinition();
osParam.Name = parameterInfo.Name;
osParam.IsByValue = parameterInfo.GetCustomAttribute<ByRefAttribute>() != null;
osParam.HasDefaultValue = parameterInfo.HasDefaultValue;
osParam.DefaultValueIndex = -1;
// On Mono 5.20 we can't use GetCustomAttributes<T> because it fails with InvalidCast.
// Here's a workaround with home-made attribute Type filter.
var attributes = parameterInfo.GetCustomAttributes()
.OfType<UserAnnotationAttribute>();
osParam.Annotations = GetAnnotations(attributes);
osParams[i] = osParam;
}
dest.Add(osMethod);
}
return dest;
}
private static AnnotationDefinition[] GetAnnotations(IEnumerable<UserAnnotationAttribute> attributes)
{
return attributes.Select(x => x.Annotation).ToArray();
}
private static void FillPropertiesTableForType(TypeTypeValue type, ValueTable.ValueTable result)
{
var clrType = GetReflectableClrType(type);
var nativeProps = clrType.GetProperties()
.Select(x => new
{
PropDef = x.GetCustomAttribute<ContextPropertyAttribute>(),
Prop = x
})
.Where(x=>x.PropDef != null);
int indices = 0;
var infos = new List<VariableInfo>();
foreach(var prop in nativeProps)
{
var info = new VariableInfo();
info.Type = SymbolType.ContextProperty;
info.Index = indices++;
info.Identifier = prop.PropDef.GetName();
info.Annotations = GetAnnotations(prop.Prop.GetCustomAttributes<UserAnnotationAttribute>());
infos.Add(info);
}
if (clrType.BaseType == typeof(ScriptDrivenObject))
{
var nativeFields = clrType.GetFields();
foreach(var field in nativeFields)
{
var info = new VariableInfo();
info.Type = SymbolType.ContextProperty;
info.Index = indices++;
info.Identifier = field.Name;
info.Annotations = GetAnnotations(field.GetCustomAttributes<UserAnnotationAttribute>());
infos.Add(info);
}
}
FillPropertiesTable(result, infos);
}
private static void FillMethodsTable(ValueTable.ValueTable result, IEnumerable<MethodInfo> methods)
{
var nameColumn = result.Columns.Add("Имя", TypeDescription.StringType(), "Имя");
var countColumn = result.Columns.Add("КоличествоПараметров", TypeDescription.IntegerType(), "Количество параметров");
var isFunctionColumn = result.Columns.Add("ЭтоФункция", TypeDescription.BooleanType(), "Это функция");
var annotationsColumn = result.Columns.Add("Аннотации", new TypeDescription(), "Аннотации");
var paramsColumn = result.Columns.Add("Параметры", new TypeDescription(), "Параметры");
var isExportlColumn = result.Columns.Add("Экспорт", new TypeDescription(), "Экспорт");
foreach (var methInfo in methods)
{
ValueTableRow new_row = result.Add();
new_row.Set(nameColumn, ValueFactory.Create(methInfo.Name));
new_row.Set(countColumn, ValueFactory.Create(methInfo.ArgCount));
new_row.Set(isFunctionColumn, ValueFactory.Create(methInfo.IsFunction));
new_row.Set(isExportlColumn, ValueFactory.Create(methInfo.IsExport));
new_row.Set(annotationsColumn, methInfo.AnnotationsCount != 0 ? CreateAnnotationTable(methInfo.Annotations) : EmptyAnnotationsTable());
var paramTable = new ValueTable.ValueTable();
var paramNameColumn = paramTable.Columns.Add("Имя", TypeDescription.StringType(), "Имя");
var paramByValue = paramTable.Columns.Add("ПоЗначению", TypeDescription.BooleanType(), "По значению");
var paramHasDefaultValue = paramTable.Columns.Add("ЕстьЗначениеПоУмолчанию", TypeDescription.BooleanType(), "Есть значение по-умолчанию");
var paramAnnotationsColumn = paramTable.Columns.Add("Аннотации", new TypeDescription(), "Аннотации");
new_row.Set(paramsColumn, paramTable);
if (methInfo.ArgCount != 0)
{
var index = 0;
foreach (var param in methInfo.Params)
{
var name = string.Format("param{0}", ++index);
var paramRow = paramTable.Add();
paramRow.Set(paramNameColumn, ValueFactory.Create(name));
paramRow.Set(paramByValue, ValueFactory.Create(param.IsByValue));
paramRow.Set(paramHasDefaultValue, ValueFactory.Create(param.HasDefaultValue));
paramRow.Set(paramAnnotationsColumn, param.AnnotationsCount != 0 ? CreateAnnotationTable(param.Annotations) : EmptyAnnotationsTable());
}
}
}
}
/// <summary>
/// Получает таблицу свойств для переданного объекта..
/// </summary>
/// <param name="target">Объект, из которого получаем таблицу свойств.</param>
/// <returns>Таблица значений с колонками - Имя, Аннотации</returns>
[ContextMethod("ПолучитьТаблицуСвойств", "GetPropertiesTable")]
public ValueTable.ValueTable GetPropertiesTable(IValue target)
{
ValueTable.ValueTable result = new ValueTable.ValueTable();
if(target.DataType == DataType.Object)
FillPropertiesTable(result, target.AsObject().GetProperties());
else if (target.DataType == DataType.Type)
{
var type = target.GetRawValue() as TypeTypeValue;
FillPropertiesTableForType(type, result);
}
else
throw RuntimeException.InvalidArgumentType();
return result;
}
private static void FillPropertiesTable(ValueTable.ValueTable result, IEnumerable<VariableInfo> properties)
{
var nameColumn = result.Columns.Add("Имя", TypeDescription.StringType(), "Имя");
var annotationsColumn = result.Columns.Add("Аннотации", new TypeDescription(), "Аннотации");
var systemVarNames = new string[] { "этотобъект", "thisobject" };
foreach (var propInfo in properties)
{
if (systemVarNames.Contains(propInfo.Identifier.ToLower())) continue;
ValueTableRow new_row = result.Add();
new_row.Set(nameColumn, ValueFactory.Create(propInfo.Identifier));
new_row.Set(annotationsColumn, propInfo.AnnotationsCount != 0 ? CreateAnnotationTable(propInfo.Annotations) : EmptyAnnotationsTable());
}
}
public static Type ReflectUserType(string typeName)
{
LoadedModule module;
try
{
module = AttachedScriptsFactory.GetModuleOfType(typeName);
}
catch (KeyNotFoundException)
{
throw NonReflectableType();
}
var builder = new ClassBuilder<UserScriptContextInstance>();
return builder
.SetTypeName(typeName)
.SetModule(module)
.ExportDefaults()
.Build();
}
public static Type ReflectContext(Type clrType)
{
var attrib = clrType.GetCustomAttribute<ContextClassAttribute>();
if (attrib == null || !typeof(ContextIValueImpl).IsAssignableFrom(clrType))
throw NonReflectableType();
var builderType = typeof(ClassBuilder<>).MakeGenericType(clrType);
var builder = (IReflectedClassBuilder)Activator.CreateInstance(builderType);
return builder.SetTypeName(attrib.GetName())
.ExportDefaults()
.Build();
}
[ScriptConstructor]
public static IRuntimeContextInstance CreateNew()
{
return new ReflectorContext();
}
}
}