public Expression CalculateScriptFunctionCall(ScriptFunctionCall scriptFunctionCall, ParameterFinder parameterFinder, List <Expression> arguments)
        {
            var args = scriptFunctionCall.Arguments.Select(arg => GetExpressionBody(arg, parameterFinder, null));

            //add first argument if it's being supplied (probably from ScriptPipeCall)
            if (arguments != null)
            {
                args = arguments.Union(args);
            }

            // we are attempting to pull the bottom target member up, so we know the method Name
            var toMember = scriptFunctionCall.Target as ScriptMemberExpression;

            if (toMember == null)
            {
                throw new NotSupportedException();
            }
            else
            {
                var            argumentTypes      = args.ToNullSafe().Select(e => e.Type);
                var            targetType         = GetExpressionBody(toMember.Target, parameterFinder, null);
                ScriptVariable functionNameScript = toMember.Member;
                var            methodInfo         = memberFinder.FindMember(targetType.Type, functionNameScript.Name, argumentTypes.ToArray());
                var            convertedArgs      = ConvertArgs(methodInfo as MethodInfo, args);
                return(ExpressionHelpers.CallMember(targetType, methodInfo, convertedArgs));
            }
        }
示例#2
0
        /// <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);
        }
示例#3
0
        private void TransformLiquidFunctionCallToScriban(ScriptFunctionCall functionCall)
        {
            var    liquidTarget = functionCall.Target as ScriptVariable;
            string targetName;
            string memberName;

            // In case of cycle we transform it to array.cycle at runtime
            if (liquidTarget != null && LiquidBuiltinsFunctions.TryLiquidToScriban(liquidTarget.Name, out targetName, out memberName))
            {
                var arrayCycle = new ScriptMemberExpression
                {
                    Span   = liquidTarget.Span,
                    Target = new ScriptVariableGlobal(targetName)
                    {
                        Span = liquidTarget.Span
                    },
                    Member = new ScriptVariableGlobal(memberName)
                    {
                        Span = liquidTarget.Span
                    },
                };

                // Transfer trivias accordingly to target (trivias before) and member (trivias after)
                if (_isKeepTrivia && liquidTarget.Trivias != null)
                {
                    arrayCycle.Target.AddTrivias(liquidTarget.Trivias.Before, true);
                    arrayCycle.Member.AddTrivias(liquidTarget.Trivias.After, false);
                }
                functionCall.Target = arrayCycle;
            }
        }
示例#4
0
        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);
            }
        }
示例#5
0
        public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
        {
            if (arguments == null || arguments.Count == 0)
            {
                throw new ScriptRuntimeException(callerContext.Span, string.Format(RS.BadFunctionInvokeArgEmpty, "where"));
            }

            if (arguments.Count == 1)
            {
                return(arguments[0]);
            }

            // the second argument is a condition
            ScriptFunctionCall funcCall  = (ScriptFunctionCall)callerContext;
            ScriptExpression   condition = funcCall.Arguments[0];

            // hold the result in an array
            ScriptArray result = new ScriptArray();

            // the first argument doesn't have to be an array
            try
            {
                // string is enumerable but we want to treat it as a whole
                if (arguments[0] is string)
                {
                    throw new InvalidCastException();
                }

                IEnumerable array = (IEnumerable)arguments[0];
                foreach (object item in array)
                {
                    if ((bool)ReplaceCondition(condition, item).Evaluate(context))
                    {
                        result.Add(item);
                    }
                }
            }
            catch (InvalidCastException)
            {
                if ((bool)ReplaceCondition(condition, arguments[0]).Evaluate(context))
                {
                    return(arguments[0]);
                }
            }
            catch (Exception ex)
            {
                throw new ScriptParserRuntimeException(callerContext.Span, string.Format("RS.FilterFunctionException"), null, ex);
            }

            return(result);
        }
示例#6
0
        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>
        /// Parses the referenced include templates from a template
        /// </summary>
        /// <param name="template">Template to parse</param>
        /// <returns>List of referenced include</returns>
        private async Task <List <IncludeExportTemplateReference> > ParseIncludeTemplates(ExportTemplate template)
        {
            Template parsedTemplate = Template.Parse(template.Code);

            List <IncludeExportTemplateReference> includeTemplateRefs = new List <IncludeExportTemplateReference>();
            List <ScriptStatement> statementsToCheck = ScribanStatementExtractor.ExtractPlainNonTextStatements(parsedTemplate);

            foreach (ScriptStatement curStatement in statementsToCheck)
            {
                if (!(curStatement is ScriptExpressionStatement))
                {
                    continue;
                }

                ScriptExpressionStatement expressionStatement = (ScriptExpressionStatement)curStatement;
                if (expressionStatement.Expression == null || !(expressionStatement.Expression is ScriptFunctionCall))
                {
                    continue;
                }

                ScriptFunctionCall functionCall = (ScriptFunctionCall)expressionStatement.Expression;
                if (functionCall.Target == null || functionCall.Target.ToString() != "include")
                {
                    continue;
                }

                if (functionCall.Arguments.Count < 1)
                {
                    continue;
                }

                string templateName = functionCall.Arguments[0].ToString().Trim('\"').Trim('\'');
                IncludeExportTemplate existingTemplate = await _cachedDbAccess.GetIncludeTemplateByName(template.ProjectId, templateName);

                if (existingTemplate != null && !includeTemplateRefs.Any(e => e.IncludeTemplateId == existingTemplate.Id))
                {
                    includeTemplateRefs.Add(new IncludeExportTemplateReference {
                        IncludeTemplateId = existingTemplate.Id,
                        Name = existingTemplate.Name
                    });
                }
            }

            return(includeTemplateRefs);
        }
示例#8
0
        /// <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>
        /// Returns a language key
        /// </summary>
        /// <param name="templateContext">Template context</param>
        /// <param name="callerContext">Call context</param>
        /// <param name="arguments">Arguments</param>
        /// <returns>Language key</returns>
        private async ValueTask <object> GetLanguageKey(TemplateContext templateContext, ScriptNode callerContext, ScriptArray arguments)
        {
            ScriptFunctionCall functionCall = callerContext as ScriptFunctionCall;

            if (functionCall == null)
            {
                _errorCollection.AddCanNotGenerateLanguageKey(ScribanErrorUtil.FormatScribanSpan(callerContext.Span));
                return("<<USE NON PIPE NOTATION FOR LANGKEY>>");
            }

            if (functionCall.Arguments.Count != 1 || arguments.Count != 1)
            {
                _errorCollection.AddCanNotGenerateLanguageKey(ScribanErrorUtil.FormatScribanSpan(callerContext.Span));
                return("<<PASS IN EXACTLY ONE ARGUMENT FOR LANGKEY>>");
            }

            List <string> callHierarchy = ScribanParsingUtil.GetCallerHierarchy(functionCall.Arguments[0]);

            return(await GenerateLanguageKeyFromCallHierarchy(templateContext, callerContext, callHierarchy));
        }
示例#10
0
        /// <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);
        }
示例#11
0
        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());
        }
示例#12
0
        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);
            }
        }
示例#13
0
        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--;
            }
        }
示例#14
0
        private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0,
                                                 ParseExpressionMode mode = ParseExpressionMode.Default)
        {
            int expressionCount = 0;

            _expressionLevel++;
            var expressionDepthBeforeEntering = _expressionDepth;

            EnterExpression();
            try
            {
                ScriptFunctionCall functionCall = null;
parseExpression:
                expressionCount++;
                ScriptExpression leftOperand = null;
                switch (Current.Type)
                {
                case TokenType.Identifier:
                case TokenType.IdentifierSpecial:
                    leftOperand = ParseVariable();

                    // In case of liquid template, we accept the syntax colon after a tag
                    if (_isLiquid && parentNode is ScriptPipeCall && Current.Type == TokenType.Colon)
                    {
                        NextToken();
                    }

                    // 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.ImplicitString:
                    leftOperand = ParseImplicitString();
                    break;

                case TokenType.VerbatimString:
                    leftOperand = ParseVerbatimString();
                    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)
                {
                    if (functionCall != null)
                    {
                        LogError($"Unexpected token `{GetAsText(Current)}` while parsing function call `{functionCall}`");
                    }
                    else
                    {
                        LogError($"Unexpected token `{GetAsText(Current)}` while parsing expression");
                    }
                    return(null);
                }

                if (leftOperand is ScriptAnonymousFunction)
                {
                    hasAnonymousFunction = true;
                }

                while (!hasAnonymousFunction)
                {
                    if (_isLiquid && Current.Type == TokenType.Comma && functionCall != null)
                    {
                        NextToken(); // Skip the comma for arguments in a function call
                    }

                    // 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();

                            if (GetAsText(Current) == "empty" && PeekToken().Type == TokenType.Question)
                            {
                                var memberExpression = Open <ScriptIsEmptyExpression>();
                                NextToken(); // skip empty
                                NextToken(); // skip ?
                                memberExpression.Target = leftOperand;
                                leftOperand             = Close(memberExpression);
                            }
                            else
                            {
                                var memberExpression = Open <ScriptMemberExpression>();
                                memberExpression.Target = leftOperand;
                                var member = ParseVariable();
                                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 IScriptVariablePath && !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 (mode == ParseExpressionMode.BasicExpression)
                    {
                        break;
                    }

                    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 = TransformKeyword(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) || (_isLiquid && TryLiquidBinaryOperator(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;
                        }

                        // We fake entering an expression here to limit the number of expression
                        EnterExpression();
                        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 (StartAsExpression())
                    {
                        // If we can parse a statement, we have a method call
                        if (parentExpression != null)
                        {
                            break;
                        }

                        // Parse named parameters
                        var paramContainer = parentNode as IScriptNamedArgumentContainer;
                        if (Current.Type == TokenType.Identifier && (parentNode is IScriptNamedArgumentContainer || !_isLiquid && PeekToken().Type == TokenType.Colon))
                        {
                            if (paramContainer == null)
                            {
                                if (functionCall == null)
                                {
                                    functionCall            = Open <ScriptFunctionCall>();
                                    functionCall.Target     = leftOperand;
                                    functionCall.Span.Start = leftOperand.Span.Start;
                                }
                                else
                                {
                                    functionCall.Arguments.Add(leftOperand);
                                }
                                Close(leftOperand);
                            }

                            while (true)
                            {
                                if (Current.Type != TokenType.Identifier)
                                {
                                    break;
                                }

                                var parameter     = Open <ScriptNamedArgument>();
                                var parameterName = GetAsText(Current);
                                parameter.Name = parameterName;

                                // Skip argument name
                                NextToken();

                                if (paramContainer != null)
                                {
                                    paramContainer.AddParameter(Close(parameter));
                                }
                                else
                                {
                                    functionCall.Arguments.Add(parameter);
                                }

                                // If we have a colon, we have a value
                                // otherwise it is a boolean argument name
                                if (Current.Type == TokenType.Colon)
                                {
                                    NextToken();
                                    parameter.Value    = ExpectAndParseExpression(parentNode, mode: ParseExpressionMode.BasicExpression);
                                    parameter.Span.End = parameter.Value.Span.End;
                                }

                                if (functionCall != null)
                                {
                                    functionCall.Span.End = parameter.Span.End;
                                }
                            }

                            // As we have handled leftOperand here, we don't let the function out of this while to pick up the leftOperand
                            if (functionCall != null)
                            {
                                leftOperand  = functionCall;
                                functionCall = null;
                            }

                            // We don't allow anything after named parameters
                            break;
                        }

                        if (functionCall == null)
                        {
                            functionCall        = Open <ScriptFunctionCall>();
                            functionCall.Target = leftOperand;

                            // If we need to convert liquid to scriban functions:
                            if (_isLiquid && Options.LiquidFunctionsToScriban)
                            {
                                TransformLiquidFunctionCallToScriban(functionCall);
                            }

                            functionCall.Span.Start = leftOperand.Span.Start;
                        }
                        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);
                    functionCall.Span.End = leftOperand.Span.End;
                    return(functionCall);
                }
                return(Close(leftOperand));
            }
            finally
            {
                LeaveExpression();
                // Force to restore back to a level
                _expressionDepth = expressionDepthBeforeEntering;
                _expressionLevel--;
            }
        }
        private ScriptStatement ParseLiquidIncludeStatement()
        {
            ScriptFunctionCall include = Open <ScriptFunctionCall>();

            include.Target = ParseVariable();

            Token            templateNameToken = Current;
            ScriptExpression templateName      = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression);

            if (templateName != null)
            {
                var literal = templateName as ScriptLiteral;
                if (!(literal?.Value is string || templateName is IScriptVariablePath))
                {
                    LogError(templateNameToken, string.Format(RS.UnexpectedIncludeTemplateName, templateName));
                }

                include.Arguments.Add(templateName);
            }
            Close(include);

            ScriptExpressionStatement includeStatement = new ScriptExpressionStatement()
            {
                Span = include.Span, Expression = include
            };

            ScriptForStatement   forStatement = null;
            ScriptBlockStatement block        = null;

            if (Current.Type == TokenType.Identifier)
            {
                string next = GetAsText(Current);
                // Parse with <value>
                // Create a block statement equivalent:
                // this[target] = value;
                // include target
                if (next == "with")
                {
                    NextToken(); // skip with

                    ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>();
                    assignExpression.Target = new ScriptIndexerExpression()
                    {
                        Target = new ScriptThisExpression {
                            Span = CurrentSpan
                        },
                        Index = templateName
                    };

                    assignExpression.Value = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression);
                    Close(assignExpression);

                    block = new ScriptBlockStatement {
                        Span = include.Span
                    };
                    block.Statements.Add(new ScriptExpressionStatement()
                    {
                        Span       = assignExpression.Span,
                        Expression = assignExpression
                    });
                    block.Statements.Add(includeStatement);
                    Close(block);
                }
                else if (next == "for")
                {
                    // Create a block statement equivalent:
                    // for this[target] in value
                    //  include target
                    // end

                    NextToken(); // skip for

                    forStatement          = Open <ScriptForStatement>();
                    forStatement.Variable = new ScriptIndexerExpression()
                    {
                        Target = new ScriptThisExpression {
                            Span = CurrentSpan
                        },
                        Index = templateName
                    };

                    forStatement.Iterator = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression);

                    forStatement.Body = new ScriptBlockStatement()
                    {
                        Span = include.Span
                    };
                    forStatement.Body.Statements.Add(includeStatement);
                    Close(forStatement);
                }

                // For following variable separated by colon, add them as assignment before the include
                while (Current.Type == TokenType.Identifier)
                {
                    Token            variableToken  = Current;
                    ScriptExpression variableObject = ParseVariable();

                    var variable = variableObject as ScriptVariable;
                    if (variable == null)
                    {
                        LogError(variableToken, string.Format(RS.UnexpectedVariableInInclude, GetAsText(variableToken)));
                    }

                    if (Current.Type == TokenType.Colon)
                    {
                        NextToken(); // skip :
                    }
                    else
                    {
                        LogError(Current, string.Format(RS.UnexpectedTokenAfterVariable, GetAsText(Current), variable, ":"));
                    }

                    if (block == null)
                    {
                        block = new ScriptBlockStatement {
                            Span = include.Span
                        };
                        block.Statements.Add(includeStatement);
                    }

                    ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>();
                    assignExpression.Target = variable;
                    assignExpression.Value  = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression);

                    block.Statements.Insert(0, new ScriptExpressionStatement()
                    {
                        Span       = assignExpression.Span,
                        Expression = assignExpression
                    });

                    if (Current.Type == TokenType.Comma)
                    {
                        NextToken();
                    }
                }

                ExpectEndOfStatement(includeStatement);

                // If we only have an include for, return it directly
                if (forStatement != null)
                {
                    if (block == null)
                    {
                        return(Close(forStatement));
                    }

                    block.Statements.Add(forStatement);
                }

                // Else we have a block
                if (block != null)
                {
                    Close(block);
                    return(block);
                }
            }

            ExpectEndOfStatement(includeStatement);
            return(Close(includeStatement));
        }
        private ScriptExpressionStatement ParseLiquidCycleStatement()
        {
            ScriptExpressionStatement statement    = Open <ScriptExpressionStatement>();
            ScriptFunctionCall        functionCall = Open <ScriptFunctionCall>();

            statement.Expression = functionCall;
            functionCall.Target  = ParseVariable();

            if (Options.ConvertLiquidFunctions)
            {
                TransformFromLiquidFunctionCall(functionCall);
            }

            ScriptArrayInitializerExpression arrayInit = null;

            // Parse cycle without group: cycle "a", "b", "c" => transform to textscript: array.cycle ["a", "b", "c"]
            // Parse cycle with group: cycle "group1": "a", "b", "c" => transform to textscript: array.cycle ["a", "b", "c"] "group1"

            bool isFirst = true;

            while (IsVariableOrLiteral(Current))
            {
                ScriptExpression value = ParseVariableOrLiteral();

                if (isFirst && Current.Type == TokenType.Colon)
                {
                    NextToken(); // Skip :
                    ScriptNamedArgument namedArg = Open <ScriptNamedArgument>();
                    namedArg.Name  = "group";
                    namedArg.Value = value;
                    Close(namedArg);
                    namedArg.Span = value.Span;

                    isFirst = false;
                    functionCall.Arguments.Add(namedArg);
                    continue;
                }

                if (arrayInit == null)
                {
                    arrayInit = Open <ScriptArrayInitializerExpression>();
                    functionCall.Arguments.Insert(0, arrayInit);
                    arrayInit.Span.Start = value.Span.Start;
                }

                arrayInit.Values.Add(value);
                arrayInit.Span.End = value.Span.End;

                if (Current.Type == TokenType.Comma)
                {
                    NextToken();
                }
                else if (Current.Type == TokenType.LiquidTagExit)
                {
                    break;
                }
                else
                {
                    LogError(Current, string.Format(RS.UnexpectedTokenAfterCycle, GetAsText(Current), value));
                    NextToken();
                    break;
                }
            }

            Close(functionCall);

            ExpectEndOfStatement(statement);
            return(Close(statement));
        }
示例#17
0
        /// <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);
        }
示例#18
0
        public ScriptExpression ReplaceCondition(ScriptExpression condition, object replaceValue)
        {
            if (condition is ScriptVariableLocal localVar)
            {
                if (localVar.Name == string.Empty)
                {
                    return(new ScriptLiteral(replaceValue));
                }
                else
                {
                    return(localVar);
                }
            }
            else if (condition is ScriptMemberExpression objExpr)
            {
                return(new ScriptMemberExpression()
                {
                    Member = objExpr.Member,
                    Trivias = objExpr.Trivias,
                    Target = ReplaceCondition(objExpr.Target, replaceValue)
                });
            }
            else if (condition is ScriptNestedExpression nestedExpr)
            {
                return(new ScriptNestedExpression()
                {
                    Expression = ReplaceCondition(nestedExpr.Expression, replaceValue),
                    Trivias = nestedExpr.Trivias
                });
            }
            else if (condition is ScriptFunctionCall funcCall)
            {
                ScriptFunctionCall funcResult = new ScriptFunctionCall()
                {
                    Target  = funcCall.Target,
                    Trivias = funcCall.Trivias
                };

                foreach (ScriptExpression expr in funcCall.Arguments)
                {
                    funcResult.Arguments.Add(ReplaceCondition(expr, replaceValue));
                }

                return(funcResult);
            }
            else if (condition is ScriptPipeCall pipeCall)
            {
                ScriptPipeCall pipeResult = new ScriptPipeCall()
                {
                    From    = ReplaceCondition(pipeCall.From, replaceValue),
                    To      = ReplaceCondition(pipeCall.To, replaceValue),
                    Trivias = pipeCall.Trivias
                };

                return(pipeResult);
            }
            else if (condition is ScriptBinaryExpression binCondition)
            {
                ScriptBinaryExpression binExprResult = new ScriptBinaryExpression()
                {
                    Left     = ReplaceCondition(binCondition.Left, replaceValue),
                    Right    = ReplaceCondition(binCondition.Right, replaceValue),
                    Operator = binCondition.Operator,
                    Trivias  = binCondition.Trivias
                };

                return(binExprResult);
            }
            else
            {
                return(condition);
            }
        }