diff --git a/src/OneScript.Core/Contexts/ContextMethodAttribute.cs b/src/OneScript.Core/Contexts/ContextMethodAttribute.cs
index 78c4c5626..3eabb3d8a 100644
--- a/src/OneScript.Core/Contexts/ContextMethodAttribute.cs
+++ b/src/OneScript.Core/Contexts/ContextMethodAttribute.cs
@@ -5,7 +5,10 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/
+#nullable enable
using System;
+using System.Linq;
+using System.Reflection;
using System.Runtime.CompilerServices;
using OneScript.Commons;
@@ -16,6 +19,10 @@ public class ContextMethodAttribute : Attribute, INameAndAliasProvider
{
private readonly string _name;
private readonly string _alias;
+ private bool _isDeprecated;
+ private bool _throwOnUse;
+ private bool _skipForDocumenter;
+ private Type? _converter;
public ContextMethodAttribute(string name, string alias)
{
@@ -35,16 +42,49 @@ public ContextMethodAttribute(string name, string _ = null,
{
}
- public bool IsDeprecated { get; set; }
+ public bool IsDeprecated
+ {
+ get => _isDeprecated;
+ set => _isDeprecated = value;
+ }
+
+ public bool ThrowOnUse
+ {
+ get => _throwOnUse;
+ set => _throwOnUse = value;
+ }
- public bool ThrowOnUse { get; set; }
-
///
/// Данный метод не будет обработан генератором документации при обходе типов
///
- public bool SkipForDocumenter { get; set; }
+ public bool SkipForDocumenter
+ {
+ get => _skipForDocumenter;
+ set => _skipForDocumenter = value;
+ }
public string Name => _name;
public string Alias => _alias;
+
+ ///
+ /// Конвертер возвращаемого значения функции, необходим для неподдерживаемых маршаллингом типов
+ ///
+ public Type? Converter
+ {
+ get => _converter;
+ set
+ {
+ if (value != null)
+ {
+ if (!value.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IContextValueConverter<>)))
+ throw new Exception("Конвертер должен реализовывать интерфейс IContextValueConverter");
+
+ if (value.IsAbstract || value.IsInterface)
+ throw new Exception("Конвертер не может быть абстрактным типом или интерфейсом");
+ }
+
+ _converter = value;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/OneScript.Core/Contexts/ContextMethodInfo.cs b/src/OneScript.Core/Contexts/ContextMethodInfo.cs
index 362a2e209..b2ecf688e 100644
--- a/src/OneScript.Core/Contexts/ContextMethodInfo.cs
+++ b/src/OneScript.Core/Contexts/ContextMethodInfo.cs
@@ -21,6 +21,8 @@ public sealed class ContextMethodInfo : BslMethodInfo, IObjectWrapper
{
private readonly MethodInfo _realMethod;
private readonly ContextMethodAttribute _scriptMark;
+
+ public Type ConverterType { get; private set; }
public ContextMethodInfo(MethodInfo realMethod)
{
@@ -39,10 +41,18 @@ public ContextMethodInfo(MethodInfo realMethod)
public ContextMethodInfo(MethodInfo realMethod, ContextMethodAttribute binding)
{
_realMethod = realMethod;
+
_scriptMark = binding;
+
InjectsProcess = _realMethod.GetParameters().FirstOrDefault()?.ParameterType == typeof(IBslProcess);
}
+ private void InitConverter()
+ {
+ if (_scriptMark.Converter != null)
+ ConverterType = _scriptMark.Converter;
+ }
+
public override Type ReturnType => _realMethod.ReturnType;
public override ParameterInfo ReturnParameter => _realMethod.ReturnParameter;
@@ -85,6 +95,18 @@ public override MethodInfo GetBaseDefinition()
{
return _realMethod.GetBaseDefinition();
}
+
+ public bool TryGetConverter(out object converter)
+ {
+ if (_scriptMark.Converter == null)
+ {
+ converter = null;
+ return false;
+ }
+
+ converter = Activator.CreateInstance(_scriptMark.Converter);
+ return true;
+ }
public override ICustomAttributeProvider ReturnTypeCustomAttributes => _realMethod.ReturnTypeCustomAttributes;
diff --git a/src/OneScript.Core/Contexts/ContextPropertyAttribute.cs b/src/OneScript.Core/Contexts/ContextPropertyAttribute.cs
index 35350670a..96911307f 100644
--- a/src/OneScript.Core/Contexts/ContextPropertyAttribute.cs
+++ b/src/OneScript.Core/Contexts/ContextPropertyAttribute.cs
@@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the
----------------------------------------------------------*/
using System;
+using System.Linq;
using OneScript.Commons;
namespace OneScript.Contexts
@@ -15,6 +16,7 @@ public class ContextPropertyAttribute : Attribute, INameAndAliasProvider
{
private readonly string _name;
private readonly string _alias;
+ private Type _converter;
public ContextPropertyAttribute(string name, string alias = "")
{
@@ -40,5 +42,26 @@ public ContextPropertyAttribute(string name, string alias = "")
public string Name => _name;
public string Alias => _alias;
+
+ ///
+ /// Конвертер значения свойства
+ ///
+ public Type Converter
+ {
+ get => _converter;
+ set
+ {
+ if (value != null)
+ {
+ if (!value.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IContextValueConverter<>)))
+ throw new Exception("Конвертер должен реализовывать интерфейс IContextValueConverter");
+
+ if (value.IsAbstract || value.IsInterface)
+ throw new Exception("Конвертер не может быть абстрактным типом или интерфейсом");
+ }
+
+ _converter = value;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/OneScript.Core/Contexts/ContextPropertyInfo.cs b/src/OneScript.Core/Contexts/ContextPropertyInfo.cs
index 5b7d9b2db..3b9e104d4 100644
--- a/src/OneScript.Core/Contexts/ContextPropertyInfo.cs
+++ b/src/OneScript.Core/Contexts/ContextPropertyInfo.cs
@@ -10,6 +10,7 @@ This Source Code Form is subject to the terms of the
using System.Linq;
using System.Reflection;
using OneScript.Commons;
+using ScriptEngine.Machine;
namespace OneScript.Contexts
{
@@ -20,7 +21,7 @@ public class ContextPropertyInfo : BslPropertyInfo, IObjectWrapper
{
private readonly PropertyInfo _realProperty;
private readonly ContextPropertyAttribute _scriptMark;
-
+
public ContextPropertyInfo(PropertyInfo wrappedInfo)
{
_realProperty = wrappedInfo;
@@ -54,6 +55,32 @@ public override bool Equals(BslPropertyInfo other)
public override string Alias => _scriptMark.Alias;
+ public Type ConverterType => _scriptMark.Converter;
+
+ public bool TryGetConverter(out object converter)
+ {
+ if (ConverterType == null)
+ {
+ converter = null;
+ return false;
+ }
+
+ converter = Activator.CreateInstance(ConverterType);
+ return true;
+ }
+
+ public bool TryGetStrictConverter(out IContextValueConverter converter)
+ {
+ var result = TryGetConverter(out var c);
+
+ if (result)
+ converter = c as IContextValueConverter;
+ else
+ converter = null;
+
+ return result;
+ }
+
public override MethodInfo[] GetAccessors(bool nonPublic)
{
var getter = GetGetMethod(nonPublic);
diff --git a/src/OneScript.Core/Contexts/IContextValueConverter.cs b/src/OneScript.Core/Contexts/IContextValueConverter.cs
new file mode 100644
index 000000000..12a07272b
--- /dev/null
+++ b/src/OneScript.Core/Contexts/IContextValueConverter.cs
@@ -0,0 +1,23 @@
+/*----------------------------------------------------------
+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.Linq;
+using ScriptEngine.Machine;
+
+namespace OneScript.Contexts
+{
+ public interface IContextValueConverter
+ {
+ IValue ToIValue(TClr obj);
+
+ TClr ToClr(IValue obj);
+
+ public static bool ImplementsIt(Type type)
+ => type.GetInterfaces()
+ .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IContextValueConverter<>));
+ }
+}
diff --git a/src/ScriptEngine/Machine/Contexts/ContextMethodMapper.cs b/src/ScriptEngine/Machine/Contexts/ContextMethodMapper.cs
index 9d59cabef..636cdb0cb 100644
--- a/src/ScriptEngine/Machine/Contexts/ContextMethodMapper.cs
+++ b/src/ScriptEngine/Machine/Contexts/ContextMethodMapper.cs
@@ -152,15 +152,16 @@ private static MethodSignature CreateMetadata(MethodInfo target, ContextMethodAt
}
- var scriptMethInfo = new MethodSignature();
- scriptMethInfo.IsFunction = isFunc;
- scriptMethInfo.IsExport = true;
- scriptMethInfo.IsDeprecated = binding.IsDeprecated;
- scriptMethInfo.ThrowOnUseDeprecated = binding.ThrowOnUse;
- scriptMethInfo.Name = binding.Name;
- scriptMethInfo.Alias = binding.Alias;
-
- scriptMethInfo.Params = paramDefs;
+ var scriptMethInfo = new MethodSignature
+ {
+ IsFunction = isFunc,
+ IsExport = true,
+ IsDeprecated = binding.IsDeprecated,
+ ThrowOnUseDeprecated = binding.ThrowOnUse,
+ Name = binding.Name,
+ Alias = binding.Alias,
+ Params = paramDefs
+ };
return scriptMethInfo;
}
@@ -168,9 +169,18 @@ private static MethodSignature CreateMetadata(MethodInfo target, ContextMethodAt
private static ContextCallableDelegate CreateFunction(ContextMethodInfo target)
{
var methodCall = MethodCallExpression(target, out var instParam, out var argsParam, out var processParam);
-
- var convertRetMethod = ContextValuesMarshaller.BslReturnValueGenericConverter.MakeGenericMethod(target.ReturnType);
- var convertReturnCall = Expression.Call(convertRetMethod, methodCall);
+
+ var convertReturnCall = target.ConverterType switch
+ {
+ null => Expression.Call(
+ ContextValuesMarshaller.BslReturnValueGenericConverter.MakeGenericMethod(target.ReturnType),
+ methodCall),
+ _ => Expression.Call(
+ Expression.New(target.ConverterType),
+ target.ConverterType.GetMethod("ToIValue")!,
+ methodCall)
+ };
+
var body = convertReturnCall;
var l = Expression.Lambda>(body, instParam, argsParam, processParam);
@@ -226,28 +236,24 @@ private static InvocationExpression MethodCallExpression(
var (clrIndexStart, argsLen) = contextMethod.InjectsProcess ? (1, parameters.Length - 1) : (0, parameters.Length);
- var argsPass = new List();
- argsPass.Add(instParam);
-
+ var argsPass = new List { instParam };
+
if (contextMethod.InjectsProcess)
argsPass.Add(processParam);
for (int bslIndex = 0,clrIndex = clrIndexStart; bslIndex < argsLen; bslIndex++, clrIndex++)
{
var targetType = parameters[clrIndex].ParameterType;
- var convertMethod = ContextValuesMarshaller.BslGenericParameterConverter.MakeGenericMethod(targetType);
Expression defaultArg;
if (parameters[clrIndex].HasDefaultValue)
- {
defaultArg = Expression.Constant(parameters[clrIndex].DefaultValue, targetType);
- }
else
- {
defaultArg = ContextValuesMarshaller.GetDefaultBslValueConstant(targetType);
- }
var indexedArg = Expression.ArrayIndex(argsParam, Expression.Constant(bslIndex));
+
+ var convertMethod = ContextValuesMarshaller.BslGenericParameterConverter.MakeGenericMethod(targetType);
var conversionCall = Expression.Call(convertMethod,
indexedArg,
defaultArg,
diff --git a/src/ScriptEngine/Machine/Contexts/ContextPropertyMapper.cs b/src/ScriptEngine/Machine/Contexts/ContextPropertyMapper.cs
index 9b97c2a38..acd2fc5eb 100644
--- a/src/ScriptEngine/Machine/Contexts/ContextPropertyMapper.cs
+++ b/src/ScriptEngine/Machine/Contexts/ContextPropertyMapper.cs
@@ -16,8 +16,8 @@ namespace ScriptEngine.Machine.Contexts
{
public class PropertyTarget
{
- private readonly BslPropertyInfo _propertyInfo;
-
+ private readonly ContextPropertyInfo _propertyInfo;
+
public PropertyTarget(PropertyInfo propInfo)
{
_propertyInfo = new ContextPropertyInfo(propInfo);
@@ -27,16 +27,6 @@ public PropertyTarget(PropertyInfo propInfo)
if (string.IsNullOrEmpty(Alias))
Alias = propInfo.Name;
- IValue CantReadAction(TInstance inst)
- {
- throw PropertyAccessException.PropIsNotReadableException(Name);
- }
-
- void CantWriteAction(TInstance inst, IValue val)
- {
- throw PropertyAccessException.PropIsNotWritableException(Name);
- }
-
if (_propertyInfo.CanRead)
{
var getMethodInfo = propInfo.GetGetMethod();
@@ -84,6 +74,18 @@ void CantWriteAction(TInstance inst, IValue val)
{
Setter = CantWriteAction;
}
+
+ return;
+
+ void CantWriteAction(TInstance inst, IValue val)
+ {
+ throw PropertyAccessException.PropIsNotWritableException(Name);
+ }
+
+ IValue CantReadAction(TInstance inst)
+ {
+ throw PropertyAccessException.PropIsNotReadableException(Name);
+ }
}
public Func Getter { get; }
@@ -108,19 +110,18 @@ private Func CreateGetter(MethodInfo methInfo)
private Action CreateSetter(MethodInfo methInfo)
{
var method = (Action)Delegate.CreateDelegate(typeof(Action), methInfo);
- return (inst, val) => method(inst, ConvertParam(val));
+ return (inst, val) => method(inst, ConvertValue(val));
}
- private static T ConvertParam(IValue value)
- {
- return ContextValuesMarshaller.ConvertValueStrict(value);
- }
-
- private static IValue ConvertReturnValue(TRet param)
- {
- return ContextValuesMarshaller.ConvertReturnValue(param);
- }
+ private T ConvertValue(IValue value)
+ => _propertyInfo.TryGetStrictConverter(out var converter) ?
+ converter.ToClr(value)
+ : ContextValuesMarshaller.ConvertValueStrict(value);
+ private IValue ConvertReturnValue(TRet param)
+ => _propertyInfo.TryGetStrictConverter(out var converter)
+ ? converter!.ToIValue(param)
+ : ContextValuesMarshaller.ConvertReturnValue(param);
}
public class ContextPropertyMapper
diff --git a/src/ScriptEngine/Machine/Contexts/ContextValuesMarshaller.cs b/src/ScriptEngine/Machine/Contexts/ContextValuesMarshaller.cs
index f64d2aeea..de4eeb84b 100644
--- a/src/ScriptEngine/Machine/Contexts/ContextValuesMarshaller.cs
+++ b/src/ScriptEngine/Machine/Contexts/ContextValuesMarshaller.cs
@@ -33,7 +33,7 @@ static ContextValuesMarshaller()
BslGenericParameterConverter = typeof(ContextValuesMarshaller).GetMethods()
.First(x => x.Name == nameof(ConvertParam) && x.GetGenericArguments().Length == 1 &&
x.GetParameters().Length == 3);
-
+
BslReturnValueGenericConverter = typeof(ContextValuesMarshaller).GetMethods()
.First(x => x.Name == nameof(ConvertReturnValue) && x.GetGenericArguments().Length == 1);
}
@@ -88,7 +88,7 @@ public static object ConvertParam(IValue value, Type type, IBslProcess process)
/// Значение целевого типа
public static T ConvertParam(IValue value, T defaultValue = default)
{
- return ConvertParam(value, defaultValue, ForbiddenBslProcess.Instance);
+ return ConvertParam(value, defaultValue, ForbiddenBslProcess.Instance);
}
///
@@ -101,7 +101,7 @@ public static T ConvertParam(IValue value, T defaultValue = default)
/// Значение целевого типа
public static T ConvertParam(IValue value, T defaultValue, IBslProcess process)
{
- object valueObj = ConvertParam(value, typeof(T), process);
+ var valueObj = ConvertParam(value, typeof(T), process);
return valueObj != null ? (T)valueObj : defaultValue;
}
@@ -163,7 +163,7 @@ private static object ConvertValueType(IValue value, Type type, IBslProcess proc
{
return null;
}
-
+
if (Nullable.GetUnderlyingType(type) != null)
{
return ConvertValueType(value, Nullable.GetUnderlyingType(type), process);
@@ -293,25 +293,21 @@ private static IValue ConvertReturnValue(object objParam, Type type)
}
if (type.IsEnum)
- {
return ConvertEnum(objParam, type);
- }
- else if (typeof(IRuntimeContextInstance).IsAssignableFrom(type))
- {
+
+ if (typeof(IRuntimeContextInstance).IsAssignableFrom(type))
return (IValue)(IRuntimeContextInstance)objParam;
- }
- else if (typeof(IValue).IsAssignableFrom(type))
- {
+
+ if (typeof(IValue).IsAssignableFrom(type))
return (IValue)objParam;
- }
- else if (Nullable.GetUnderlyingType(type) != null)
- {
+
+ if (Nullable.GetUnderlyingType(type) != null)
return ConvertReturnValue(objParam, Nullable.GetUnderlyingType(type));
- }
- else
- {
- throw ValueMarshallingException.TypeNotSupported(type);
- }
+
+ if (type.GetCustomAttribute() != null && !typeof(IRuntimeContextInstance).IsAssignableFrom(type))
+ return new NotBslValueWrapper(objParam);
+
+ throw ValueMarshallingException.TypeNotSupported(type);
}
private static IValue ConvertEnum(object objParam, Type type)
diff --git a/src/ScriptEngine/Machine/NotBslValueWrapper.cs b/src/ScriptEngine/Machine/NotBslValueWrapper.cs
new file mode 100644
index 000000000..d2815aee7
--- /dev/null
+++ b/src/ScriptEngine/Machine/NotBslValueWrapper.cs
@@ -0,0 +1,231 @@
+/*----------------------------------------------------------
+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.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using OneScript.Commons;
+using OneScript.Contexts;
+using OneScript.Exceptions;
+using OneScript.Execution;
+using OneScript.Types;
+using OneScript.Values;
+using ScriptEngine.Machine.Contexts;
+using ScriptEngine.Types;
+using TinyIoC;
+
+namespace ScriptEngine.Machine
+{
+ public class NotBslValueWrapper : ContextIValueImpl, IObjectWrapper
+ {
+ private static readonly object InitLocker = new object();
+ private readonly Type _underlyingType;
+
+ private static readonly Dictionary PropertiesIndexers =
+ new Dictionary();
+ private static readonly Dictionary MethodsIndexers =
+ new Dictionary();
+
+ private static readonly Dictionary> PropertiesCaches =
+ new Dictionary>();
+ private static readonly Dictionary> MethodsCaches =
+ new Dictionary>();
+
+ public object UnderlyingObject { get; }
+
+ public NotBslValueWrapper(object obj)
+ {
+ UnderlyingObject = obj;
+ _underlyingType = UnderlyingObject.GetType();
+
+ DefineType(obj.GetType().GetTypeFromClassMarkup());
+ InitMethodsProperties();
+ }
+
+ private void InitMethodsProperties()
+ {
+ var objType = UnderlyingObject.GetType();
+
+ lock (InitLocker)
+ {
+ // Свойства и методы уже были закешированы
+ if (PropertiesIndexers.ContainsKey(objType))
+ return;
+
+ var propertiesIndex = new IndexedNamesCollection();
+ var propertiesCache = new Dictionary();
+
+ var methodsIndex = new IndexedNamesCollection();
+ var methodsCache = new Dictionary();
+
+
+ var props = objType.GetProperties()
+ .Where(x => x.GetCustomAttributes(typeof(ContextPropertyAttribute), false).Length != 0)
+ .ToList();
+
+ foreach (var property in props)
+ {
+ var info = new ContextPropertyInfo(property);
+ var id = propertiesIndex.RegisterName(info.Name, string.IsNullOrEmpty(info.Alias) ? null : info.Alias);
+
+ propertiesCache.Add(id, info);
+ }
+
+ var methods = objType.GetMethods()
+ .Where(x => x.GetCustomAttributes(typeof(ContextMethodAttribute), false).Length != 0)
+ .ToList();
+
+ foreach (var method in methods)
+ {
+ var info = new ContextMethodInfo(method);
+ var id = methodsIndex.RegisterName(info.Name, string.IsNullOrEmpty(info.Alias) ? null : info.Alias);
+
+ methodsCache.Add(id, info);
+ }
+
+ PropertiesIndexers.Add(objType, propertiesIndex);
+ PropertiesCaches.Add(objType, propertiesCache);
+
+ MethodsIndexers.Add(objType, methodsIndex);
+ MethodsCaches.Add(objType, methodsCache);
+ }
+ }
+
+ public override int GetPropertyNumber(string name)
+ => GetPropertiesIndexer().TryGetIdOfName(name, out var id) ? id : -1;
+
+ public override bool IsPropReadable(int propNum)
+ => GetPropertiesCache()[propNum].CanRead;
+
+ public override bool IsPropWritable(int propNum)
+ => GetPropertiesCache()[propNum].CanWrite;
+
+ public override IValue GetPropValue(int propNum)
+ {
+ var prop = GetPropertiesCache()[propNum];
+ var getter = prop.GetGetMethod(true);
+ if (getter == null)
+ throw PropertyAccessException.PropIsNotReadableException(prop.Name);
+ var value = getter.Invoke(UnderlyingObject, Array.Empty