/* * Copyright © 2011, Petro Protsyk, Denys Vuika * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Generic; using Scripting.SSharp.Runtime; using System; using System.Linq; using System.Reflection.Emit; using System.Reflection; namespace Scripting.SSharp.Parser.Ast { /// /// /// internal class ScriptFunctionDefinition : ScriptAst, IInvokable { public string Name { get; private set; } private readonly ScriptFuncParameters _parameters; private readonly ScriptFuncContract _contract; private readonly ScriptGlobalList _globalList; private readonly ScriptCompoundStatement _body; private IScriptContext _activeContext; private List _globalNames; //Field used to ensure consistency internal Script _owner; public ScriptFunctionDefinition(AstNodeArgs args) : base(args) { var funcName = ChildNodes[1] as TokenAst; var index = 0; if (funcName != null) { Name = funcName.Text; } else //Function expression { Name = null; index = 1; } if (ChildNodes.Count == 5-index) { _contract = ChildNodes[3 - index] as ScriptFuncContract; _parameters = ChildNodes[3 - index] as ScriptFuncParameters; } if (ChildNodes.Count == 6 - index) { _parameters = ChildNodes[2 - index] as ScriptFuncParameters; _globalList = ChildNodes[3 - index] as ScriptGlobalList; _contract = ChildNodes[4 - index] as ScriptFuncContract; } _body = (ScriptCompoundStatement)ChildNodes[ChildNodes.Count - 1]; _body.ShouldCreateScope = false; if (_contract != null) _contract._function = this; } public override void Evaluate(IScriptContext context) { if (Name != null) context.SetItem(Name, this); context.Result = this; } #region IInvokable Members public bool CanInvoke() { return true; } //TODO: Review this approach public object ThreadInvoke(IScriptContext context) { return Invoke(context, null); } public object Invoke(IScriptContext context, object[] args) { _activeContext = context; var result = RuntimeHost.NullValue; var functionScope = (INotifyingScope)RuntimeHost.ScopeFactory.Create(ScopeTypes.Function, context.Scope, context); context.CreateScope(functionScope); try { if (_parameters != null) { context.Result = args; _parameters.Evaluate(context); } _globalNames = GetGlobalNames(_activeContext); functionScope.BeforeSetItem += ScopeBeforeSetItem; if (_contract != null) { functionScope.AfterSetItem += CheckContractInvariant; _contract.CheckPre(context); _contract.CheckInv(context); } context.Result = RuntimeHost.NullValue; _body.Evaluate(context); result = context.Result; if (_contract != null) { functionScope.AfterSetItem -= CheckContractInvariant; _contract.CheckInv(context); _contract.CheckPost(context); } } finally { context.RemoveLocalScope(); context.ResetControlFlags(); context.Result = result; functionScope.BeforeSetItem -= ScopeBeforeSetItem; _activeContext = null; } return result; } private void ScopeBeforeSetItem(IScriptScope sender, ScopeArgs args) { if (_globalNames.Contains(args.Name)) { switch (args.Operation) { case ScopeOperation.Set: ScriptQualifiedName.SetToParentScope(sender.Parent, args.Name, args.Value); args.Cancel = true; break; case ScopeOperation.Create: throw new ScriptExecutionException(string.Format(Strings.LocalIdConflictWithGlobalList, args.Name)); default: break; } } } private void CheckContractInvariant(object sender, ScopeArgs args) { _contract.CheckInv(_activeContext); } private List GetGlobalNames(IScriptContext context) { var globalNames = new List(); if (_globalList != null) { _globalList.Evaluate(context); globalNames = new List((string[]) context.Result); } return globalNames; } internal FunctionDelegate AsDelegate(Type delegateType) { if (!typeof(Delegate).IsAssignableFrom(delegateType)) return null; MethodInfo delegateInvokeMethod = delegateType.GetMethod("Invoke"); Type[] tParameters = new Type[] { typeof(FunctionDelegate) }.Concat(delegateInvokeMethod.GetParameters().Select(p => p.ParameterType)).ToArray(); DynamicMethod dm = new DynamicMethod(ToString(), delegateInvokeMethod.ReturnType, tParameters, typeof(FunctionDelegate).Module); MethodInfo invokeMethod; if (delegateInvokeMethod.ReturnType == typeof(void)) invokeMethod = typeof(FunctionDelegate).GetMethod("VoidInvoke"); else invokeMethod = typeof(FunctionDelegate).GetMethod("Invoke").MakeGenericMethod(delegateInvokeMethod.ReturnType); ILGenerator il = dm.GetILGenerator(); il.DeclareLocal(typeof(object[])); //Create array il.Emit(OpCodes.Ldc_I4, tParameters.Length - 1); il.Emit(OpCodes.Newarr, typeof(object)); il.Emit(OpCodes.Stloc_0); for (int i = 0; i < tParameters.Length - 1; i++) { il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldc_I4, i); il.Emit(OpCodes.Ldarg, i+1); if (tParameters[i + 1].IsValueType) { il.Emit(OpCodes.Box, tParameters[i + 1]); } il.Emit(OpCodes.Stelem_Ref); } il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldloc_0); il.EmitCall(OpCodes.Call, invokeMethod, null); //convert to return type //il.Emit(OpCodes.Castclass, delegateInvokeMethod.ReturnType); //il.Emit(OpCodes.Unbox_Any); il.Emit(OpCodes.Ret); FunctionDelegate result = new FunctionDelegate(); result.Function = this; result.Method = dm.CreateDelegate(delegateType, result); return result; //DynamicMethod dm = new DynamicMethod(ToString(), // typeof(object), // new Type[] { typeof(IInvokable), typeof(IScriptContext), typeof(object[]) }, // typeof(IInvokable).Module); //MethodInfo invokeMethod = typeof(IInvokable) // .GetMethod("Invoke"); //ILGenerator il = dm.GetILGenerator(256); //il.Emit(OpCodes.Ldarg_0); //il.Emit(OpCodes.Ldarg_1); //il.Emit(OpCodes.Ldarg_2); //il.EmitCall(OpCodes.Call, invokeMethod, null); //il.Emit(OpCodes.Ret); //return dm.CreateDelegate(delegateType, this); } #endregion public override string ToString() { return Name == null ? "_anonimous_func" : "_func:" + Name; } } internal class FunctionDelegate { public ScriptFunctionDefinition Function { get; set; } public Delegate Method { get; set; } public IScriptContext ActiveContext { get; set; } public T Invoke(object[] args) { object rez = Function.Invoke(ActiveContext, args); return (T)rez; } public void VoidInvoke(object[] args) { object rez = Function.Invoke(ActiveContext, args); } } }