Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1696 lines (1536 sloc) 68.6 KB
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#if SCRIBAN_ASYNC
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Scriban.Helpers;
using Scriban.Parsing;
using Scriban.Runtime;
using Scriban.Syntax;
namespace Scriban
{
/// <summary>
/// Basic entry point class to parse templates and render them. For more advanced scenario, you should use <see cref="TemplateContext"/> directly.
/// </summary>
public partial class Template
{
/// <summary>
/// Evaluates the template using the specified context. See remarks.
/// </summary>
/// <param name="context">The template context.</param>
/// <param name="render"><c>true</c> to render the output to the <see cref="TemplateContext.Output"/></param>
/// <exception cref="System.ArgumentNullException">If context is null</exception>
/// <exception cref="System.InvalidOperationException">If the template <see cref="HasErrors"/>. Check the <see cref="Messages"/> property for more details</exception>
private async ValueTask<object> EvaluateAndRenderAsync(TemplateContext context, bool render)
{
if (context == null) throw new ArgumentNullException(nameof(context));
CheckErrors();
// Make sure that we are using the same parserOptions
if (SourceFilePath != null)
{
context.PushSourceFile(SourceFilePath);
}
try
{
var result = await context.EvaluateAsync(Page).ConfigureAwait(false);
if (render)
{
if (Page != null && context.EnableOutput && result != null)
{
await context.WriteAsync(Page.Span, result).ConfigureAwait(false);
}
}
return result;
}
finally
{
if (SourceFilePath != null)
{
context.PopSourceFile();
}
}
}
/// <summary>
/// Evaluates the template using the specified context. See remarks.
/// </summary>
/// <param name="context">The template context.</param>
/// <exception cref="System.ArgumentNullException">If context is null</exception>
/// <exception cref="System.InvalidOperationException">If the template <see cref="HasErrors"/>. Check the <see cref="Messages"/> property for more details</exception>
/// <returns>Returns the result of the last statement</returns>
public async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var previousOutput = context.EnableOutput;
try
{
context.EnableOutput = false;
return await EvaluateAndRenderAsync(context, false).ConfigureAwait(false);
}
finally
{
context.EnableOutput = previousOutput;
}
}
/// <summary>
/// Parse and evaluates a code only expression (without enclosing `{{` and `}}`) within the specified context.
/// </summary>
/// <param name="expression">A code only expression (without enclosing `{{` and `}}`)</param>
/// <param name="context">The template context</param>
/// <returns>The result of the evaluation of the expression</returns>
public static async ValueTask<object> EvaluateAsync(string expression, TemplateContext context)
{
if (expression == null) throw new ArgumentNullException(nameof(expression));
var lexerOption = new LexerOptions() {Mode = ScriptMode.ScriptOnly};
var template = Parse(expression, lexerOptions: lexerOption);
return await template.EvaluateAsync(context).ConfigureAwait(false);
}
/// <summary>
/// Evaluates the template using the specified context
/// </summary>
/// <param name="model">An object model to use with the evaluation.</param>
/// <param name="memberRenamer">The member renamer used to import this .NET object and transitive objects. See member renamer documentation for more details.</param>
/// <param name="memberFilter">The member filter used to filter members for .NET objects being accessed through the template, including the model being passed to this method.</param>
/// <exception cref="System.InvalidOperationException">If the template <see cref="HasErrors"/>. Check the <see cref="Messages"/> property for more details</exception>
/// <returns>Returns the result of the last statement</returns>
public async ValueTask<object> EvaluateAsync(object model = null, MemberRenamerDelegate memberRenamer = null, MemberFilterDelegate memberFilter = null)
{
var scriptObject = new ScriptObject();
if (model != null)
{
scriptObject.Import(model, renamer: memberRenamer, filter: memberFilter);
}
var context = new TemplateContext
{
EnableOutput = false,
MemberRenamer = memberRenamer,
MemberFilter = memberFilter
};
context.PushGlobal(scriptObject);
var result = await EvaluateAsync(context).ConfigureAwait(false);
context.PopGlobal();
return result;
}
/// <summary>
/// Parse and evaluates a code only expression (without enclosing `{{` and `}}`) within the specified context.
/// </summary>
/// <param name="expression">A code only expression (without enclosing `{{` and `}}`)</param>
/// <param name="model">An object instance used as a model for evaluating this expression</param>
/// <param name="memberRenamer">The member renamer used to import this .NET object and transitive objects. See member renamer documentation for more details.</param>
/// <param name="memberFilter">The member filter used to filter members for .NET objects being accessed through the template, including the model being passed to this method.</param>
/// <returns>The result of the evaluation of the expression</returns>
public static async ValueTask<object> EvaluateAsync(string expression, object model, MemberRenamerDelegate memberRenamer = null, MemberFilterDelegate memberFilter = null)
{
if (expression == null) throw new ArgumentNullException(nameof(expression));
var lexerOption = new LexerOptions() { Mode = ScriptMode.ScriptOnly };
var template = Parse(expression, lexerOptions: lexerOption);
return await template.EvaluateAsync(model, memberRenamer, memberFilter).ConfigureAwait(false);
}
/// <summary>
/// Renders this template using the specified context. See remarks.
/// </summary>
/// <param name="context">The template context.</param>
/// <exception cref="System.ArgumentNullException">If context is null</exception>
/// <exception cref="System.InvalidOperationException">If the template <see cref="HasErrors"/>. Check the <see cref="Messages"/> property for more details</exception>
/// <remarks>
/// When using this method, the result of rendering this page is output to <see cref="TemplateContext.Output"/>
/// </remarks>
public async ValueTask<string> RenderAsync(TemplateContext context)
{
await EvaluateAndRenderAsync(context, true).ConfigureAwait(false);
var result = context.Output.ToString();
var output = context.Output as StringBuilderOutput;
if (output != null)
{
output.Builder.Length = 0;
}
return result;
}
/// <summary>
/// Renders this template using the specified object model.
/// </summary>
/// <param name="model">The object model.</param>
/// <param name="memberRenamer">The member renamer used to import this .NET object and transitive objects. See member renamer documentation for more details.</param>
/// <param name="memberFilter">The member filter used to filter members for .NET objects being accessed through the template, including the model being passed to this method.</param>
/// <returns>A rendering result as a string </returns>
public async ValueTask<string> RenderAsync(object model = null, MemberRenamerDelegate memberRenamer = null, MemberFilterDelegate memberFilter = null)
{
var scriptObject = new ScriptObject();
if (model != null)
{
scriptObject.Import(model, renamer: memberRenamer, filter: memberFilter);
}
var context = LexerOptions.Mode == ScriptMode.Liquid ? new LiquidTemplateContext() : new TemplateContext();
context.MemberRenamer = memberRenamer;
context.MemberFilter = memberFilter;
context.PushGlobal(scriptObject);
return await RenderAsync(context).ConfigureAwait(false);
}
}
/// <summary>
/// The template context contains the state of the page, the model.
/// </summary>
public partial class TemplateContext
{
/// <summary>
/// Evaluates the specified script node.
/// </summary>
/// <param name="scriptNode">The script node.</param>
/// <param name="aliasReturnedFunction">if set to <c>true</c> and a function would be evaluated as part of this node, return the object function without evaluating it.</param>
/// <returns>The result of the evaluation.</returns>
public async ValueTask<object> EvaluateAsync(ScriptNode scriptNode, bool aliasReturnedFunction)
{
var previousFunctionCallState = _isFunctionCallDisabled;
var previousLevel = _getOrSetValueLevel;
try
{
_getOrSetValueLevel = 0;
_isFunctionCallDisabled = aliasReturnedFunction;
return await EvaluateImplAsync(scriptNode).ConfigureAwait(false);
}
finally
{
_getOrSetValueLevel = previousLevel;
_isFunctionCallDisabled = previousFunctionCallState;
}
}
/// <summary>
/// Evaluates the specified script node.
/// </summary>
/// <param name="scriptNode">The script node.</param>
/// <returns>The result of the evaluation.</returns>
public async ValueTask<object> EvaluateAsync(ScriptNode scriptNode)
{
return await EvaluateAsync(scriptNode, false).ConfigureAwait(false);
}
/// <summary>
/// Evaluates the specified script node by calling <see cref="ScriptNode.Evaluate"/>
/// </summary>
/// <param name="scriptNode">The script node (might be null but should not throw an error)</param>
/// <returns>The result of the evaluation</returns>
/// <remarks>The purpose of this method is to allow to hook during the evaluation of all ScriptNode. By default calls <see cref="ScriptNode.Evaluate"/></remarks>
protected virtual async ValueTask<object> EvaluateImplAsync(ScriptNode scriptNode)
{
return scriptNode != null ? await scriptNode.EvaluateAsync(this) .ConfigureAwait(false): null;
}
/// <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 async ValueTask<object> GetOrSetValueAsync(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 = await EvaluateAsync(targetExpression).ConfigureAwait(false);
}
else
{
throw new ScriptRuntimeException(targetExpression.Span, $"Unsupported expression for target for assignment: {targetExpression} = ..."); // unit test: 105-assign-error1.txt
}
}
catch (Exception readonlyException) when(_getOrSetValueLevel == 1 && !(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)
var allowFunctionCall = (_isFunctionCallDisabled && _getOrSetValueLevel > 1) || !_isFunctionCallDisabled;
if (allowFunctionCall && ScriptFunctionCall.IsFunction(value))
{
// Allow to pipe arguments only for top level returned function
value = await ScriptFunctionCall.CallAsync(this, targetExpression, value, _getOrSetValueLevel == 1).ConfigureAwait(false);
}
return value;
}
/// <summary>
/// Gets the value from the specified expression using the current <see cref="ScriptObject"/> bound to the model context.
/// </summary>
/// <param name="target">The expression</param>
/// <returns>The value of the expression</returns>
public async ValueTask<object> GetValueAsync(ScriptExpression target)
{
_getOrSetValueLevel++;
try
{
return await GetOrSetValueAsync(target, null, false).ConfigureAwait(false);
}
finally
{
_getOrSetValueLevel--;
}
}
/// <summary>
/// Sets the target expression with the specified value.
/// </summary>
/// <param name="target">The target expression.</param>
/// <param name="value">The value.</param>
/// <exception cref="System.ArgumentNullException">If target is null</exception>
public async ValueTask SetValueAsync(ScriptExpression target, object value)
{
if (target == null) throw new ArgumentNullException(nameof(target));
_getOrSetValueLevel++;
try
{
await GetOrSetValueAsync(target, value, true).ConfigureAwait(false);
}
finally
{
_getOrSetValueLevel--;
}
}
/// <summary>
/// Writes the text to the current <see cref="Output"/>
/// </summary>
/// <param name="text">The text.</param>
/// <param name="startIndex">The zero-based position of the substring of text</param>
/// <param name="count">The number of characters to output starting at <paramref name="startIndex"/> position from the text</param>
public async ValueTask<TemplateContext> WriteAsync(string text, int startIndex, int count)
{
if (text != null)
{
await Output.WriteAsync(text, startIndex, count, CancellationToken).ConfigureAwait(false);
}
return this;
}
/// <summary>
/// Writes the text to the current <see cref="Output"/>
/// </summary>
/// <param name="text">The text.</param>
public async ValueTask<TemplateContext> WriteAsync(string text)
{
if (text != null)
{
await Output.WriteAsync(text, CancellationToken).ConfigureAwait(false);
}
return this;
}
/// <summary>
/// Writes an object value to the current <see cref="Output"/>.
/// </summary>
/// <param name="span">The span of the object to render.</param>
/// <param name="textAsObject">The text as object.</param>
public async ValueTask<TemplateContext> WriteAsync(SourceSpan span, object textAsObject)
{
if (textAsObject != null)
{
var text = ToString(span, textAsObject);
await WriteAsync(text).ConfigureAwait(false);
}
return this;
}
/// <summary>
/// Writes the a new line to the current <see cref="Output"/>
/// </summary>
public async ValueTask<TemplateContext> WriteLineAsync()
{
await Output.WriteAsync(NewLine, CancellationToken).ConfigureAwait(false);
return this;
}
}
}
namespace Scriban.Functions
{
/// <summary>
/// The include function available through the function 'include' in scriban.
/// </summary>
public sealed partial class IncludeFunction {
public async ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
{
if (arguments.Count == 0)
{
throw new ScriptRuntimeException(callerContext.Span, "Expecting at least the name of the template to include for the <include> function");
}
var templateName = context.ToString(callerContext.Span, arguments[0]);
// If template name is empty, throw an exception
if (string.IsNullOrEmpty(templateName))
{
// In a liquid template context, we let an include to continue without failing
if (context is LiquidTemplateContext)
{
return null;
}
throw new ScriptRuntimeException(callerContext.Span, $"Include template name cannot be null or empty");
}
var templateLoader = context.TemplateLoader;
if (templateLoader == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"Unable to include <{templateName}>. No TemplateLoader registered in TemplateContext.TemplateLoader");
}
string templatePath;
try
{
templatePath = templateLoader.GetPath(context, callerContext.Span, templateName);
}
catch (Exception ex) when (!(ex is ScriptRuntimeException))
{
throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception while getting the path for the include name `{templateName}`", ex);
}
// If template path is empty (probably because template doesn't exist), throw an exception
if (templatePath == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"Include template path is null for `{templateName}");
}
// Compute a new parameters for the include
var newParameters = new ScriptArray(arguments.Count - 1);
for (int i = 1; i < arguments.Count; i++)
{
newParameters[i] = arguments[i];
}
context.SetValue(ScriptVariable.Arguments, newParameters, true);
Template template;
if (!context.CachedTemplates.TryGetValue(templatePath, out template))
{
string templateText;
try
{
templateText = await templateLoader.LoadAsync(context, callerContext.Span, templatePath).ConfigureAwait(false);
}
catch (Exception ex) when (!(ex is ScriptRuntimeException))
{
throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception while loading the include `{templateName}` from path `{templatePath}`", ex);
}
if (templateText == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"The result of including `{templateName}->{templatePath}` cannot be null");
}
// Clone parser options
var parserOptions = context.TemplateLoaderParserOptions;
var lexerOptions = context.TemplateLoaderLexerOptions;
template = Template.Parse(templateText, templatePath, parserOptions, lexerOptions);
// If the template has any errors, throw an exception
if (template.HasErrors)
{
throw new ScriptParserRuntimeException(callerContext.Span, $"Error while parsing template `{templateName}` from `{templatePath}`", template.Messages);
}
context.CachedTemplates.Add(templatePath, template);
}
// Make sure that we cannot recursively include a template
context.PushOutput();
object result = null;
try
{
context.EnterRecursive(callerContext);
result = await template.RenderAsync(context).ConfigureAwait(false);
context.ExitRecursive(callerContext);
}
finally
{
context.PopOutput();
}
return result;
}
}
}
namespace Scriban.Runtime
{
public abstract partial class DynamicCustomFunction
{
protected async ValueTask<ArgumentValue> GetValueFromNamedArgumentAsync(TemplateContext context, ScriptNode callerContext, ScriptNamedArgument namedArg)
{
for (int j = 0; j < Parameters.Length; j++)
{
var arg = Parameters[j];
if (arg.Name == namedArg.Name)
{
return new ArgumentValue(j, arg.ParameterType, await context.EvaluateAsync(namedArg).ConfigureAwait(false));
}
}
throw new ScriptRuntimeException(callerContext.Span, $"Invalid argument `{namedArg.Name}` not found for function `{callerContext}`");
}
}
/// <summary>
/// Generic function wrapper handling any kind of function parameters.
/// </summary>
partial class GenericFunctionWrapper {
public override async ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
{
var expectedNumberOfParameters = Parameters.Length;
if (_hasTemplateContext)
{
expectedNumberOfParameters--;
if (_hasSpan)
{
expectedNumberOfParameters--;
}
}
var minimumRequiredParameters = expectedNumberOfParameters - _optionalParameterCount;
// Check parameters
if ((_hasObjectParams && arguments.Count < minimumRequiredParameters - 1) || (!_hasObjectParams && arguments.Count < minimumRequiredParameters))
{
if (minimumRequiredParameters != expectedNumberOfParameters)
{
throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments `{arguments.Count}` passed to `{callerContext}` while expecting at least `{minimumRequiredParameters}` arguments");
}
else
{
throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments `{arguments.Count}` passed to `{callerContext}` while expecting `{expectedNumberOfParameters}` arguments");
}
}
// Convert arguments
object[] paramArguments = null;
var argMask = 0;
if (_hasObjectParams)
{
var objectParamsCount = arguments.Count - _lastParamsIndex;
if (_hasTemplateContext)
{
objectParamsCount++;
if (_hasSpan)
{
objectParamsCount++;
}
}
paramArguments = new object[objectParamsCount];
_arguments[_lastParamsIndex] = paramArguments;
argMask |= 1 << _lastParamsIndex;
}
// Copy TemplateContext/SourceSpan parameters
int argOffset = 0;
if (_hasTemplateContext)
{
_arguments[0] = context;
argOffset++;
argMask |= 1;
if (_hasSpan)
{
_arguments[1] = callerContext.Span;
argOffset++;
argMask |= 2;
}
}
var argOrderedIndex = argOffset;
// Setup any default parameters
if (_optionalParameterCount > 0)
{
for (int i = Parameters.Length - 1; i >= Parameters.Length - _optionalParameterCount; i--)
{
_arguments[i] = Parameters[i].DefaultValue;
argMask |= 1 << i;
}
}
int paramsIndex = 0;
for (int i = 0; i < arguments.Count; i++)
{
Type argType = null;
try
{
int argIndex;
var arg = arguments[i];
var namedArg = arg as ScriptNamedArgument;
if (namedArg != null)
{
var namedArgValue = await GetValueFromNamedArgumentAsync(context, callerContext, namedArg).ConfigureAwait(false);
arg = namedArgValue.Value;
argIndex = namedArgValue.Index;
argType = namedArgValue.Type;
if (_hasObjectParams && argIndex == _lastParamsIndex)
{
argType = _paramsElementType;
argIndex = argIndex + paramsIndex;
paramsIndex++;
}
}
else
{
argIndex = argOrderedIndex;
if (_hasObjectParams && argIndex == _lastParamsIndex)
{
argType = _paramsElementType;
argIndex = argIndex + paramsIndex;
paramsIndex++;
}
else
{
argType = Parameters[argIndex].ParameterType;
argOrderedIndex++;
}
}
var argValue = context.ToObject(callerContext.Span, arg, argType);
if (paramArguments != null && argIndex >= _lastParamsIndex)
{
paramArguments[argIndex - _lastParamsIndex] = argValue;
}
else
{
_arguments[argIndex] = argValue;
argMask |= 1 << argIndex;
}
}
catch (Exception exception)
{
throw new ScriptRuntimeException(callerContext.Span, $"Unable to convert parameter #{i} of type `{arguments[i]?.GetType()}` to type `{argType}`", exception);
}
}
// In case we have named arguments we need to verify that all arguments were set
if (argMask != (1 << Parameters.Length) - 1)
{
if (minimumRequiredParameters != expectedNumberOfParameters)
{
throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments `{arguments.Count}` passed to `{callerContext}` while expecting at least `{minimumRequiredParameters}` arguments");
}
else
{
throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments `{arguments.Count}` passed to `{callerContext}` while expecting `{expectedNumberOfParameters}` arguments");
}
}
// Call method
try
{
var result = Method.Invoke(_target, _arguments);
// NOTE: The following line should not be touch as it is being matched by ScribanAsyncCodeGen
return IsAwaitable ? await ConfigureAwait(result) : result ; }
catch (TargetInvocationException exception)
{
throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception when calling {callerContext}", exception.InnerException);
}
}
}
/// <summary>
/// Extensions for <see cref="IScriptOutput"/>
/// </summary>
public static partial class ScriptOutputExtensions
{
public static async ValueTask<IScriptOutput> WriteAsync(this IScriptOutput scriptOutput, string text,CancellationToken cancellationToken)
{
if (text == null) throw new ArgumentNullException(nameof(text));
return await scriptOutput.WriteAsync(text, 0, text.Length, cancellationToken).ConfigureAwait(false);
}
}
}
namespace Scriban.Syntax
{
public partial class ScriptArrayInitializerExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var scriptArray = new ScriptArray();
foreach (var value in Values)
{
var valueEval = await context.EvaluateAsync(value).ConfigureAwait(false);
scriptArray.Add(valueEval);
}
return scriptArray;
}
}
public partial class ScriptAssignExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var valueObject = await context.EvaluateAsync(Value).ConfigureAwait(false);
await context.SetValueAsync(Target, valueObject).ConfigureAwait(false);
return null;
}
}
public partial class ScriptBinaryExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var leftValueOriginal = await context.EvaluateAsync(Left).ConfigureAwait(false);
var leftValue = leftValueOriginal;
var rightValueOriginal = await context.EvaluateAsync(Right).ConfigureAwait(false);
object rightValue = rightValueOriginal;
return Evaluate(context, Span, Operator, leftValue, rightValue);
}
}
public sealed partial class ScriptBlockStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
object result = null;
for (int i = 0; i < Statements.Count; i++)
{
var statement = Statements[i];
var expressionStatement = statement as ScriptExpressionStatement;
var isAssign = expressionStatement?.Expression is ScriptAssignExpression;
#if SCRIBAN_ASYNC
// Throw if cancellation is requested
if (context.CancellationToken.IsCancellationRequested)
{
context.CancellationToken.ThrowIfCancellationRequested();
}
#endif
result = await context.EvaluateAsync(statement).ConfigureAwait(false);
// Top-level assignment expression don't output anything
if (isAssign)
{
result = null;
}
else if (result != null && context.FlowState != ScriptFlowState.Return && context.EnableOutput)
{
await context.WriteAsync(Span, result).ConfigureAwait(false);
result = null;
}
// If flow state is different, we need to exit this loop
if (context.FlowState != ScriptFlowState.None)
{
break;
}
}
return result;
}
}
public partial class ScriptCaptureStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// unit test: 230-capture-statement.txt
context.PushOutput();
try
{
await context.EvaluateAsync(Body).ConfigureAwait(false);
}
finally
{
var result = context.PopOutput();
await context.SetValueAsync(Target, result).ConfigureAwait(false);
}
return null;
}
}
public partial class ScriptCaseStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var caseValue = await context.EvaluateAsync(Value).ConfigureAwait(false);
context.PushCase(caseValue);
try
{
return await context.EvaluateAsync(Body).ConfigureAwait(false);
}
finally
{
context.PopCase();
}
}
}
public partial class ScriptElseStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
await context.EvaluateAsync(Body).ConfigureAwait(false);
return await context.EvaluateAsync(Else).ConfigureAwait(false);
}
}
public partial class ScriptExpressionStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var result = await context.EvaluateAsync(Expression).ConfigureAwait(false);
// This code is necessary for wrap to work
var codeDelegate = result as ScriptNode;
if (codeDelegate != null)
{
return await context.EvaluateAsync(codeDelegate).ConfigureAwait(false);
}
return result;
}
}
public partial class ScriptForStatement
{
protected override async ValueTask<object> EvaluateImplAsync(TemplateContext context)
{
var loopIterator = await context.EvaluateAsync(Iterator).ConfigureAwait(false);
var list = loopIterator as IList;
if (list == null)
{
var iterator = loopIterator as IEnumerable;
if (iterator != null)
{
list = new ScriptArray(iterator);
}
}
if (list != null)
{
object loopResult = null;
await context.SetValueAsync(ScriptVariable.LoopLength, list.Count).ConfigureAwait(false);
object previousValue = null;
bool reversed = false;
int startIndex = 0;
int limit = list.Count;
if (NamedArguments != null)
{
foreach (var option in NamedArguments)
{
switch (option.Name)
{
case "offset":
startIndex = context.ToInt(option.Value.Span, await context.EvaluateAsync(option.Value).ConfigureAwait(false));
break;
case "reversed":
reversed = true;
break;
case "limit":
limit = context.ToInt(option.Value.Span, await context.EvaluateAsync(option.Value).ConfigureAwait(false));
break;
default:
await ProcessArgumentAsync(context, option).ConfigureAwait(false);
break;
}
}
}
var endIndex = Math.Min(limit + startIndex, list.Count) - 1;
var index = reversed ? endIndex : startIndex;
var dir = reversed ? -1 : 1;
bool isFirst = true;
int i = 0;
await BeforeLoopAsync(context).ConfigureAwait(false);
while (!reversed && index <= endIndex || reversed && index >= startIndex)
{
if (!context.StepLoop(this))
{
return null;
}
// We update on next run on previous value (in order to handle last)
var value = list[index];
bool isLast = reversed ? index == startIndex : index == endIndex;
context.SetValue(ScriptVariable.LoopLast, isLast);
await context.SetValueAsync(ScriptVariable.LoopChanged, isFirst || !Equals(previousValue, value)).ConfigureAwait(false);
await context.SetValueAsync(ScriptVariable.LoopRIndex, list.Count - index - 1).ConfigureAwait(false);
await context.SetValueAsync(Variable, value).ConfigureAwait(false);
loopResult = await LoopItemAsync(context, index, i, isLast).ConfigureAwait(false);
if (!ContinueLoop(context))
{
break;
}
previousValue = value;
isFirst = false;
index += dir;
i++;
}
await AfterLoopAsync(context).ConfigureAwait(false);
await context.SetValueAsync(ScriptVariable.Continue, index).ConfigureAwait(false);
return loopResult;
}
if (loopIterator != null)
{
throw new ScriptRuntimeException(Iterator.Span, $"Unexpected type `{loopIterator.GetType()}` for iterator");
}
return null;
}
}
public partial class ScriptFunctionCall
{
public static async ValueTask<object> CallAsync(TemplateContext context, ScriptNode callerContext, object functionObject, bool processPipeArguments, List<ScriptExpression> arguments = null)
{
if (callerContext == null)
throw new ArgumentNullException(nameof(callerContext));
if (functionObject == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"The target function `{callerContext}` is null");
}
var function = functionObject as ScriptFunction;
var externFunction = functionObject as IScriptCustomFunction;
if (function == null && externFunction == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"Invalid target function `{callerContext}`( as `{functionObject?.GetType()}`)");
}
ScriptBlockStatement blockDelegate = null;
if (context.BlockDelegates.Count > 0)
{
blockDelegate = context.BlockDelegates.Pop();
}
// We can't cache this array because it might be collect by the function
// So we absolutely need to generate a new array everytime we call a function
var argumentValues = new ScriptArray();
// Handle pipe arguments here
if (processPipeArguments && context.PipeArguments != null && context.PipeArguments.Count > 0)
{
argumentValues.AddRange(context.PipeArguments);
context.PipeArguments.Clear();
}
// Process direct arguments
if (arguments != null)
{
foreach (var argument in arguments)
{
object value;
// Handle named arguments
var namedArg = argument as ScriptNamedArgument;
if (namedArg != null)
{
// In case of a ScriptFunction, we write the named argument into the ScriptArray directly
if (externFunction == null)
{
// We can't add an argument that is "size" for array
if (argumentValues.CanWrite(namedArg.Name))
{
argumentValues.SetValue(context, callerContext.Span, namedArg.Name, await context.EvaluateAsync(namedArg).ConfigureAwait(false), false);
continue;
}
// Otherwise pass as a regular argument
value = await context.EvaluateAsync(namedArg).ConfigureAwait(false);
}
else
{
// Named argument are passed as is to the IScriptCustomFunction
value = argument;
}
}
else
{
value = await context.EvaluateAsync(argument).ConfigureAwait(false);
}
// Handle parameters expansion for a function call when the operator ^ is used
var unaryExpression = argument as ScriptUnaryExpression;
if (unaryExpression != null && unaryExpression.Operator == ScriptUnaryOperator.FunctionParametersExpand)
{
var valueEnumerator = value as IEnumerable;
if (valueEnumerator != null)
{
foreach (var subValue in valueEnumerator)
{
argumentValues.Add(subValue);
}
continue;
}
}
argumentValues.Add(value);
}
}
object result = null;
context.EnterFunction(callerContext);
try
{
if (externFunction != null)
{
result = await externFunction.InvokeAsync(context, callerContext, argumentValues, blockDelegate).ConfigureAwait(false);
}
else
{
context.SetValue(ScriptVariable.Arguments, argumentValues, true);
// Set the block delegate
if (blockDelegate != null)
{
context.SetValue(ScriptVariable.BlockDelegate, blockDelegate, true);
}
result = await context.EvaluateAsync(function.Body).ConfigureAwait(false);
}
}
finally
{
context.ExitFunction();
}
// Restore the flow state to none
context.FlowState = ScriptFlowState.None;
return result;
}
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// Invoke evaluate on the target, but don't automatically call the function as if it was a parameterless call.
var targetFunction = await context.EvaluateAsync(Target, true).ConfigureAwait(false);
// Throw an exception if the target function is null
if (targetFunction == null)
{
throw new ScriptRuntimeException(Target.Span, $"The target `{Target}` function is null");
}
return await CallAsync(context, this, targetFunction, context.AllowPipeArguments, Arguments).ConfigureAwait(false);
}
}
public partial class ScriptIfStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var conditionValue = context.ToBool(Condition.Span, await context.EvaluateAsync(Condition).ConfigureAwait(false));
if (InvertCondition)
{
conditionValue = !conditionValue;
}
return conditionValue ? await context.EvaluateAsync(Then).ConfigureAwait(false) : await context.EvaluateAsync(Else).ConfigureAwait(false);
}
}
public partial class ScriptImportStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var value = await context.EvaluateAsync(Expression).ConfigureAwait(false);
if (value == null)
{
return null;
}
var scriptObject = value as ScriptObject;
if (scriptObject == null)
{
throw new ScriptRuntimeException(Expression.Span, $"Unexpected value `{value.GetType()}` for import. Expecting an plain script object {{}}");
}
context.CurrentGlobal.Import(scriptObject);
return null;
}
}
public partial class ScriptIndexerExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.GetValueAsync(this).ConfigureAwait(false);
}
private async ValueTask<object> GetOrSetValueAsync(TemplateContext context, object valueToSet, bool setter)
{
object value = null;
var targetObject = await context.GetValueAsync(Target).ConfigureAwait(false);
if (targetObject == null)
{
if (context.EnableRelaxedMemberAccess)
{
return null;
}
else
{
throw new ScriptRuntimeException(Target.Span, $"Object `{Target}` is null. Cannot access indexer: {this}"); // unit test: 130-indexer-accessor-error1.txt
}
}
var index = await context.EvaluateAsync(Index).ConfigureAwait(false);
if (index == null)
{
if (context.EnableRelaxedMemberAccess)
{
return null;
}
else
{
throw new ScriptRuntimeException(Index.Span, $"Cannot access target `{Target}` with a null indexer: {this}"); // unit test: 130-indexer-accessor-error2.txt
}
}
if (targetObject is IDictionary || targetObject is ScriptObject)
{
var accessor = context.GetMemberAccessor(targetObject);
var indexAsString = context.ToString(Index.Span, index);
if (setter)
{
if (!accessor.TrySetValue(context, Span, targetObject, indexAsString, valueToSet))
{
throw new ScriptRuntimeException(Index.Span, $"Cannot set a value for the readonly member `{indexAsString}` in the indexer: {Target}['{indexAsString}']"); // unit test: 130-indexer-accessor-error3.txt
}
}
else
{
if (!accessor.TryGetValue(context, Span, targetObject, indexAsString, out value))
{
context.TryGetMember?.Invoke(context, Span, targetObject, indexAsString, out value);
}
}
}
else
{
var accessor = context.GetListAccessor(targetObject);
if (accessor == null)
{
throw new ScriptRuntimeException(Target.Span, $"Expecting a list. Invalid value `{targetObject}/{targetObject.GetType().Name}` for the target `{Target}` for the indexer: {this}"); // unit test: 130-indexer-accessor-error4.txt
}
int i = context.ToInt(Index.Span, index);
// Allow negative index from the end of the array
if (i < 0)
{
i = accessor.GetLength(context, Span, targetObject) + i;
}
if (i >= 0)
{
if (setter)
{
accessor.SetValue(context, Span, targetObject, i, valueToSet);
}
else
{
value = accessor.GetValue(context, Span, targetObject, i);
}
}
}
return value;
}
public async ValueTask<object> GetValueAsync(TemplateContext context)
{
return await GetOrSetValueAsync(context, null, false).ConfigureAwait(false);
}
public async ValueTask SetValueAsync(TemplateContext context, object valueToSet)
{
await GetOrSetValueAsync(context, valueToSet, true).ConfigureAwait(false);
}
}
public partial class ScriptIsEmptyExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.GetValueAsync(this).ConfigureAwait(false);
}
private async ValueTask<object> GetTargetObjectAsync(TemplateContext context, bool isSet)
{
var targetObject = await context.GetValueAsync(Target).ConfigureAwait(false);
if (targetObject == null)
{
if (isSet || !context.EnableRelaxedMemberAccess)
{
throw new ScriptRuntimeException(this.Span, $"Object `{this.Target}` is null. Cannot access property `empty?`");
}
}
return targetObject;
}
public async ValueTask<object> GetValueAsync(TemplateContext context)
{
var targetObject = await GetTargetObjectAsync(context, false).ConfigureAwait(false);
return context.IsEmpty(Span, targetObject);
}
}
/// <summary>
/// Base class for a loop statement
/// </summary>
public abstract partial class ScriptLoopStatementBase
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// Notify the context that we enter a loop block (used for variable with scope Loop)
object result = null;
context.EnterLoop(this);
try
{
result = await EvaluateImplAsync(context).ConfigureAwait(false);
}
finally
{
// Level scope block
context.ExitLoop(this);
if (context.FlowState != ScriptFlowState.Return)
{
// Revert to flow state to none unless we have a return that must be handled at a higher level
context.FlowState = ScriptFlowState.None;
}
}
return result;
}
/// <summary>
/// Base implementation for a loop single iteration
/// </summary>
/// <param name = "context">The context</param>
/// <param name = "index">The index in the loop</param>
/// <param name = "localIndex"></param>
/// <param name = "isLast"></param>
/// <returns></returns>
protected virtual async ValueTask<object> LoopItemAsync(TemplateContext context, int index, int localIndex, bool isLast)
{
// Setup variable
context.SetValue(ScriptVariable.LoopFirst, index == 0);
var even = (index & 1) == 0;
context.SetValue(ScriptVariable.LoopEven, even);
await context.SetValueAsync(ScriptVariable.LoopOdd, !even).ConfigureAwait(false);
context.SetValue(ScriptVariable.LoopIndex, index);
// bug: temp workaround to correct a bug with ret. Should be handled differently
return await context.EvaluateAsync(Body).ConfigureAwait(false);
}
}
public partial class ScriptMemberExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.GetValueAsync(this).ConfigureAwait(false);
}
private async ValueTask<object> GetTargetObjectAsync(TemplateContext context, bool isSet)
{
var targetObject = await context.GetValueAsync(Target).ConfigureAwait(false);
if (targetObject == null)
{
if (isSet || !context.EnableRelaxedMemberAccess)
{
throw new ScriptRuntimeException(this.Span, $"Object `{this.Target}` is null. Cannot access member: {this}"); // unit test: 131-member-accessor-error1.txt
}
}
else if (targetObject is string || targetObject.GetType().IsPrimitiveOrDecimal())
{
if (isSet || !context.EnableRelaxedMemberAccess)
{
throw new ScriptRuntimeException(this.Span, $"Cannot get or set a member on the primitive `{targetObject}/{targetObject.GetType()}` when accessing member: {this}"); // unit test: 132-member-accessor-error2.txt
}
// If this is relaxed, set the target object to null
if (context.EnableRelaxedMemberAccess)
{
targetObject = null;
}
}
return targetObject;
}
public async ValueTask<object> GetValueAsync(TemplateContext context)
{
var targetObject = await GetTargetObjectAsync(context, false).ConfigureAwait(false);
// In case TemplateContext.EnableRelaxedMemberAccess
if (targetObject == null)
{
return null;
}
var accessor = context.GetMemberAccessor(targetObject);
var memberName = this.Member.Name;
object value;
if (!accessor.TryGetValue(context, Span, targetObject, memberName, out value))
{
context.TryGetMember?.Invoke(context, Span, targetObject, memberName, out value);
}
return value;
}
public async ValueTask SetValueAsync(TemplateContext context, object valueToSet)
{
var targetObject = await GetTargetObjectAsync(context, true).ConfigureAwait(false);
var accessor = context.GetMemberAccessor(targetObject);
var memberName = this.Member.Name;
if (!accessor.TrySetValue(context, this.Span, targetObject, memberName, valueToSet))
{
throw new ScriptRuntimeException(this.Member.Span, $"Cannot set a value for the readonly member: {this}"); // unit test: 132-member-accessor-error3.txt
}
}
}
public partial class ScriptNamedArgument
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
if (Value != null)
return await context.EvaluateAsync(Value).ConfigureAwait(false);
return true;
}
}
public partial class ScriptNestedExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// A nested expression will reset the pipe arguments for the group
context.PushPipeArguments();
try
{
return await context.GetValueAsync(this).ConfigureAwait(false);
}
finally
{
context.PopPipeArguments();
}
}
public async ValueTask<object> GetValueAsync(TemplateContext context)
{
return await context.EvaluateAsync(Expression).ConfigureAwait(false);
}
public async ValueTask SetValueAsync(TemplateContext context, object valueToSet)
{
await context.SetValueAsync(Expression, valueToSet).ConfigureAwait(false);
}
}
public partial class ScriptObjectInitializerExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var scriptObject = new ScriptObject();
foreach (var member in Members)
{
var variable = member.Key as ScriptVariable;
var literal = member.Key as ScriptLiteral;
var name = variable?.Name ?? literal?.Value?.ToString();
scriptObject.SetValue(context, Span, name, await context.EvaluateAsync(member.Value).ConfigureAwait(false), false);
}
return scriptObject;
}
}
public partial class ScriptPage
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.EvaluateAsync(Body).ConfigureAwait(false);
}
}
public partial class ScriptPipeCall
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// We don't evaluate the From but we let the pipe evaluate it later
var leftResult = await context.EvaluateAsync(From).ConfigureAwait(false);
// Push a new pipe arguments
context.PushPipeArguments();
try
{
// Support for Parameters expansion
var unaryExpression = From as ScriptUnaryExpression;
if (unaryExpression != null && unaryExpression.Operator == ScriptUnaryOperator.FunctionParametersExpand)
{
// TODO: Pipe calls will not work correctly in case of (a | b) | ( c | d)
var valueEnumerator = leftResult as IEnumerable;
if (valueEnumerator != null)
{
var pipeArguments = context.PipeArguments;
foreach (var subValue in valueEnumerator)
{
pipeArguments.Add(subValue);
}
}
else
{
context.PipeArguments.Add(leftResult);
}
}
else
{
context.PipeArguments.Add(leftResult);
}
var result = await context.EvaluateAsync(To).ConfigureAwait(false);
// If we have still remaining arguments, it is likely that the destination expression is not a function
// so pipe arguments were not picked up and this is an error
if (context.PipeArguments.Count > 0)
{
throw new ScriptRuntimeException(To.Span, $"Pipe expression destination `{To}` is not a valid function ");
}
return result;
}
finally
{
context.PopPipeArguments();
}
}
}
public partial class ScriptRawStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
if (Text == null)
return null;
var length = Span.End.Offset - Span.Start.Offset + 1;
if (length > 0)
{
// If we are in the context of output, output directly to TemplateContext.Output
if (context.EnableOutput)
{
await context.WriteAsync(Text, Span.Start.Offset, length).ConfigureAwait(false);
}
else
{
return Text.Substring(Span.Start.Offset, length);
}
}
return null;
}
}
public partial class ScriptReturnStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var result = await context.EvaluateAsync(Expression).ConfigureAwait(false);
context.FlowState = ScriptFlowState.Return;
return result;
}
}
/// <summary>
/// Statement handling the `tablerow`
/// </summary>
public partial class ScriptTableRowStatement
{
protected override async ValueTask AfterLoopAsync(TemplateContext context)
{
await context.Write("</tr>").WriteLineAsync().ConfigureAwait(false);
}
protected override async ValueTask BeforeLoopAsync(TemplateContext context)
{
await context.WriteAsync("<tr class=\"row1\">").ConfigureAwait(false);
}
protected override async ValueTask<object> LoopItemAsync(TemplateContext context, int index, int localIndex, bool isLast)
{
var columnIndex = localIndex % _columnsCount;
await context.SetValueAsync(ScriptVariable.TableRowCol, columnIndex + 1).ConfigureAwait(false);
if (columnIndex == 0 && localIndex > 0)
{
await context.Write("</tr>").WriteAsync(context.NewLine).ConfigureAwait(false);
var rowIndex = (localIndex / _columnsCount) + 1;
await context.Write("<tr class=\"row").Write(rowIndex.ToString(CultureInfo.InvariantCulture)).WriteAsync("\">").ConfigureAwait(false);
}
await context.Write("<td class=\"col").Write((columnIndex + 1).ToString(CultureInfo.InvariantCulture)).WriteAsync("\">").ConfigureAwait(false);
var result = await base.LoopItemAsync(context, index, localIndex, isLast).ConfigureAwait(false);
await context.WriteAsync("</td>").ConfigureAwait(false);
return result;
}
protected override async ValueTask ProcessArgumentAsync(TemplateContext context, ScriptNamedArgument argument)
{
_columnsCount = 1;
if (argument.Name == "cols")
{
_columnsCount = context.ToInt(argument.Value.Span, await context.EvaluateAsync(argument.Value).ConfigureAwait(false));
if (_columnsCount <= 0)
{
_columnsCount = 1;
}
return;
}
await base.ProcessArgumentAsync(context, argument).ConfigureAwait(false);
}
}
public partial class ScriptThisExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.GetValueAsync(this).ConfigureAwait(false);
}
}
public partial class ScriptUnaryExpression
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
switch (Operator)
{
case ScriptUnaryOperator.Not:
{
var value = await context.EvaluateAsync(Right).ConfigureAwait(false);
return !context.ToBool(Right.Span, value);
}
case ScriptUnaryOperator.Negate:
case ScriptUnaryOperator.Plus:
{
var value = await context.EvaluateAsync(Right).ConfigureAwait(false);
bool negate = Operator == ScriptUnaryOperator.Negate;
if (value != null)
{
if (value is int)
{
return negate ? -((int)value) : value;
}
else if (value is double)
{
return negate ? -((double)value) : value;
}
else if (value is float)
{
return negate ? -((float)value) : value;
}
else if (value is long)
{
return negate ? -((long)value) : value;
}
else if (value is decimal)
{
return negate ? -((decimal)value) : value;
}
else
{
throw new ScriptRuntimeException(this.Span, $"Unexpected value `{value} / Type: {value?.GetType()}`. Cannot negate(-)/positive(+) a non-numeric value");
}
}
}
break;
case ScriptUnaryOperator.FunctionAlias:
return await context.EvaluateAsync(Right, true).ConfigureAwait(false);
case ScriptUnaryOperator.FunctionParametersExpand:
// Function parameters expand is done at the function level, so here, we simply return the actual list
return await context.EvaluateAsync(Right).ConfigureAwait(false);
}
throw new ScriptRuntimeException(Span, $"Operator `{Operator}` is not supported");
}
}
public abstract partial class ScriptVariable
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
return await context.GetValueAsync((ScriptExpression)this).ConfigureAwait(false);
}
}
public partial class ScriptWhenStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var caseValue = context.PeekCase();
foreach (var value in Values)
{
var whenValue = await context.EvaluateAsync(value).ConfigureAwait(false);
var result = ScriptBinaryExpression.Evaluate(context, Span, ScriptBinaryOperator.CompareEqual, caseValue, whenValue);
if (result is bool && (bool)result)
{
return await context.EvaluateAsync(Body).ConfigureAwait(false);
}
}
return await context.EvaluateAsync(Next).ConfigureAwait(false);
}
}
public partial class ScriptWhileStatement
{
protected override async ValueTask<object> EvaluateImplAsync(TemplateContext context)
{
var index = 0;
object result = null;
await BeforeLoopAsync(context).ConfigureAwait(false);
while (context.StepLoop(this))
{
var conditionResult = context.ToBool(Condition.Span, await context.EvaluateAsync(Condition).ConfigureAwait(false));
if (!conditionResult)
{
break;
}
result = await LoopItemAsync(context, index++, index, false).ConfigureAwait(false);
if (!ContinueLoop(context))
{
break;
}
}
;
await AfterLoopAsync(context).ConfigureAwait(false);
return result;
}
}
public partial class ScriptWithStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
var target = await context.GetValueAsync(Name).ConfigureAwait(false);
if (!(target is IScriptObject))
{
var targetName = target?.GetType().Name ?? "null";
throw new ScriptRuntimeException(Name.Span, $"Invalid target property `{Name}` used for [with] statement. Must be a ScriptObject instead of `{targetName}`");
}
context.PushGlobal((IScriptObject)target);
try
{
var result = await context.EvaluateAsync(Body).ConfigureAwait(false);
return result;
}
finally
{
context.PopGlobal();
}
}
}
public partial class ScriptWrapStatement
{
public override async ValueTask<object> EvaluateAsync(TemplateContext context)
{
// Check that the Target is actually a function
var functionCall = Target as ScriptFunctionCall;
if (functionCall == null)
{
var parameterLessFunction = await context.EvaluateAsync(Target, true).ConfigureAwait(false);
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);
return await ScriptFunctionCall.CallAsync(context, this, parameterLessFunction, false).ConfigureAwait(false);
}
else
{
context.BlockDelegates.Push(Body);
return await context.EvaluateAsync(functionCall).ConfigureAwait(false);
}
}
}
}
#endif