private ScriptExpression ParseVariable()
        {
            var currentToken = Current;
            var currentSpan  = CurrentSpan;
            var endSpan      = currentSpan;
            var text         = GetAsText(currentToken);

            // Return ScriptLiteral for null, true, false
            // Return ScriptAnonymousFunction
            switch (text)
            {
            case "null":
                var nullValue = Open <ScriptLiteral>();
                NextToken();
                return(Close(nullValue));

            case "true":
                var trueValue = Open <ScriptLiteral>();
                trueValue.Value = true;
                NextToken();
                return(Close(trueValue));

            case "false":
                var falseValue = Open <ScriptLiteral>();
                falseValue.Value = false;
                NextToken();
                return(Close(falseValue));

            case "do":
                var functionExp = Open <ScriptAnonymousFunction>();
                functionExp.Function = ParseFunctionStatement(true);
                var func = Close(functionExp);
                return(func);

            case "this":
                if (!_isLiquid)
                {
                    var thisExp = Open <ScriptThisExpression>();
                    NextToken();
                    return(Close(thisExp));
                }
                break;
            }

            // Keeps trivia before this token
            List <ScriptTrivia> triviasBefore = null;

            if (_isKeepTrivia && _trivias.Count > 0)
            {
                triviasBefore = new List <ScriptTrivia>();
                triviasBefore.AddRange(_trivias);
                _trivias.Clear();
            }

            NextToken();
            var scope = ScriptVariableScope.Global;

            if (text.StartsWith("$"))
            {
                scope = ScriptVariableScope.Local;
                text  = text.Substring(1);

                // Convert $0, $1... $n variable into $[0] $[1]...$[n] variables
                int index;
                if (int.TryParse(text, out index))
                {
                    var indexerExpression = new ScriptIndexerExpression {
                        Span = currentSpan,

                        Target = new ScriptVariableLocal(ScriptVariable.Arguments.Name)
                        {
                            Span = currentSpan
                        },

                        Index = new ScriptLiteral()
                        {
                            Span = currentSpan, Value = index
                        }
                    };

                    if (_isKeepTrivia)
                    {
                        if (triviasBefore != null)
                        {
                            indexerExpression.Target.AddTrivias(triviasBefore, true);
                        }
                        FlushTrivias(indexerExpression.Index, false);
                    }

                    return(indexerExpression);
                }
            }
            else if (text == "for" || text == "while" || text == "tablerow" || (_isLiquid && (text == "forloop" || text == "tablerowloop")))
            {
                if (Current.Type == TokenType.Dot)
                {
                    NextToken();
                    if (Current.Type == TokenType.Identifier)
                    {
                        endSpan = CurrentSpan;
                        var loopVariableText = GetAsText(Current);
                        NextToken();

                        scope = ScriptVariableScope.Loop;
                        if (_isLiquid)
                        {
                            switch (loopVariableText)
                            {
                            case "first":
                                text = ScriptVariable.LoopFirst.Name;
                                break;

                            case "last":
                                text = ScriptVariable.LoopLast.Name;
                                break;

                            case "index0":
                                text = ScriptVariable.LoopIndex.Name;
                                break;

                            case "rindex0":
                                text = ScriptVariable.LoopRIndex.Name;
                                break;

                            case "rindex":
                            case "index":
                                // Because forloop.index is 1 based index, we need to create a binary expression
                                // to support it here
                                bool isrindex = loopVariableText == "rindex";

                                var nested = new ScriptNestedExpression()
                                {
                                    Expression = new ScriptBinaryExpression()
                                    {
                                        Operator = ScriptBinaryOperator.Add,
                                        Left     = new ScriptVariableLoop(isrindex ? ScriptVariable.LoopRIndex.Name : ScriptVariable.LoopIndex.Name)
                                        {
                                            Span = currentSpan
                                        },
                                        Right = new ScriptLiteral(1)
                                        {
                                            Span = currentSpan
                                        },
                                        Span = currentSpan
                                    },
                                    Span = currentSpan
                                };

                                if (_isKeepTrivia)
                                {
                                    if (triviasBefore != null)
                                    {
                                        nested.AddTrivias(triviasBefore, true);
                                    }
                                    FlushTrivias(nested, false);
                                }
                                return(nested);

                            case "length":
                                text = ScriptVariable.LoopLength.Name;
                                break;

                            case "col":
                                if (text != "tablerowloop")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, $"The loop variable <{text}.col> is invalid");
                                }
                                text = ScriptVariable.TableRowCol.Name;
                                break;

                            default:
                                text = text + "." + loopVariableText;
                                LogError(currentToken, $"The liquid loop variable <{text}> is not supported");
                                break;
                            }
                        }
                        else
                        {
                            switch (loopVariableText)
                            {
                            case "first":
                                text = ScriptVariable.LoopFirst.Name;
                                break;

                            case "last":
                                if (text == "while")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, "The loop variable <while.last> is invalid");
                                }
                                text = ScriptVariable.LoopLast.Name;
                                break;

                            case "changed":
                                if (text == "while")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, "The loop variable <while.changed> is invalid");
                                }
                                text = ScriptVariable.LoopChanged.Name;
                                break;

                            case "length":
                                if (text == "while")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, "The loop variable <while.length> is invalid");
                                }
                                text = ScriptVariable.LoopLength.Name;
                                break;

                            case "even":
                                text = ScriptVariable.LoopEven.Name;
                                break;

                            case "odd":
                                text = ScriptVariable.LoopOdd.Name;
                                break;

                            case "index":
                                text = ScriptVariable.LoopIndex.Name;
                                break;

                            case "rindex":
                                if (text == "while")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, "The loop variable <while.rindex> is invalid");
                                }
                                text = ScriptVariable.LoopRIndex.Name;
                                break;

                            case "col":
                                if (text != "tablerow")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, $"The loop variable <{text}.col> is invalid");
                                }
                                text = ScriptVariable.TableRowCol.Name;
                                break;

                            default:
                                text = text + "." + loopVariableText;
                                // unit test: 108-variable-loop-error1.txt
                                LogError(currentToken, $"The loop variable <{text}> is not supported");
                                break;
                            }
                        }

                        // We no longer checks at parse time usage of loop variables, as they can be used in a wrap context
                        //if (!IsInLoop())
                        //{
                        //    LogError(currentToken, $"Unexpected variable <{text}> outside of a loop");
                        //}
                    }
                    else
                    {
                        LogError(currentToken, $"Invalid token `{Current.Type}`. The loop variable <{text}> dot must be followed by an identifier");
                    }
                }
            }
            else if (_isLiquid && text == "continue")
            {
                scope = ScriptVariableScope.Local;
            }

            var result = ScriptVariable.Create(text, scope);

            result.Span = new SourceSpan {
                FileName = currentSpan.FileName,
                Start    = currentSpan.Start,
                End      = endSpan.End
            };

            // A liquid variable can have `-` in its identifier
            // If this is the case, we need to translate it to `this["this"]` instead
            if (_isLiquid && text.IndexOf('-') >= 0)
            {
                var newExp = new ScriptIndexerExpression {
                    Target = new ScriptThisExpression()
                    {
                        Span = result.Span
                    },
                    Index = new ScriptLiteral(text)
                    {
                        Span = result.Span
                    },
                    Span = result.Span
                };

                // Flush any trivias after
                if (_isKeepTrivia)
                {
                    if (triviasBefore != null)
                    {
                        newExp.Target.AddTrivias(triviasBefore, true);
                    }
                    FlushTrivias(newExp, false);
                }
                // Return the expression
                return(newExp);
            }

            if (_isKeepTrivia)
            {
                // Flush any trivias after
                if (triviasBefore != null)
                {
                    result.AddTrivias(triviasBefore, true);
                }
                FlushTrivias(result, false);
            }
            return(result);
        }
Example #2
0
        private ScriptExpression ParseVariableOrLiteral()
        {
            var currentToken = Current;
            var currentSpan  = CurrentSpan;
            var endSpan      = currentSpan;
            var text         = GetAsText(currentToken);

            // Return ScriptLiteral for null, true, false
            // Return ScriptAnonymousFunction
            switch (text)
            {
            case "null":
                var nullValue = Open <ScriptLiteral>();
                NextToken();
                return(Close(nullValue));

            case "true":
                var trueValue = Open <ScriptLiteral>();
                trueValue.Value = true;
                NextToken();
                return(Close(trueValue));

            case "false":
                var falseValue = Open <ScriptLiteral>();
                falseValue.Value = false;
                NextToken();
                return(Close(falseValue));

            case "do":
                var functionExp = Open <ScriptAnonymousFunction>();
                functionExp.Function = ParseFunctionStatement(true);
                return(Close(functionExp));
            }

            NextToken();

            var scope = ScriptVariableScope.Global;

            if (text.StartsWith("$"))
            {
                scope = ScriptVariableScope.Local;
                text  = text.Substring(1);

                // Convert $0, $1... $n variable into $[0] $[1]...$[n] variables
                int index;
                if (int.TryParse(text, out index))
                {
                    var indexerExpression = new ScriptIndexerExpression
                    {
                        Span = currentSpan,

                        Target = new ScriptVariable(ScriptVariable.Arguments.Name, ScriptVariableScope.Local)
                        {
                            Span = currentSpan
                        },

                        Index = new ScriptLiteral()
                        {
                            Span = currentSpan, Value = index
                        }
                    };
                    return(indexerExpression);
                }
            }

            if (text == "for" || text == "while")
            {
                if (Current.Type == TokenType.Dot)
                {
                    NextToken();
                    if (Current.Type == TokenType.Identifier)
                    {
                        endSpan = CurrentSpan;
                        var loopVariableText = GetAsText(Current);
                        scope = ScriptVariableScope.Loop;
                        switch (loopVariableText)
                        {
                        case "first":
                            text = "for.first";
                            break;

                        case "last":
                            if (text == "while")
                            {
                                // unit test: 108-variable-loop-error2.txt
                                LogError(currentToken, "The loop variable <while.last> is invalid");
                            }
                            text = "for.last";
                            break;

                        case "even":
                            text = "for.even";
                            break;

                        case "odd":
                            text = "for.odd";
                            break;

                        case "index":
                            text = "for.index";
                            break;

                        default:
                            text = text + "." + loopVariableText;
                            // unit test: 108-variable-loop-error1.txt
                            LogError(currentToken, $"The loop variable <{text}> is not supported");
                            break;
                        }

                        // We no longer checks at parse time usage of loop variables, as they can be used in a wrap context
                        //if (!IsInLoop())
                        //{
                        //    LogError(currentToken, $"Unexpected variable <{text}> outside of a loop");
                        //}

                        NextToken();
                    }
                    else
                    {
                        LogError(currentToken, $"Invalid token [{Current.Type}]. The loop variable <{text}> dot must be followed by an identifier");
                    }
                }
                else
                {
                    LogError(currentToken, $"The reserved keyword <{text}> cannot be used as a variable");
                }
            }
            else if (IsKeyword(text))
            {
                // unit test: 108-variable-error1.txt
                LogError(currentToken, $"The reserved keyword <{text}> cannot be used as a variable");
            }

            var result = new ScriptVariable(text, scope)
            {
                Span = { FileName = currentSpan.FileName, Start = currentSpan.Start, End = endSpan.End }
            };

            return(result);
        }
Example #3
0
        private ScriptExpression ParseVariable()
        {
            var currentToken = Current;
            var currentSpan  = CurrentSpan;
            var endSpan      = currentSpan;
            var text         = GetAsText(currentToken);

            // Return ScriptLiteral for null, true, false
            // Return ScriptAnonymousFunction
            switch (text)
            {
            case "null":
                var nullValue = Open <ScriptLiteral>();
                NextToken();
                return(Close(nullValue));

            case "true":
                var trueValue = Open <ScriptLiteral>();
                trueValue.Value = true;
                NextToken();
                return(Close(trueValue));

            case "false":
                var falseValue = Open <ScriptLiteral>();
                falseValue.Value = false;
                NextToken();
                return(Close(falseValue));

            case "do":
                var functionExp = Open <ScriptAnonymousFunction>();
                functionExp.Function = ParseFunctionStatement(true);
                var func = Close(functionExp);
                return(func);

            case "this":
                if (!_isLiquid)
                {
                    var thisExp = Open <ScriptThisExpression>();
                    ExpectAndParseKeywordTo(thisExp.ThisKeyword);
                    return(Close(thisExp));
                }
                break;
            }

            // Keeps trivia before this token
            List <ScriptTrivia> triviasBefore = null;

            if (_isKeepTrivia && _trivias.Count > 0)
            {
                triviasBefore = new List <ScriptTrivia>();
                triviasBefore.AddRange(_trivias);
                _trivias.Clear();
            }

            NextToken();
            var scope = ScriptVariableScope.Global;

            if (text.StartsWith("$"))
            {
                scope = ScriptVariableScope.Local;
                text  = text.Substring(1);

                // Convert $0, $1... $n variable into $[0] $[1]...$[n] variables
                int index;
                if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out index))
                {
                    var target = new ScriptVariableLocal(ScriptVariable.Arguments.BaseName)
                    {
                        Span = currentSpan
                    };
                    var indexLiteral = new ScriptLiteral()
                    {
                        Span = currentSpan, Value = index
                    };

                    var indexerExpression = new ScriptIndexerExpression
                    {
                        Span   = currentSpan,
                        Target = target,
                        Index  = indexLiteral
                    };

                    if (_isKeepTrivia)
                    {
                        if (triviasBefore != null)
                        {
                            target.AddTrivias(triviasBefore, true);
                        }

                        // Special case, we add the trivias to the index as the [] brackets
                        // won't be output-ed
                        FlushTrivias(indexLiteral, false);
                        _lastTerminalWithTrivias = indexLiteral;
                    }

                    return(indexerExpression);
                }
            }
            else if (text == "for" || text == "while" || text == "tablerow" || (_isLiquid && (text == "forloop" || text == "tablerowloop")))
            {
                if (Current.Type == TokenType.Dot)
                {
                    scope = ScriptVariableScope.Loop;
                    var token = PeekToken();
                    if (token.Type == TokenType.Identifier)
                    {
                        //endSpan = GetSpanForToken(token);
                        var loopVariableText = GetAsText(token);
                        if (_isLiquid)
                        {
                            switch (loopVariableText)
                            {
                            case "first":
                            case "last":
                            case "index0":
                            case "rindex0":
                            case "index":
                            case "rindex":
                            case "length":
                                break;

                            case "col":
                                if (text != "tablerowloop")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, $"The loop variable <{text}.col> is invalid");
                                }
                                break;

                            default:
                                LogError(currentToken, $"The liquid loop variable <{text}.{loopVariableText}> is not supported");
                                break;
                            }

                            if (text == "forloop")
                            {
                                text = "for";
                            }
                            else if (text == "tablerowloop")
                            {
                                text = "tablerow";
                            }
                        }
                        else
                        {
                            switch (loopVariableText)
                            {
                            // supported by both for and while
                            case "first":
                            case "even":
                            case "odd":
                            case "index":
                                break;

                            case "last":
                            case "changed":
                            case "length":
                            case "rindex":
                                if (text == "while")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, $"The loop variable <while.{loopVariableText}> is invalid");
                                }
                                break;

                            case "col":
                                if (text != "tablerow")
                                {
                                    // unit test: 108-variable-loop-error2.txt
                                    LogError(currentToken, $"The loop variable <{text}.col> is invalid");
                                }
                                break;

                            default:
                                // unit test: 108-variable-loop-error1.txt
                                LogError(currentToken, $"The loop variable <{text}.{loopVariableText}> is not supported");
                                break;
                            }
                        }
                    }
                    else
                    {
                        LogError(currentToken, $"Invalid token `{GetAsText(Current)}`. The loop variable <{text}> dot must be followed by an identifier");
                    }
                }
            }
            else if (_isLiquid && text == "continue")
            {
                scope = ScriptVariableScope.Local;
            }

            var result = ScriptVariable.Create(text, scope);

            result.Span = new SourceSpan
            {
                FileName = currentSpan.FileName,
                Start    = currentSpan.Start,
                End      = endSpan.End
            };

            // A liquid variable can have `-` in its identifier
            // If this is the case, we need to translate it to `this["this"]` instead
            if (_isLiquid && text.IndexOf('-') >= 0)
            {
                var target = new ScriptThisExpression()
                {
                    Span = result.Span
                };
                var newExp = new ScriptIndexerExpression
                {
                    Target = target,
                    Index  = new ScriptLiteral(text)
                    {
                        Span = result.Span
                    },
                    Span = result.Span
                };

                // Flush any trivias after
                if (_isKeepTrivia)
                {
                    if (triviasBefore != null)
                    {
                        target.ThisKeyword.AddTrivias(triviasBefore, true);
                    }
                    FlushTrivias(newExp.CloseBracket, false);
                    _lastTerminalWithTrivias = newExp.CloseBracket;
                }

                // Return the expression
                return(newExp);
            }

            if (_isKeepTrivia)
            {
                // Flush any trivias after
                if (triviasBefore != null)
                {
                    result.AddTrivias(triviasBefore, true);
                }
                FlushTrivias(result, false);

                _lastTerminalWithTrivias = result;
            }

            return(result);
        }
Example #4
0
        private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0,
                                                 ParseExpressionMode mode = ParseExpressionMode.Default)
        {
            int expressionCount = 0;

            _expressionLevel++;
            int 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(RS.DelegateBlockInNestedExpr);
                        }

                        if (!(parentNode is ScriptExpressionStatement))
                        {
                            LogError(parentNode, RS.DelegateBlockOutsideExpr);
                        }

                        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(string.Format(RS.UnexpectedTokenInFunc, GetAsText(Current), functionCall));
                    }
                    else
                    {
                        LogError(string.Format(RS.UnexpectedTokenInExpr, GetAsText(Current)));
                    }

                    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)
                    {
                        Token nextToken = PeekToken();
                        if (nextToken.Type == TokenType.Identifier)
                        {
                            NextToken();

                            if (GetAsText(Current) == "empty" && PeekToken().Type == TokenType.Question)
                            {
                                ScriptIsEmptyExpression memberExpression = Open <ScriptIsEmptyExpression>();
                                NextToken(); // skip empty
                                NextToken(); // skip ?
                                memberExpression.Target = leftOperand;
                                leftOperand             = Close(memberExpression);
                            }
                            else
                            {
                                ScriptMemberExpression memberExpression = Open <ScriptMemberExpression>();
                                memberExpression.Target = leftOperand;
                                ScriptExpression member = ParseVariable();
                                if (!(member is ScriptVariable))
                                {
                                    LogError(string.Format(RS.UnexpectedLiteralMember, member));
                                    return(null);
                                }
                                memberExpression.Member = (ScriptVariable)member;
                                leftOperand             = Close(memberExpression);
                            }
                        }
                        else
                        {
                            LogError(nextToken, string.Format(RS.InvalidTokenAfterDot, nextToken.Type));
                            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();
                        ScriptIndexerExpression indexerExpression = Open <ScriptIndexerExpression>();
                        indexerExpression.Target = leftOperand;
                        // unit test: 130-indexer-accessor-error5.txt
                        indexerExpression.Index = ExpectAndParseExpression(indexerExpression, ref hasAnonymousFunction, functionCall, 0, string.Format(RS.ExpectToken, "index_expression", Current.Type));

                        if (Current.Type != TokenType.CloseBracket)
                        {
                            LogError(string.Format(RS.ExpectToken, "]", Current.Type));
                        }
                        else
                        {
                            NextToken();
                        }

                        leftOperand = Close(indexerExpression);
                        continue;
                    }

                    if (mode == ParseExpressionMode.BasicExpression)
                    {
                        break;
                    }

                    if (Current.Type == TokenType.Equal)
                    {
                        ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>();

                        if (_expressionLevel > 1)
                        {
                            // unit test: 101-assign-complex-error1.txt
                            LogError(assignExpression, RS.ExprForTopLevelAssignmentOnly);
                        }

                        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)))
                    {
                        int 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();
                        ScriptBinaryExpression 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,
                                                                          string.Format(RS.ExpectTokenInRightOperator, "expression", 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;
                                }

                                ScriptNamedArgument parameter = Open <ScriptNamedArgument>();
                                string 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 textscript functions:
                            if (_isLiquid && Options.ConvertLiquidFunctions)
                            {
                                TransformFromLiquidFunctionCall(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;
                        }

                        ScriptPipeCall 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--;
            }
        }