/// <summary> /// Evaluates the specified expression /// </summary> /// <param name="targetExpression">The expression to evaluate</param> /// <param name="valueToSet">A value to set in case of a setter</param> /// <param name="setter">true if this a setter</param> /// <returns>The value of the targetExpression</returns> private object GetOrSetValue(ScriptExpression targetExpression, object valueToSet, bool setter) { object value = null; try { if (targetExpression is IScriptVariablePath nextPath) { if (setter) { nextPath.SetValue(this, valueToSet); } else { value = nextPath.GetValue(this); } } else if (!setter) { value = Evaluate(targetExpression); } else { throw new ScriptRuntimeException(targetExpression.Span, $"Unsupported target expression for assignment."); // unit test: 105-assign-error1.txt } } catch (Exception readonlyException) when(_getOrSetValueLevel == 1 && !(readonlyException is ScriptRuntimeException)) { throw new ScriptRuntimeException(targetExpression.Span, $"Unexpected exception while accessing target expression: {readonlyException.Message}", readonlyException); } // If the variable being returned is a function, we need to evaluate it // If function call is disabled, it will be only when returning the final object (level 0 of recursion) var allowFunctionCall = (_isFunctionCallDisabled && _getOrSetValueLevel > 1) || !_isFunctionCallDisabled; if (allowFunctionCall && ScriptFunctionCall.IsFunction(value)) { // Allow to pipe arguments only for top level returned function value = ScriptFunctionCall.Call(this, targetExpression, value, _getOrSetValueLevel == 1, null); } return(value); }
/// <summary> /// Evaluates the specified expression /// </summary> /// <param name="targetExpression">The expression to evaluate</param> /// <param name="valueToSet">A value to set in case of a setter</param> /// <param name="setter">true if this a setter</param> /// <param name="level">The indirection level (0 before entering the expression)</param> /// <returns>The value of the targetExpression</returns> private object GetOrSetValue(ScriptExpression targetExpression, object valueToSet, bool setter, int level) { object value = null; try { var nextVariable = targetExpression as ScriptVariable; if (nextVariable != null) { if (setter) { SetValue(nextVariable, valueToSet, false); } else { value = GetValueFromVariable(nextVariable); } } else { if (targetExpression is ScriptMemberExpression nextDot) { var targetObject = GetOrSetValue(nextDot.Target, valueToSet, false, level + 1); if (targetObject == null) { throw new ScriptRuntimeException(nextDot.Span, $"Object [{nextDot.Target}] is null. Cannot access member: {nextDot}"); // unit test: 131-member-accessor-error1.txt } if (targetObject is string || targetObject.GetType().GetTypeInfo().IsPrimitive) { throw new ScriptRuntimeException(nextDot.Span, $"Cannot get or set a member on the primitive [{targetObject}/{targetObject.GetType()}] when accessing member: {nextDot}"); // unit test: 132-member-accessor-error2.txt } var accessor = GetMemberAccessor(targetObject); var memberName = nextDot.Member.Name; if (setter) { if (!accessor.TrySetValue(this, targetExpression.Span, targetObject, memberName, valueToSet)) { throw new ScriptRuntimeException(nextDot.Member.Span, $"Cannot set a value for the readonly member: {nextDot}"); // unit test: 132-member-accessor-error3.txt } } else { if (!accessor.TryGetValue(this, targetExpression.Span, targetObject, memberName, out value)) { TryGetMember?.Invoke(this, targetExpression.Span, targetObject, memberName, out value); } } } else { if (targetExpression is ScriptIndexerExpression nextIndexer) { var targetObject = GetOrSetValue(nextIndexer.Target, valueToSet, false, level + 1); if (targetObject == null) { throw new ScriptRuntimeException(nextIndexer.Target.Span, $"Object [{nextIndexer.Target}] is null. Cannot access indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error1.txt } else { var index = Evaluate(nextIndexer.Index); if (index == null) { throw new ScriptRuntimeException(nextIndexer.Index.Span, $"Cannot access target [{nextIndexer.Target}] with a null indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error2.txt } else { if (targetObject is IDictionary || targetObject is ScriptObject) { var accessor = GetMemberAccessor(targetObject); var indexAsString = ToString(nextIndexer.Index.Span, index); if (setter) { if (!accessor.TrySetValue(this, targetExpression.Span, targetObject, indexAsString, valueToSet)) { throw new ScriptRuntimeException(nextIndexer.Index.Span, $"Cannot set a value for the readonly member [{indexAsString}] in the indexer: {nextIndexer.Target}['{indexAsString}']"); // unit test: 130-indexer-accessor-error3.txt } } else { if (!accessor.TryGetValue(this, targetExpression.Span, targetObject, indexAsString, out value)) { TryGetMember?.Invoke(this, targetExpression.Span, targetObject, indexAsString, out value); } } } else { var accessor = GetListAccessor(targetObject); if (accessor == null) { throw new ScriptRuntimeException(nextIndexer.Target.Span, $"Expecting a list. Invalid value [{targetObject}/{targetObject.GetType().Name}] for the target [{nextIndexer.Target}] for the indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error4.txt } else { int i = ToInt(nextIndexer.Index.Span, index); // Allow negative index from the end of the array if (i < 0) { i = accessor.GetLength(this, targetExpression.Span, targetObject) + i; } if (i >= 0) { if (setter) { accessor.SetValue(this, targetExpression.Span, targetObject, i, valueToSet); } else { value = accessor.GetValue(this, targetExpression.Span, targetObject, i); } } } } } } } else if (!setter) { value = Evaluate(targetExpression); } else { throw new ScriptRuntimeException(targetExpression.Span, $"Unsupported expression for target for assignment: {targetExpression} = ..."); // unit test: 105-assign-error1.txt } } } } catch (Exception readonlyException) when(level == 0 && !(readonlyException is ScriptRuntimeException)) { throw new ScriptRuntimeException(targetExpression.Span, $"Unexpected exception while accessing `{targetExpression}`", readonlyException); } // If the variable being returned is a function, we need to evaluate it // If function call is disabled, it will be only when returning the final object (level 0 of recursion) if ((!_isFunctionCallDisabled || level > 0) && ScriptFunctionCall.IsFunction(value)) { value = ScriptFunctionCall.Call(this, targetExpression, value); } return(value); }