/*
* 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using Scripting.SSharp.Runtime;
using Scripting.SSharp.Runtime.Promotion;
namespace Scripting.SSharp.Parser.Ast
{
///
/// Qualified Name
///
internal class ScriptQualifiedName : ScriptExpr
{
#region Members
private readonly string _identifier;
private readonly List _modifiers;
private readonly ScriptQualifiedName _namePart;
private delegate void EvaluateFunction(IScriptContext context);
private delegate void AssignFunction(object value, IScriptContext context);
private readonly EvaluateFunction _evaluation;
private readonly AssignFunction _assignment;
public ReadOnlyCollection Modifiers
{
get
{
return new ReadOnlyCollection(_modifiers);
}
}
public ScriptQualifiedName NextPart
{
get
{
return _namePart;
}
}
internal bool NextFirst
{
get;
private set;
}
public string Identifier
{
get
{
return _identifier;
}
}
///
/// Identifies that node represents single variable
///
public bool IsVariable
{
get;
private set;
}
public bool IsGlobal
{
get;
private set;
}
public bool IsVar
{
get;
internal set;
}
#endregion
#region Constructor
public ScriptQualifiedName(AstNodeArgs args)
: base(args)
{
IsVariable = false;
NextFirst = false;
IsVar = false;
if (ChildNodes.Count == 2 && ChildNodes[1].ChildNodes.Count == 0)
{
_identifier = ExtractId(((TokenAst)ChildNodes[0]).Text);
IsVariable = true;
_evaluation = EvaluateIdentifier;
_assignment = AssignIdentifier;
}
else
if (ChildNodes[0] is TokenAst && ChildNodes[1].ChildNodes.Count != 0)
{
_identifier = ExtractId(((TokenAst)ChildNodes[0]).Text);
//NOTE: There might be two cases:
// 1) a()[]...()
// 2) a<>.(NamePart)
_modifiers = new List();
foreach (ScriptAst node in ChildNodes[1].ChildNodes)
_modifiers.Add(node);
var generic = _modifiers.FirstOrDefault() as ScriptGenericsPostfix;
if (generic != null && _modifiers.Count == 1)
{
//Case 2
_evaluation = EvaluateGenericType;
_assignment = null;
}
else
{
//Case 1
_evaluation = EvaluateFunctionCall;
_assignment = AssignArray;
}
}
else
{
_namePart = ChildNodes[0] as ScriptQualifiedName;
_identifier = ExtractId(((TokenAst)ChildNodes[2]).Text);
NextFirst = true;
if (ChildNodes.Count == 4 && ChildNodes[3].ChildNodes.Count != 0)
{
_modifiers = new List();
foreach (ScriptAst node in ChildNodes[3].ChildNodes)
{
_modifiers.Add(node);
}
}
_evaluation = EvaluateNamePart;
_assignment = AssignNamePart;
}
}
#endregion
#region Public Methods
public override void Evaluate(IScriptContext context)
{
_evaluation(context);
}
public void Assign(object value, IScriptContext context)
{
_assignment(value, context);
}
#endregion
#region Identifier
IValueReference _variable;
private void EvaluateIdentifier(IScriptContext context)
{
object result;
// if variable was previously cached and it still belongs to the given scope return the cached value
if (_variable != null && _variable.Scope.Equals(context.Scope))
{
result = _variable.Value;
}
else
{
result = GetIndentifierValue(context, _identifier);
}
context.Result = result;
}
private object GetIndentifierValue(IScriptContext context, string identifier)
{
//object result = context.GetItem(identifier, false);
//if (result != RuntimeHost.NoVariable) return result;
if (IsGlobal)
{
_variable = null;
IScriptScope scope = context.Scope.Parent;
while (scope != null)
{
if (scope.HasVariable(identifier))
{
return scope.GetItem(identifier, true);
}
scope = scope.Parent;
}
}
else
{
if (_variable != null && _variable.Value != null) return _variable.Value;
object result;
_variable = CreateRef(identifier, context, true, out result);
if (result != RuntimeHost.NoVariable)
{
return result;
}
}
return RuntimeHost.HasType(identifier)
? (object) RuntimeHost.GetType(identifier)
: NamespaceResolver.Get(identifier);
}
private void AssignIdentifier(object value, IScriptContext context)
{
if (IsGlobal)
{
SetToParentScope(context.Scope.Parent, _identifier, value);
_variable = null;
return;
}
if (IsVar && ((context.Scope is LocalScope) || (context.Scope is FunctionScope)))
{
context.Scope.CreateVariable(_identifier, value);
return;
}
if (_variable != null)
{
_variable.Value = value;
}
else
{
context.SetItem(_identifier, value);
object tmp;
_variable = CreateRef(_identifier, context, false, out tmp);
}
}
private IValueReference CreateRef(string id, IScriptContext context, bool resolve, out object value)
{
IValueReference result = context.Ref(id);
value = RuntimeHost.NoVariable;
if (result != null)
{
result.Removed += ReferenceRemoved;
if (resolve)
value = result.Value;
}
else
{
if (resolve)
value = context.GetItem(_identifier, false);
}
return result;
}
private void ReferenceRemoved(object sender, EventArgs e)
{
if (sender == _variable)
{
_variable.Removed -= ReferenceRemoved;
_variable = null;
}
}
///
/// Sets variable to the first scope in hierarchy which already has this variable
///
///
///
///
///
internal static void SetToParentScope(IScriptScope parent, string id, object value)
{
var scope = parent;
while (scope != null)
{
if (scope.HasVariable(id))
{
scope.SetItem(id, value);
return;
}
scope = scope.Parent;
}
throw new ScriptIdNotFoundException(string.Format(Strings.GlobalNameNotFound, id));
}
#endregion
#region Single Call
private void EvaluateGenericType(IScriptContext context)
{
var genericPostfix = (ScriptGenericsPostfix)_modifiers.First();
var genericType = GetIndentifierValue(context, genericPostfix.GetGenericTypeName(_identifier)) as Type;
if (genericType == null || !genericType.IsGenericType)
{
throw new ScriptExecutionException(string.Format(Strings.TypeIsNotGeneric, Code(context), genericType.Name));
}
genericPostfix.Evaluate(context);
context.Result = genericType.MakeGenericType((Type[])context.Result);
}
private void EvaluateFunctionCall(IScriptContext context)
{
EvaluateIdentifier(context);
foreach (var node in _modifiers)
{
var funcCall = node as ScriptFunctionCall;
if (funcCall != null)
{
var function = context.Result as IInvokable;
if (function == null)
throw new ScriptExecutionException(string.Format(Strings.ObjectDoesNotImplementIInvokable, Code(context), node.Code(context), Span.Start.Line, Span.Start.Position));
context.Result = CallFunction(function, funcCall, context);
continue;
}
var arrayResolution = node as ScriptArrayResolution;
if (arrayResolution != null)
{
GetArrayValue(context.Result, arrayResolution, context);
continue;
}
var genericPostfix = node as ScriptGenericsPostfix;
if (genericPostfix != null)
{
throw new NotSupportedException();
//genericPostfix.Evaluate(Context);
//continue;
}
}
}
private void AssignArray(object value, IScriptContext context)
{
var obj = context.GetItem(_identifier, true);
foreach (var node in _modifiers)
{
var functionCall = node as ScriptFunctionCall;
if (functionCall != null)
{
obj = CallFunction(context.GetFunctionDefinition(_identifier), functionCall, context);
continue;
}
var arrayResolution = node as ScriptArrayResolution;
if (arrayResolution != null)
{
SetArrayValue(obj, arrayResolution, context, value);
continue;
}
var genericPostfix = node as ScriptGenericsPostfix;
if (genericPostfix != null)
{
throw new NotSupportedException();
}
}
}
private static void SetArrayValue(object obj, ScriptArrayResolution scriptArrayResolution, IScriptContext context, object value) {
scriptArrayResolution.Evaluate(context);
object[] indexParameters = (object[])context.Result;
object[] setterParameters = new object[indexParameters.Length + 1];
indexParameters.CopyTo(setterParameters, 0);
setterParameters[indexParameters.Length] = value;
IBinding setter = RuntimeHost.Binder.BindToIndex(obj, setterParameters, true);
if (setter != null) {
setter.Invoke(context, null);
return;
}
throw MethodNotFoundException("setter", indexParameters);
}
private static void GetArrayValue(object obj, ScriptArrayResolution scriptArrayResolution, IScriptContext context) {
scriptArrayResolution.Evaluate(context);
object[] param = (object[])context.Result;
IBinding indexBind = RuntimeHost.Binder.BindToIndex(obj, param, false);
if (indexBind != null) {
context.Result = indexBind.Invoke(context, null);
} else {
throw MethodNotFoundException("indexer[]", param);
}
}
//NOTE: This code contains bug, when dealing with multi-dimensional arrays
//private static void SetArrayValue(object obj, ScriptArrayResolution scriptArrayResolution, IScriptContext context, object value)
//{
// scriptArrayResolution.Evaluate(context);
// var indexParameters = (object[])context.Result;
// // Denis: don't remove (dynamic) casting whatever R# tells you
// ((dynamic)obj)[(dynamic)indexParameters[0]] = (dynamic)value;
//}
//private static void GetArrayValue(object obj, ScriptArrayResolution scriptArrayResolution, IScriptContext context)
//{
// scriptArrayResolution.Evaluate(context);
// var param = (object[])context.Result;
// context.Result = ((dynamic)obj)[(dynamic)param[0]];
//}
#endregion
#region Name Part
private void EvaluateNamePart(IScriptContext context)
{
_namePart.Evaluate(context);
object obj = context.Result;
bool firstResolution = false;
if (_modifiers == null)
{
context.Result = GetMemberValue(obj, _identifier);
return;
}
Type[] genericArguments = null;
foreach (ScriptAst node in _modifiers)
{
//NOTE: Generic modifier should be the first among other modifiers in the list
var generic = node as ScriptGenericsPostfix;
if (generic != null)
{
if (genericArguments != null)
{
throw new ScriptSyntaxErrorException(string.Format(Strings.WrongSequenceOfModifiers, Code(context)));
}
generic.Execute(context);
genericArguments = (Type[])context.Result;
continue;
}
var functionCall = node as ScriptFunctionCall;
if (functionCall != null)
{
CallClassMethod(obj, _identifier, functionCall, genericArguments, context);
obj = context.Result;
continue;
}
var arrayResolution = node as ScriptArrayResolution;
if (arrayResolution != null)
{
if (!firstResolution)
{
obj = GetMemberValue(obj, _identifier);
firstResolution = true;
}
GetArrayValue(obj, arrayResolution, context);
obj = context.Result;
continue;
}
}
}
private void AssignNamePart(object value, IScriptContext context)
{
_namePart.Evaluate(context);
var obj = context.Result;
if (_modifiers == null)
{
SetMember(context, obj, value);
return;
}
//TODO: Bug, first evaluate get member, see unit test AssignmentToArrayObject
string localIdentifier = _identifier;
for (int index=0; index<_modifiers.Count; index++)
{
var scriptFunctionCall = _modifiers[index] as ScriptFunctionCall;
if (scriptFunctionCall != null)
{
if (localIdentifier != null)
{
CallClassMethod(obj, localIdentifier, scriptFunctionCall, null, context);
obj = context.Result;
localIdentifier = null;
}
else
{
var funcDef = obj as IInvokable;
if (funcDef == null) throw new ScriptExecutionException(string.Format(Strings.ObjectDoesNotImplementIInvokable, Code(context), "", Span.Start.Line, Span.Start.Position));
obj = CallFunction(funcDef, scriptFunctionCall, context);
}
continue;
}
var scriptArrayResolution = _modifiers[index] as ScriptArrayResolution;
if (scriptArrayResolution != null)
{
if (localIdentifier != null)
{
obj = GetMemberValue(obj, localIdentifier);
localIdentifier = null;
}
if (index == _modifiers.Count - 1)
{
SetArrayValue(obj, scriptArrayResolution, context, value);
}
else
{
GetArrayValue(obj, scriptArrayResolution, context);
obj = context.Result;
}
continue;
}
}
}
#endregion
#region Call Function
private static object CallFunction(IInvokable functionDefinition, ScriptFunctionCall scriptFunctionCall, IScriptContext context)
{
scriptFunctionCall.Evaluate(context);
return functionDefinition.Invoke(context, (object[])context.Result);
}
private static void CallClassMethod(object obj, string memeberInfo, ScriptFunctionCall scriptFunctionCall, Type[] genericArguments, IScriptContext context)
{
scriptFunctionCall.Evaluate(context);
context.Result = CallAppropriateMethod(context, obj, memeberInfo, genericArguments, (object[])context.Result);
}
private static object CallAppropriateMethod(IScriptContext context, object obj, string name, Type[] genericArguments, object[] param)
{
var methodBind = RuntimeHost.Binder.BindToMethod(obj, name, genericArguments, param);
if (methodBind != null)
return methodBind.Invoke(context, null);
throw MethodNotFoundException(name, param);
}
#endregion
#region Helpers
private static readonly char[] IdSeparator = new[] { ':' };
private string ExtractId(string id)
{
var parts = id.Split(IdSeparator);
if (parts.Length == 1) return parts[0];
IsGlobal = true;
return parts[1];
}
private void SetMember(IScriptContext context, object obj, object value)
{
// Check whether we are setting member value for a dynamic object
var dynamicObject = obj as DynamicObject;
if (dynamicObject != null)
{
// Get or create a new call site for a setter
var setter = CallSiteCache.GetOrCreatePropertySetter(_identifier);
if (setter == null) throw new ScriptIdNotFoundException(_identifier);
// set property value for the dynamic object
setter.Target(setter, obj, value);
}
else
{
IMemberBinding bind = RuntimeHost.Binder.BindToMember(obj, _identifier, true);
if (bind == null) throw new ScriptIdNotFoundException(_identifier);
bind.SetValue(value);
}
context.Result = value;
}
private static object GetMemberValue(object obj, string memberInfo)
{
// Check whether we are getting member value from a dynamic object
var dynamicObject = obj as DynamicObject;
if (dynamicObject != null)
{
// Get or create a new call site for property getter
var getter = CallSiteCache.GetOrCreatePropertyGetter(memberInfo);
if (getter == null) throw new ScriptIdNotFoundException(memberInfo);
// get property value from dynamic object
return getter.Target(getter, obj);
}
var bind = RuntimeHost.Binder.BindToMember(obj, memberInfo, true);
if (bind == null)
throw new ScriptIdNotFoundException(memberInfo);
return bind.GetValue();
}
private static ScriptMethodNotFoundException MethodNotFoundException(string name, IEnumerable