/// <summary> /// The evaluates a string as a scriban expression or evaluate the passed function or return the passed value. /// </summary> /// <param name="context">The template context</param> /// <param name="span">The source span</param> /// <param name="value">The input value, either a scriban template in a string, or an alias function or directly a value.</param> /// <returns>The evaluation of the input value.</returns> /// <remarks> /// ```scriban-html /// {{ "1 + 2" | object.eval }} /// ``` /// ```html /// 3 /// ``` /// </remarks> public static object Eval(TemplateContext context, SourceSpan span, object value) { if (value == null) { return(null); } if (value is string templateStr) { try { var template = Template.Parse(templateStr, lexerOptions: new LexerOptions() { Lang = context.Language, Mode = ScriptMode.ScriptOnly }); return(context.Evaluate(template.Page)); } catch (Exception ex) { throw new ArgumentException(ex.Message, nameof(value)); } } if (value is IScriptCustomFunction function) { return(ScriptFunctionCall.Call(context, context.CurrentNode, function, false, null)); } return(value); }
private static IEnumerable EachInternal(TemplateContext context, ScriptNode callerContext, SourceSpan span, IEnumerable list, IScriptCustomFunction function, Type destType) { var arg = new ScriptArray(1); foreach (var item in list) { var itemToTransform = context.ToObject(span, item, destType); arg[0] = itemToTransform; var itemTransformed = ScriptFunctionCall.Call(context, callerContext, function, arg); yield return(itemTransformed); } }
static IEnumerable FilterInternal(TemplateContext context, SourceSpan span, IEnumerable list, IScriptCustomFunction function, Type destType) { var arg = new ScriptArray(1); foreach (var item in list) { var itemToTransform = context.ToObject(span, item, destType); arg[0] = itemToTransform; var itemTransformed = ScriptFunctionCall.Call(context, context.CurrentNode, function, arg); if (context.ToBool(span, itemTransformed)) { yield return(itemToTransform); } } }
/// <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> /// The evaluates a string as a scriban template or evaluate the passed function or return the passed value. /// </summary> /// <param name="context">The template context</param> /// <param name="span">The source span</param> /// <param name="value">The input value, either a scriban template in a string, or an alias function or directly a value.</param> /// <returns>The evaluation of the input value.</returns> /// <remarks> /// ```scriban-html /// {{ "This is a template text {{ 1 + 2 }}" | object.eval_template }} /// ``` /// ```html /// This is a template text 3 /// ``` /// </remarks> public static object EvalTemplate(TemplateContext context, SourceSpan span, object value) { if (value == null) { return(null); } if (value is string templateStr) { try { var template = Template.Parse(templateStr, lexerOptions: new LexerOptions() { Lang = context.Language, Mode = ScriptMode.Default }); var output = new StringBuilderOutput(); context.PushOutput(output); try { context.Evaluate(template.Page); } finally { context.PopOutput(); } return(output.ToString()); } catch (Exception ex) { throw new ArgumentException(ex.Message, nameof(value)); } } if (value is IScriptCustomFunction function) { return(ScriptFunctionCall.Call(context, context.CurrentNode, function, false, null)); } return(value); }
public static string Join(TemplateContext context, SourceSpan span, IEnumerable list, string delimiter, object function = null) { if (list == null) { return(string.Empty); } var scriptingFunction = function as IScriptCustomFunction; if (function != null && scriptingFunction == null) { throw new ArgumentException($"The parameter `{function}` is not a function. Maybe prefix it with @?", nameof(function)); } var text = new StringBuilder(); bool afterFirst = false; var arg = new ScriptArray(1); foreach (var obj in list) { if (afterFirst) { text.Append(delimiter); } var item = context.ObjectToString(obj); if (scriptingFunction != null) { arg[0] = item; var result = ScriptFunctionCall.Call(context, context.CurrentNode, scriptingFunction, arg); item = context.ObjectToString(result); } text.Append(item); afterFirst = true; } return(text.ToString()); }
public override void Evaluate(TemplateContext context) { // Check that the Target is actually a function var functionCall = Target as ScriptFunctionCall; if (functionCall == null) { var parameterLessFunction = context.Evaluate(Target, true); if (!(parameterLessFunction is IScriptCustomFunction)) { var targetPrettyname = ScriptSyntaxAttribute.Get(Target); throw new ScriptRuntimeException(Target.Span, $"Expecting a direct function instead of the expression [{Target}/{targetPrettyname.Name}]"); } context.BlockDelegates.Push(Body); context.Result = ScriptFunctionCall.Call(context, this, parameterLessFunction); } else { context.BlockDelegates.Push(Body); context.Result = context.Evaluate(functionCall); } }
/// <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); }