private ScriptStatement ParseLiquidExpressionStatement(ScriptStatement parent)
        {
            Token startToken = Current;

            CheckNotInCase(parent, startToken);
            ScriptExpressionStatement expressionStatement = ParseExpressionStatement();

            ScriptExpressionStatement statement = expressionStatement;

            // NOTE: We were previously performing the following checks
            // but as liquid doesn't have a strict syntax, we are instead not enforcing any kind of rules
            // so that the parser can still read custom liquid tags/object expressions, assuming that
            // they are not using fancy argument syntaxes (which are unfortunately allowed in liquid)

            //var functionCall = expressionStatement.Expression as ScriptFunctionCall;
            //// Otherwise it is an expression statement
            //if (functionCall != null)
            //{
            //    if (!_isLiquidTagSection)
            //    {
            //        LogError(startToken, $"The `{functionCall}` statement must be in a tag section `{{% ... %}}`");
            //    }
            //}
            //else if (_isLiquidTagSection)
            //{
            //    LogError(startToken, $"Expecting the expression `{GetAsText(startToken)}` to be in an object section `{{{{ ... }}}}`");
            //}
            //else if (!(expressionStatement.Expression is IScriptVariablePath || expressionStatement.Expression is ScriptPipeCall))
            //{
            //    LogError(statement, $"The <{expressionStatement.Expression}> is not allowed in this context");
            //}
            return(statement);
        }
        /// <summary>
        /// Builds a script expression to get a parent object
        /// </summary>
        /// <param name="callHierarchy">Call hierarchy</param>
        /// <returns>Script expression</returns>
        private ScriptNode BuildParentObjectValueScriptExpression(List <string> callHierarchy)
        {
            ScriptNode parentObjectExpression = null;

            if (callHierarchy.Count == 2)
            {
                parentObjectExpression = new ScriptVariableGlobal(callHierarchy[callHierarchy.Count - 2]);
            }
            else
            {
                ScriptExpressionStatement statement           = new ScriptExpressionStatement();
                ScriptMemberExpression    curMemberExpression = new ScriptMemberExpression();
                statement.Expression = curMemberExpression;
                for (int curHierarchEntry = callHierarchy.Count - 2; curHierarchEntry >= 0; --curHierarchEntry)
                {
                    curMemberExpression.Member = new ScriptVariableGlobal(callHierarchy[curHierarchEntry]);
                    if (curHierarchEntry > 1)
                    {
                        ScriptMemberExpression nextMemberExpression = new ScriptMemberExpression();
                        curMemberExpression.Target = nextMemberExpression;
                        curMemberExpression        = nextMemberExpression;
                    }
                    else
                    {
                        curMemberExpression.Target = new ScriptVariableGlobal(callHierarchy[0]);
                        break;
                    }
                }

                parentObjectExpression = statement;
            }

            return(parentObjectExpression);
        }
Esempio n. 3
0
        private ScriptExpressionStatement ParseExpressionStatement()
        {
            ScriptExpressionStatement expressionStatement = Open <ScriptExpressionStatement>();
            bool hasAnonymous;

            expressionStatement.Expression = TransformKeyword(ExpectAndParseExpressionAndAnonymous(expressionStatement, out hasAnonymous));

            // In case of an anonymous, there was already an ExpectEndOfStatement issued for the function
            // so we don't have to verify this here again
            if (!hasAnonymous)
            {
                ExpectEndOfStatement(expressionStatement);
            }

            return(Close(expressionStatement));
        }
        /// <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);
        }
        private ScriptStatement ParseLiquidIncDecStatement(bool isDec)
        {
            ScriptExpressionStatement incdecStatement = Open <ScriptExpressionStatement>();

            NextToken(); // skip increment/decrement keyword

            ScriptBinaryExpression binaryExpression = Open <ScriptBinaryExpression>();

            binaryExpression.Left  = ExpectAndParseVariable(incdecStatement);
            binaryExpression.Right = new ScriptLiteral()
            {
                Span = binaryExpression.Span, Value = 1
            };
            binaryExpression.Operator = isDec ? ScriptBinaryOperator.Substract : ScriptBinaryOperator.Add;
            ExpectEndOfStatement(incdecStatement);

            incdecStatement.Expression = binaryExpression;

            Close(binaryExpression);
            return(Close(incdecStatement));
        }
        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 void ParseLiquidStatement(string identifier, ScriptStatement parent, ref ScriptStatement statement, ref bool hasEnd, ref bool nextStatement)
        {
            Token startToken = Current;

            if (!_isLiquidTagSection)
            {
                statement = ParseLiquidExpressionStatement(parent);
                return;
            }

            if (identifier != "when" && identifier != "case" && !identifier.StartsWith("end") && parent is ScriptCaseStatement)
            {
                // 205-case-when-statement-error1.txt
                LogError(startToken, string.Format(RS.UnexpectedTokenInCase, GetAsText(startToken)));
            }

            ScriptStatement startStatement = null;
            string          pendingStart   = null;

            switch (identifier)
            {
            case "endif":
                startStatement = FindFirstStatementExpectingEnd() as ScriptIfStatement;
                pendingStart   = "`if`/`else`";
                // Handle after the switch
                break;

            case "endifchanged":
                startStatement = FindFirstStatementExpectingEnd() as ScriptIfStatement;
                pendingStart   = "`ifchanged`";
                // Handle after the switch
                break;

            case "endunless":
                startStatement = FindFirstStatementExpectingEnd() as ScriptIfStatement;
                pendingStart   = "`unless`";
                break;

            case "endfor":
                startStatement = FindFirstStatementExpectingEnd() as ScriptForStatement;
                pendingStart   = "`for`";
                break;

            case "endcase":
                startStatement = FindFirstStatementExpectingEnd() as ScriptCaseStatement;
                pendingStart   = "`case`";
                break;

            case "endcapture":
                startStatement = FindFirstStatementExpectingEnd() as ScriptCaptureStatement;
                pendingStart   = "`capture`";
                break;

            case "endtablerow":
                startStatement = FindFirstStatementExpectingEnd() as ScriptTableRowStatement;
                pendingStart   = "`tablerow`";
                break;

            case "case":
                statement = ParseCaseStatement();
                break;

            case "when":
                ScriptWhenStatement whenStatement = ParseWhenStatement();
                var whenParent = parent as ScriptConditionStatement;
                if (parent is ScriptWhenStatement)
                {
                    ((ScriptWhenStatement)whenParent).Next = whenStatement;
                }
                else if (parent is ScriptCaseStatement)
                {
                    statement = whenStatement;
                }
                else
                {
                    nextStatement = false;

                    // unit test: TODO
                    LogError(startToken, RS.WhenConditionSyntaxError);
                }
                hasEnd = true;
                break;

            case "if":
                statement = ParseIfStatement(false, false);
                break;

            case "ifchanged":
                statement = ParseLiquidIfChanged();
                break;

            case "unless":
                CheckNotInCase(parent, startToken);
                statement = ParseIfStatement(true, false);
                break;

            case "else":
            case "elsif":
                ScriptConditionStatement nextCondition = ParseElseStatement(identifier == "elsif");
                var parentCondition = parent as ScriptConditionStatement;
                if (parent is ScriptIfStatement || parent is ScriptWhenStatement)
                {
                    if (parent is ScriptIfStatement)
                    {
                        ((ScriptIfStatement)parentCondition).Else = nextCondition;
                    }
                    else
                    {
                        if (identifier == "elsif")
                        {
                            LogError(startToken, RS.ElseIfConditionSyntaxError);
                        }

                        ((ScriptWhenStatement)parentCondition).Next = nextCondition;
                    }
                }
                else
                {
                    nextStatement = false;

                    // unit test: 201-if-else-error3.txt
                    LogError(startToken, RS.ElseConditionSyntaxError);
                }
                hasEnd = true;
                break;

            case "for":
                statement = ParseForStatement <ScriptForStatement>();
                break;

            case "tablerow":
                statement = ParseForStatement <ScriptTableRowStatement>();
                break;

            case "cycle":
                statement = ParseLiquidCycleStatement();
                break;

            case "break":
                statement = Open <ScriptBreakStatement>();
                NextToken();
                ExpectEndOfStatement(statement);
                Close(statement);
                break;

            case "continue":
                statement = Open <ScriptContinueStatement>();
                NextToken();
                ExpectEndOfStatement(statement);
                Close(statement);
                break;

            case "assign":
                if (_isKeepTrivia)
                {
                    _trivias.Clear();
                }

                NextToken();     // skip assign

                Token token = _token;
                // Try to parse an expression
                ScriptExpressionStatement expressionStatement = ParseExpressionStatement();
                // If we don't have an assign expression, this is not a valid assign
                if (!(expressionStatement.Expression is ScriptAssignExpression))
                {
                    LogError(token, RS.ExpectAssignmentExpression);
                }

                statement = expressionStatement;
                break;

            case "capture":
                statement = ParseCaptureStatement();
                break;

            case "increment":
                statement = ParseLiquidIncDecStatement(false);
                break;

            case "decrement":
                statement = ParseLiquidIncDecStatement(true);
                break;

            case "include":
                statement = ParseLiquidIncludeStatement();
                break;

            default:
                statement = ParseLiquidExpressionStatement(parent);
                break;
            }

            if (pendingStart != null)
            {
                if (_isKeepTrivia)
                {
                    _trivias.Add(new ScriptTrivia(CurrentSpan, ScriptTriviaType.End));
                }

                NextToken();

                hasEnd        = true;
                nextStatement = false;

                if (startStatement == null)
                {
                    LogError(startToken, string.Format(RS.IdentifierPendingStartMissing, pendingStart, identifier));
                }

                ExpectEndOfStatement(startStatement);
                if (_isKeepTrivia)
                {
                    FlushTrivias(startStatement, false);
                }
            }
        }
        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));
        }