private static object Round(TemplateContext context, ScriptNode callerContext, ScriptArray parameters) { if (parameters.Count < 1 || parameters.Count > 2) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected number of arguments [{parameters.Count}] for math.round. Expecting at least 1 parameter <precision>? <value>"); } var value = ScriptValueConverter.ToDouble(callerContext.Span, parameters[parameters.Count - 1]); int precision = 0; if (parameters.Count == 2) { precision = ScriptValueConverter.ToInt(callerContext.Span, parameters[0]); } return Round(precision, value); }
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray parameters, ScriptBlockStatement blockStatement) { return(_customFunction(context, callerContext, parameters)); }
public override object Invoke(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 = GetValueFromNamedArgument(context, callerContext, namedArg); 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(result); } catch (TargetInvocationException exception) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception when calling {callerContext}", exception.InnerException); } }
public virtual ValueTask <object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement) { return(new ValueTask <object>(Invoke(context, callerContext, arguments, blockStatement))); }
public abstract object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement);
public object Evaluate(TemplateContext context, ScriptNode callerContext, ScriptArray parameters, ScriptBlockStatement blockStatement) { if (parameters.Count == 0) { throw new ScriptRuntimeException(callerContext.Span, "Expecting at least the name of the template to include for the <include> function"); } string templateName = null; try { templateName = ScriptValueConverter.ToString(callerContext.Span, parameters[0]); } catch (Exception ex) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception while converting first parameter for <include> function. Expecting a string", ex); } // If template name is empty, throw an exception if (templateName == null || string.IsNullOrEmpty(templateName = templateName.Trim())) { throw new ScriptRuntimeException(callerContext.Span, $"Include template name cannot be null or empty"); } // Compute a new parameters for the include var newParameters = new ScriptArray(parameters.Count - 1); for (int i = 1; i < parameters.Count; i++) { newParameters[i] = parameters[i]; } context.SetValue(ScriptVariable.Arguments, newParameters, true); Template template; if (!context.CachedTemplates.TryGetValue(templateName, out template)) { if (context.TemplateLoader == null) { throw new ScriptRuntimeException(callerContext.Span, $"Unable to include <{templateName}>. No TemplateLoader registered in TemplateContext.Options.TemplateLoader"); } string templateFilePath; var templateText = context.TemplateLoader.Load(context, callerContext.Span, templateName, out templateFilePath); if (templateText == null) { throw new ScriptRuntimeException(callerContext.Span, $"The result of including <{templateName}> cannot be null"); } // IF template file path is not defined, we use the template name instead templateFilePath = templateFilePath ?? templateName; // Clone parser options var parserOptions = context.TemplateLoaderParserOptions.Clone(); // Parse include in default modes (while top page can be using front matter) parserOptions.Mode = parserOptions.Mode == ScriptMode.ScriptOnly ? ScriptMode.ScriptOnly : ScriptMode.Default; template = Template.Parse(templateText, templateFilePath, parserOptions); // If the template has any errors, throw an exception if (template.HasErrors) { throw new ScriptParserRuntimeException(callerContext.Span, $"Error while parsing template <{templateName}> from [{templateFilePath}]", template.Messages); } context.CachedTemplates.Add(templateName, template); } // Query the pending includes stored in the context HashSet<string> pendingIncludes; object pendingIncludesObject; if (!context.Tags.TryGetValue(typeof(IncludeFunction), out pendingIncludesObject)) { pendingIncludesObject = pendingIncludes = new HashSet<string>(); context.Tags[typeof (IncludeFunction)] = pendingIncludesObject; } else { pendingIncludes = (HashSet<string>) pendingIncludesObject; } // Make sure that we cannot recursively include a template if (pendingIncludes.Contains(templateName)) { throw new ScriptRuntimeException(callerContext.Span, $"The include [{templateName}] cannot be used recursively"); } pendingIncludes.Add(templateName); context.PushOutput(); object result = null; try { template.Render(context); } finally { result = context.PopOutput(); pendingIncludes.Remove(templateName); } return result; }
private static object Map(TemplateContext context, ScriptNode callerContext, ScriptArray parameters) { if (parameters.Count != 2) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected number of arguments [{parameters.Count}] for map. Expecting at 2 parameters: <property> <array>"); } var member = ScriptValueConverter.ToString(callerContext.Span, parameters[0]); var target = parameters[1]; return Map(context, target, member); }
private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0) { int expressionCount = 0; expressionLevel++; try { ScriptFunctionCall functionCall = null; parseExpression: expressionCount++; ScriptExpression leftOperand = null; switch (Current.Type) { case TokenType.Identifier: case TokenType.IdentifierSpecial: leftOperand = ParseVariableOrLiteral(); // Special handle of the $$ block delegate variable if (ScriptVariable.BlockDelegate.Equals(leftOperand)) { if (expressionCount != 1 || expressionLevel > 1) { LogError("Cannot use block delegate $$ in a nested expression"); } if (!(parentNode is ScriptExpressionStatement)) { LogError(parentNode, "Cannot use block delegate $$ outside an expression statement"); } return leftOperand; } break; case TokenType.Integer: leftOperand = ParseInteger(); break; case TokenType.Float: leftOperand = ParseFloat(); break; case TokenType.String: leftOperand = ParseString(); break; case TokenType.OpenParent: leftOperand = ParseParenthesis(ref hasAnonymousFunction); break; case TokenType.OpenBrace: leftOperand = ParseObjectInitializer(); break; case TokenType.OpenBracket: leftOperand = ParseArrayInitializer(); break; case TokenType.Not: case TokenType.Minus: case TokenType.Arroba: case TokenType.Plus: case TokenType.Caret: leftOperand = ParseUnaryExpression(ref hasAnonymousFunction); break; } // Should not happen but in case if (leftOperand == null) { LogError($"Unexpected token [{Current.Type}] for expression"); return null; } if (leftOperand is ScriptAnonymousFunction) { hasAnonymousFunction = true; } while (!hasAnonymousFunction) { // Parse Member expression are expected to be followed only by an identifier if (Current.Type == TokenType.Dot) { var nextToken = PeekToken(); if (nextToken.Type == TokenType.Identifier) { NextToken(); var memberExpression = Open<ScriptMemberExpression>(); memberExpression.Target = leftOperand; var member = ParseVariableOrLiteral(); if (!(member is ScriptVariable)) { LogError("Unexpected literal member [{member}]"); return null; } memberExpression.Member = (ScriptVariable) member; leftOperand = Close(memberExpression); } else { LogError(nextToken, $"Invalid token [{nextToken.Type}]. The dot operator is expected to be followed by a plain identifier"); return null; } continue; } // If we have a bracket but left operand is a (variable || member || indexer), then we consider next as an indexer // unit test: 130-indexer-accessor-accept1.txt if (Current.Type == TokenType.OpenBracket && leftOperand is ScriptVariablePath && !IsPreviousCharWhitespace()) { NextToken(); var indexerExpression = Open<ScriptIndexerExpression>(); indexerExpression.Target = leftOperand; // unit test: 130-indexer-accessor-error5.txt indexerExpression.Index = ExpectAndParseExpression(indexerExpression, ref hasAnonymousFunction, functionCall, 0, $"Expecting <index_expression> instead of [{Current.Type}]"); if (Current.Type != TokenType.CloseBracket) { LogError($"Unexpected [{Current.Type}]. Expecting ']'"); } else { NextToken(); } leftOperand = Close(indexerExpression); continue; } if (Current.Type == TokenType.Equal) { var assignExpression = Open<ScriptAssignExpression>(); if (expressionLevel > 1) { // unit test: 101-assign-complex-error1.txt LogError(assignExpression, $"Expression is only allowed for a top level assignment"); } NextToken(); assignExpression.Target = leftOperand; // unit test: 105-assign-error3.txt assignExpression.Value = ExpectAndParseExpression(assignExpression, ref hasAnonymousFunction, parentExpression); leftOperand = Close(assignExpression); continue; } // Handle binary operators here ScriptBinaryOperator binaryOperatorType; if (BinaryOperators.TryGetValue(Current.Type, out binaryOperatorType)) { var newPrecedence = GetOperatorPrecedence(binaryOperatorType); // Check precedence to see if we should "take" this operator here (Thanks TimJones for the tip code! ;) if (newPrecedence < precedence) break; var binaryExpression = Open<ScriptBinaryExpression>(); binaryExpression.Left = leftOperand; binaryExpression.Operator = binaryOperatorType; NextToken(); // skip the operator // unit test: 110-binary-simple-error1.txt binaryExpression.Right = ExpectAndParseExpression(binaryExpression, ref hasAnonymousFunction, functionCall ?? parentExpression, newPrecedence, $"Expecting an <expression> to the right of the operator instead of [{Current.Type}]"); leftOperand = Close(binaryExpression); continue; } if (precedence > 0) { break; } // If we can parse a statement, we have a method call if (StartAsExpression()) { if (parentExpression != null) { break; } if (functionCall == null) { functionCall = Open<ScriptFunctionCall>(); functionCall.Target = leftOperand; } else { functionCall.Arguments.Add(leftOperand); } goto parseExpression; } if (Current.Type == TokenType.Pipe) { if (functionCall != null) { functionCall.Arguments.Add(leftOperand); leftOperand = functionCall; } var pipeCall = Open<ScriptPipeCall>(); pipeCall.From = leftOperand; NextToken(); // skip | // unit test: 310-func-pipe-error1.txt pipeCall.To = ExpectAndParseExpression(pipeCall, ref hasAnonymousFunction); return Close(pipeCall); } break; } if (functionCall != null) { functionCall.Arguments.Add(leftOperand); return functionCall; } return Close(leftOperand); } finally { expressionLevel--; } }
public override async ValueTask <object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray scriptArguments, ScriptBlockStatement blockStatement) { Array paramArguments = null; var arguments = PrepareArguments(context, callerContext, scriptArguments, ref paramArguments); try { // Call the method via reflection var result = InvokeImpl(context, callerContext.Span, arguments); return(IsAwaitable ? await ConfigureAwait(result) : result); } catch (TargetInvocationException exception) { if (exception.InnerException != null) { throw exception.InnerException; } throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception when calling {callerContext}"); } finally { context.ReleaseReflectionArguments(arguments); } }
public object Evaluate(TemplateContext context, ScriptNode callerContext, ScriptArray parameters, ScriptBlockStatement blockStatement) { return customFunction(context, callerContext, parameters); }
internal void EnterFunction(ScriptNode caller) { functionDepth++; if (functionDepth > RecursiveLimit) { throw new ScriptRuntimeException(caller.Span, $"Exceeding number of recursive depth limit [{RecursiveLimit}] for function call: [{caller}]"); // unit test: 305-func-error2.txt } PushVariableScope(ScriptVariableScope.Local); }
/// <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> /// <remarks> /// <see cref="Result"/> is set to null when calling directly this method. /// </remarks> public object Evaluate(ScriptNode scriptNode, bool aliasReturnedFunction) { var previousFunctionCallState = isFunctionCallDisabled; try { isFunctionCallDisabled = aliasReturnedFunction; scriptNode?.Evaluate(this); var result = Result; Result = null; return result; } finally { isFunctionCallDisabled = previousFunctionCallState; } }
/// <summary> /// Evaluates the specified script node. /// </summary> /// <param name="scriptNode">The script node.</param> /// <returns>The result of the evaluation.</returns> /// <remarks> /// <see cref="Result"/> is set to null when calling directly this method. /// </remarks> public object Evaluate(ScriptNode scriptNode) { return Evaluate(scriptNode, false); }
public object Evaluate(TemplateContext context, ScriptNode callerContext, ScriptArray parameters, ScriptBlockStatement blockStatement) { // Check parameters if ((hasObjectParams && parameters.Count < parametersInfo.Length - 1) || (!hasObjectParams && parameters.Count != parametersInfo.Length)) { throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments passed [{parameters.Count}] while expecting [{parametersInfo.Length}] for [{callerContext}]"); } // Convert arguments var arguments = new object[parametersInfo.Length]; object[] paramArguments = null; if (hasObjectParams) { paramArguments = new object[parameters.Count - lastParamsIndex]; arguments[lastParamsIndex] = paramArguments; } for (int i = 0; i < parameters.Count; i++) { var destType = hasObjectParams && i >= lastParamsIndex ? typeof(object) : parametersInfo[i].ParameterType; try { var argValue = ScriptValueConverter.ToObject(callerContext.Span, parameters[i], destType); if (hasObjectParams && i >= lastParamsIndex) { paramArguments[i - lastParamsIndex] = argValue; } else { arguments[i] = argValue; } } catch (Exception exception) { throw new ScriptRuntimeException(callerContext.Span, $"Unable to convert parameter #{i} of type [{parameters[i]?.GetType()}] to type [{destType}]", exception); } } // Call method try { var result = method.Invoke(target, arguments); return result; } catch (Exception exception) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception when calling {callerContext}", exception); } }
public ValueTask <object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement) { return(new ValueTask <object>(_customFunction(context, callerContext, arguments))); }
private static object Slice(TemplateContext context, ScriptNode callerContext, ScriptArray parameters) { if (parameters.Count < 2 || parameters.Count > 3) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected number of arguments [{parameters.Count}] for slice. Expecting at least 2 parameters <start> <length>? <text>"); } var text = ScriptValueConverter.ToString(callerContext.Span, parameters[parameters.Count - 1]); var start = ScriptValueConverter.ToInt(callerContext.Span, parameters[0]); var length = -1; if (parameters.Count == 3) { length = ScriptValueConverter.ToInt(callerContext.Span, parameters[1]); } return Slice(text, start, length); }
private ScriptExpression ParseExpression(ScriptNode parentNode, ScriptExpression parentExpression = null, int precedence = 0) { bool hasAnonymousFunction = false; return ParseExpression(parentNode, ref hasAnonymousFunction, parentExpression, precedence); }
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray parameters, ScriptBlockStatement blockStatement) { var expectedNumberOfParameters = parametersInfo.Length; if (hasTemplateContext) { expectedNumberOfParameters--; if (hasSpan) { expectedNumberOfParameters--; } } // Check parameters if ((hasObjectParams && parameters.Count < expectedNumberOfParameters - 1) || (!hasObjectParams && parameters.Count != expectedNumberOfParameters)) { throw new ScriptRuntimeException(callerContext.Span, $"Invalid number of arguments passed [{parameters.Count}] while expecting [{expectedNumberOfParameters}] for [{callerContext}]"); } // Convert arguments var arguments = new object[parametersInfo.Length]; object[] paramArguments = null; if (hasObjectParams) { paramArguments = new object[parameters.Count - lastParamsIndex]; arguments[lastParamsIndex] = paramArguments; } // Copy TemplateContext/SourceSpan parameters int argIndex = 0; if (hasTemplateContext) { arguments[0] = context; argIndex++; if (hasSpan) { arguments[1] = callerContext.Span; argIndex++; } } for (int i = 0; i < parameters.Count; i++, argIndex++) { var destType = hasObjectParams && i >= lastParamsIndex ? typeof(object) : parametersInfo[argIndex].ParameterType; try { var argValue = context.ToObject(callerContext.Span, parameters[i], destType); if (hasObjectParams && i >= lastParamsIndex) { paramArguments[argIndex - lastParamsIndex] = argValue; } else { arguments[argIndex] = argValue; } } catch (Exception exception) { throw new ScriptRuntimeException(callerContext.Span, $"Unable to convert parameter #{i} of type [{parameters[i]?.GetType()}] to type [{destType}]", exception); } } // Call method try { var result = method.Invoke(target, arguments); return(result); } catch (Exception exception) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception when calling {callerContext}", exception); } }
public static object Call(TemplateContext context, ScriptNode callerContext, object functionObject, 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 object function [{functionObject?.GetType()}]"); } ScriptBlockStatement blockDelegate = null; if (context.BlockDelegates.Count > 0) { blockDelegate = context.BlockDelegates.Pop(); } var argumentValues = new ScriptArray(); if (arguments != null) { foreach (var argument in arguments) { var value = context.Evaluate(argument); // Handle parameters expansion for a function call when the operator ^ is used var unaryExpression = argument as ScriptUnaryExpression; if (unaryExpression != null && unaryExpression.ExpandParameters(value, argumentValues)) { continue; } argumentValues.Add(value); } } // Handle pipe arguments here if (context.PipeArguments.Count > 0) { var additionalArgument = context.PipeArguments.Pop(); var value = context.Evaluate(additionalArgument); // Handle parameters expansion for a function call when the operator ~ is used var unaryExpression = additionalArgument as ScriptUnaryExpression; if (unaryExpression == null || !unaryExpression.ExpandParameters(value, argumentValues)) { argumentValues.Add(value); } } object result = null; context.EnterFunction(callerContext); try { if (externFunction != null) { result = externFunction.Evaluate(context, callerContext, argumentValues, blockDelegate); } else { context.SetValue(ScriptVariable.Arguments, argumentValues, true); // Set the block delegate if (blockDelegate != null) { context.SetValue(ScriptVariable.BlockDelegate, blockDelegate, true); } result = context.Evaluate(function.Body); } } finally { context.ExitFunction(); } // Restore the flow state to none context.FlowState = ScriptFlowState.None; return result; }
private static object Sort(TemplateContext context, ScriptNode callerContext, ScriptArray parameters) { if (parameters.Count < 1 || parameters.Count > 2) { throw new ScriptRuntimeException(callerContext.Span, $"Unexpected number of arguments [{parameters.Count}] for sort. Expecting at least 1 parameter <property>? <array>"); } var target = parameters[parameters.Count - 1]; string member = null; if (parameters.Count == 2) { member = ScriptValueConverter.ToString(callerContext.Span, parameters[0]); } return Sort(context, target, member); }