// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices.Expando;
using Microsoft.ClearScript.Util;
namespace Microsoft.ClearScript
{
internal abstract class ScriptItem : ScriptObject, IExpando, IDynamic, IScriptMarshalWrapper
{
private static readonly MethodInfo throwLastScriptErrorMethod = typeof(ScriptItem).GetMethod("ThrowLastScriptError");
private static readonly MethodInfo clearLastScriptErrorMethod = typeof(ScriptItem).GetMethod("ClearLastScriptError");
[ThreadStatic] private static IScriptEngineException lastScriptError;
public static void ThrowLastScriptError()
{
var scriptError = lastScriptError;
if (scriptError != null)
{
if (scriptError is ScriptInterruptedException)
{
throw new ScriptInterruptedException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException);
}
throw new ScriptEngineException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException);
}
}
public static void ClearLastScriptError()
{
lastScriptError = null;
}
protected abstract bool TryBindAndInvoke(DynamicMetaObjectBinder binder, object[] args, out object result);
protected virtual object[] AdjustInvokeArgs(object[] args)
{
return args;
}
private bool TryWrappedBindAndInvoke(DynamicMetaObjectBinder binder, object[] wrappedArgs, out object result)
{
object[] args = null;
object[] savedArgs = null;
object tempResult = null;
var succeeded = Engine.ScriptInvoke(() =>
{
args = Engine.MarshalToScript(wrappedArgs);
savedArgs = (object[])args.Clone();
if (!TryBindAndInvoke(binder, args, out tempResult))
{
if ((Engine.CurrentScriptFrame != null) && (lastScriptError == null))
{
lastScriptError = Engine.CurrentScriptFrame.ScriptError;
}
return false;
}
return true;
});
if (succeeded)
{
for (var index = 0; index < args.Length; index++)
{
var arg = args[index];
if (!ReferenceEquals(arg, savedArgs[index]))
{
wrappedArgs[index] = Engine.MarshalToHost(args[index], false);
}
}
result = Engine.MarshalToHost(tempResult, false).ToDynamicResult(Engine);
return true;
}
result = null;
return false;
}
private bool TryWrappedInvokeOrInvokeMember(DynamicMetaObjectBinder binder, ParameterInfo[] parameters, object[] args, out object result)
{
Type[] paramTypes = null;
if ((parameters != null) && (parameters.Length >= args.Length))
{
paramTypes = parameters.Skip(parameters.Length - args.Length).Select(param => param.ParameterType).ToArray();
}
if (paramTypes != null)
{
for (var index = 0; index < paramTypes.Length; index++)
{
var paramType = paramTypes[index];
if (paramType.IsByRef)
{
args[index] = typeof(HostVariable<>).MakeSpecificType(paramType.GetElementType()).CreateInstance(args[index]);
}
}
}
if (TryWrappedBindAndInvoke(binder, AdjustInvokeArgs(args), out result))
{
if (paramTypes != null)
{
for (var index = 0; index < paramTypes.Length; index++)
{
if (paramTypes[index].IsByRef)
{
if (args[index] is IHostVariable hostVariable)
{
args[index] = hostVariable.Value;
}
}
}
}
return true;
}
return false;
}
private string[] GetAllPropertyNames()
{
return GetPropertyNames().Concat(GetPropertyIndices().Select(index => index.ToString(CultureInfo.InvariantCulture))).ToArray();
}
private static DynamicMetaObject PostProcessBindResult(DynamicMetaObject result)
{
var catchBody = Expression.Block(Expression.Call(throwLastScriptErrorMethod), Expression.Rethrow(), Expression.Default(result.Expression.Type));
return new DynamicMetaObject(Expression.TryCatchFinally(result.Expression, Expression.Call(clearLastScriptErrorMethod), Expression.Catch(typeof(Exception), catchBody)), result.Restrictions);
}
#region ScriptObject overrides
public override IEnumerable PropertyNames => GetPropertyNames();
public override IEnumerable PropertyIndices => GetPropertyIndices();
public new object this[string name, params object[] args]
{
get => GetProperty(name, args).ToDynamicResult(Engine);
set => SetProperty(name, args.Concat(value.ToEnumerable()).ToArray());
}
public new object this[int index]
{
get => GetProperty(index).ToDynamicResult(Engine);
set => SetProperty(index, value);
}
#endregion
#region DynamicObject overrides
public override DynamicMetaObject GetMetaObject(Expression param)
{
return new MetaScriptItem(base.GetMetaObject(param));
}
public override IEnumerable GetDynamicMemberNames()
{
return GetPropertyNames().Concat(GetPropertyIndices().Select(index => index.ToString(CultureInfo.InvariantCulture)));
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return TryWrappedBindAndInvoke(binder, ArrayHelpers.GetEmptyArray