#region using
using System.Collections.Generic;
using Irony.Compiler;
using ScriptNET.Runtime;
#endregion
namespace ScriptNET.Ast
{
///
///
///
internal class ScriptFunctionDefinition : ScriptAst, IInvokable
{
public string Name { get; private set; }
private ScriptFuncParameters Parameters;
private ScriptFuncContract Contract;
private ScriptGlobalList GlobalList;
private ScriptAst Body;
private IScriptContext activeContext;
public ScriptFunctionDefinition(AstNodeArgs args)
: base(args)
{
Token funcName = ChildNodes[1] as Token;
int 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 = (ScriptAst)ChildNodes[ChildNodes.Count - 1];
}
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;
object result = RuntimeHost.NullValue;
INotifyingScope functionScope = (INotifyingScope)RuntimeHost.ScopeFactory.Create(ScopeTypes.Function, context.Scope, context);
context.CreateScope(functionScope);
try
{
if (Parameters != null)
{
context.Result = args;
Parameters.Evaluate(context);
}
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.SetBreak(false);
context.SetContinue(false);
context.SetReturn(false);
context.Result = result;
functionScope.BeforeSetItem -= ScopeBeforeSetItem;
activeContext = null;
}
return result;
}
private void ScopeBeforeSetItem(IScriptScope sender, ScopeArgs args)
{
//TODO: Performance improvement. Should be evaluated once per function call
List globalNames = GetGlobalNames(activeContext);
if (globalNames.Contains(args.Name))
{
SetToParentScope(sender.Parent, args.Name, args.Value);
args.Cancel = true;
}
//if (!sender.HasVariable(args.Name))
//{
// args.Cancel = SetToParentScope(sender.Parent, args.Name, args.Value);
//}
}
private void CheckContractInvariant(object sender, ScopeArgs args)
{
Contract.CheckInv(activeContext);
}
///
/// Sets variable to the first scope in hierarchy which already has this variable
///
///
///
///
private static void SetToParentScope(IScriptScope parent, string id, object value)
{
IScriptScope scope = parent;
while (scope != null)
{
if (scope.HasVariable(id))
{
scope.SetItem(id, value);
return;
}
scope = scope.Parent;
}
throw new ScriptIdNotFoundException(string.Format("Global name {0} was not found in scopes", id));
//parent.SetItem(id, value);
}
private List GetGlobalNames(IScriptContext context)
{
List globalNames = new List();
if (GlobalList != null)
{
GlobalList.Evaluate(context);
globalNames = new List((string[]) context.Result);
}
return globalNames;
}
#endregion
public override string ToString()
{
return Name == null ? "_anonimous_func" : "_func:" + Name;
}
}
}