Example #1
0
        // {{#if ...}}
        //    ^
        public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment)
        {
            literal = literal.ParseVarName(out var blockNameSpan);

            PageBlockFragment statement;
            var blockName  = blockNameSpan.ToString();
            var endBlock   = "{{/" + blockName + "}}";
            var endExprPos = literal.IndexOf("}}");

            if (endExprPos == -1)
            {
                throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'");
            }

            var argument = literal.Slice(0, endExprPos).Trim();

            literal = literal.Advance(endExprPos + 2);

            var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang)
                ? lang
                : ScriptTemplate.Language;

            if (language.Name == ScriptVerbatim.Language.Name)
            {
                var endBlockPos = literal.IndexOf(endBlock);
                if (endBlockPos == -1)
                {
                    throw new SyntaxErrorException($"Unterminated end block '{endBlock}'");
                }

                var body = literal.Slice(0, endBlockPos);
                literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine();

                blockFragment = language.ParseVerbatimBlock(blockName, argument, body);
                return(literal);
            }

            literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText);
            var bodyFragments = language.Parse(context, bodyText);

            var elseBlocks = new List <PageElseBlock>();

            while (literal.StartsWith("{{else"))
            {
                literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody);

                var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody));
                elseBlocks.Add(elseBlock);
            }

            literal = literal.Advance(2 + 1 + blockName.Length + 2);

            //remove new line after partial block end tag
            literal = literal.TrimFirstNewLine();

            blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks);

            return(literal);
        }
        // {{#name}}  {{else if a=b}}  {{else}}  {{/name}}
        //          ^
        // returns    ^                         ^
        static ReadOnlyMemory <char> ParseStatementBody(this ReadOnlyMemory <char> literal, ReadOnlyMemory <char> blockName, out List <PageFragment> body)
        {
            var inStatements = 0;
            var pos          = 0;

            while (true)
            {
                pos = literal.IndexOf("{{", pos);
                if (pos == -1)
                {
                    throw new SyntaxErrorException($"End block for '{blockName}' not found.");
                }

                var c = literal.SafeGetChar(pos + 2);

                if (c == '#')
                {
                    inStatements++;
                    pos = literal.IndexOf("}}", pos) + 2; //end of expression
                    continue;
                }

                if (c == '/')
                {
                    if (inStatements == 0)
                    {
                        literal.Slice(pos + 2 + 1).ParseVarName(out var name);
                        if (name.EqualsOrdinal(blockName))
                        {
                            body = ParseTemplatePage(literal.Slice(0, pos).TrimFirstNewLine());
                            return(literal.Slice(pos));
                        }
                    }

                    inStatements--;
                }
                else if (literal.Slice(pos + 2).StartsWith("else"))
                {
                    if (inStatements == 0)
                    {
                        body = ParseTemplatePage(literal.Slice(0, pos).TrimFirstNewLine());
                        return(literal.Slice(pos));
                    }
                }

                pos += 2;
            }
        }
Example #3
0
        public static ReadOnlyMemory <char> RightPart(this ReadOnlyMemory <char> strVal, string needle)
        {
            if (strVal.IsEmpty)
            {
                return(strVal);
            }
            var pos = strVal.IndexOf(needle);

            return(pos == -1
                ? strVal
                : strVal.Slice(pos + needle.Length));
        }
Example #4
0
        public static ReadOnlyMemory <char> LeftPart(this ReadOnlyMemory <char> strVal, char needle)
        {
            if (strVal.IsEmpty)
            {
                return(strVal);
            }
            var pos = strVal.IndexOf(needle);

            return(pos == -1
                ? strVal
                : strVal.Slice(0, pos));
        }
        // #if ...
        //  ^
        public static ReadOnlyMemory <char> ParseCodeScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context,
                                                                 out PageBlockFragment blockFragment)
        {
            literal = literal.ParseVarName(out var blockNameSpan);
            var endArgumentPos = literal.IndexOf('\n');
            var argument       = literal.Slice(0, endArgumentPos).Trim();

            literal = literal.Slice(endArgumentPos + 1);

            var blockName = blockNameSpan.ToString();

            var language = context.ParseAsLanguage.TryGetValue(blockName, out var lang)
                ? lang
                : ScriptCode.Language;

            if (language.Name == ScriptVerbatim.Language.Name)
            {
                literal = literal.ParseCodeBody(blockNameSpan, out var body);
                body    = body.ChopNewLine();

                blockFragment = language.ParseVerbatimBlock(blockName, argument, body);
                return(literal);
            }

            literal = literal.ParseCodeBody(blockNameSpan, out var bodyText);
            var bodyFragments = language.Parse(context, bodyText);

            var elseBlocks = new List <PageElseBlock>();

            literal = literal.AdvancePastWhitespace();
            while (literal.StartsWith("else"))
            {
                literal = literal.ParseCodeElseBlock(blockNameSpan, out var elseArgument, out var elseBody);

                var elseBlock = new PageElseBlock(elseArgument, language.Parse(context, elseBody));
                elseBlocks.Add(elseBlock);

                literal = literal.AdvancePastWhitespace();
            }

            blockFragment = new PageBlockFragment(blockName, argument, bodyFragments, elseBlocks);

            return(literal);
        }
        //   {{else if a=b}}  {{else}}  {{/name}}
        //  ^
        // returns           ^         ^
        static ReadOnlyMemory <char> ParseElseStatement(this ReadOnlyMemory <char> literal, string blockName, out PageElseBlock statement)
        {
            var inStatements = 0;
            var pos          = 0;

            statement = null;
            var statementPos = -1;
            var elseExpr     = default(ReadOnlyMemory <char>);

            while (true)
            {
                pos = literal.IndexOf("{{", pos);
                if (pos == -1)
                {
                    throw new SyntaxErrorException($"End block for 'else' not found.");
                }

                var c = literal.SafeGetChar(pos + 2);
                if (c == '#')
                {
                    inStatements++;
                    pos = literal.IndexOf("}}", pos) + 2; //end of expression
                }
                else if (c == '/')
                {
                    if (inStatements == 0)
                    {
                        literal.Slice(pos + 2 + 1).ParseVarName(out var name);
                        if (name.EqualsOrdinal(blockName))
                        {
                            var body = ParseTemplatePage(literal.Slice(statementPos, pos - statementPos).TrimFirstNewLine());
                            statement = new PageElseBlock(elseExpr, body);
                            return(literal.Slice(pos));
                        }
                    }

                    inStatements--;
                }
                else if (literal.Slice(pos + 2).StartsWith("else"))
                {
                    if (inStatements == 0)
                    {
                        if (statementPos >= 0)
                        {
                            var bodyText = literal.Slice(statementPos, pos - statementPos).TrimFirstNewLine();
                            var body     = ParseTemplatePage(bodyText);
                            statement = new PageElseBlock(elseExpr, body);
                            return(literal.Slice(pos));
                        }

                        var endExprPos = literal.IndexOf("}}", pos);
                        if (endExprPos == -1)
                        {
                            throw new SyntaxErrorException($"End expression for 'else' not found.");
                        }

                        var exprStartPos = pos + 2 + 4; //= {{else...

                        elseExpr     = literal.Slice(exprStartPos, endExprPos - exprStartPos).Trim();
                        statementPos = endExprPos + 2;
                    }
                }

                pos += 2;
            }
        }
        public static List <PageFragment> ParseTemplatePage(ReadOnlyMemory <char> text)
        {
            var to = new List <PageFragment>();

            if (text.IsNullOrWhiteSpace())
            {
                return(to);
            }

            int pos;
            var lastPos = 0;

            while ((pos = text.IndexOf("{{", lastPos)) != -1)
            {
                var block = text.Slice(lastPos, pos - lastPos);
                if (!block.IsNullOrEmpty())
                {
                    to.Add(new PageStringFragment(block));
                }

                var varStartPos = pos + 2;

                var firstChar = text.Span[varStartPos];
                if (firstChar == '*') //comment
                {
                    lastPos = text.IndexOf("*}}", varStartPos) + 3;
                }
                else if (firstChar == '#') //block statement
                {
                    var literal = text.Slice(varStartPos + 1);
                    literal = literal.ParseVarName(out var blockNameSpan);

                    var blockName  = blockNameSpan.ToString();
                    var endExprPos = literal.IndexOf("}}");
                    if (endExprPos == -1)
                    {
                        throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'");
                    }

                    var blockExpr = literal.Slice(0, endExprPos).Trim();
                    literal = literal.Advance(endExprPos + 2);

                    if (!ScriptConfig.DontEvaluateBlocksNamed.Contains(blockName))
                    {
                        literal = literal.ParseStatementBody(blockNameSpan, out var body);
                        var elseStatements = new List <PageElseBlock>();

                        while (literal.StartsWith("{{else"))
                        {
                            literal = literal.ParseElseStatement(blockName, out var elseStatement);
                            elseStatements.Add(elseStatement);
                        }

                        literal = literal.Advance(2 + 1 + blockName.Length + 2);

                        //remove new line after partial block end tag
                        literal = literal.TrimFirstNewLine();

                        var length       = text.Length - pos - literal.Length;
                        var originalText = text.Slice(pos, length);
                        lastPos = pos + length;

                        var statement = new PageBlockFragment(originalText, blockName, blockExpr, body, elseStatements);
                        to.Add(statement);
                    }
                    else
                    {
                        var endBlock    = "{{/" + blockName + "}}";
                        var endBlockPos = literal.IndexOf(endBlock);
                        if (endBlockPos == -1)
                        {
                            throw new SyntaxErrorException($"Unterminated end block '{endBlock}'");
                        }

                        var endBlockBody = literal.Slice(0, endBlockPos);
                        literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine();
                        var body = new List <PageFragment> {
                            new PageStringFragment(endBlockBody)
                        };

                        var length       = text.Length - pos - literal.Length;
                        var originalText = text.Slice(pos, length);
                        lastPos = pos + length;

                        var statement = new PageBlockFragment(originalText, blockName, blockExpr, body);
                        to.Add(statement);
                    }
                }
                else
                {
                    var literal = text.Slice(varStartPos).Span;
                    literal = literal.ParseJsExpression(out var expr, filterExpression: true);

                    var filters = new List <JsCallExpression>();

                    if (!literal.StartsWith("}}"))
                    {
                        literal = literal.AdvancePastWhitespace();
                        if (literal.FirstCharEquals(FilterSep))
                        {
                            literal = literal.Advance(1);

                            while (true)
                            {
                                literal = literal.ParseJsCallExpression(out var filter, filterExpression: true);

                                filters.Add(filter);

                                literal = literal.AdvancePastWhitespace();

                                if (literal.IsNullOrEmpty())
                                {
                                    throw new SyntaxErrorException("Unterminated filter expression");
                                }

                                if (literal.StartsWith("}}"))
                                {
                                    literal = literal.Advance(2);
                                    break;
                                }

                                if (!literal.FirstCharEquals(FilterSep))
                                {
                                    throw new SyntaxErrorException(
                                              $"Expected filter separator '|' but was {literal.DebugFirstChar()}");
                                }

                                literal = literal.Advance(1);
                            }
                        }
                        else
                        {
                            if (!literal.IsNullOrEmpty())
                            {
                                literal = literal.Advance(1);
                            }
                        }
                    }
                    else
                    {
                        literal = literal.Advance(2);
                    }

                    var length       = text.Length - pos - literal.Length;
                    var originalText = text.Slice(pos, length);
                    lastPos = pos + length;

                    var varFragment = new PageVariableFragment(originalText, expr, filters);
                    to.Add(varFragment);

                    var newLineLen = literal.StartsWith("\n")
                        ? 1
                        : literal.StartsWith("\r\n")
                            ? 2
                            : 0;

                    if (newLineLen > 0)
                    {
                        var lastExpr   = varFragment.FilterExpressions?.LastOrDefault();
                        var filterName = lastExpr?.Name ??
                                         varFragment?.InitialExpression?.Name ?? varFragment.Binding;
                        if (filterName != null && ScriptConfig.RemoveNewLineAfterFiltersNamed.Contains(filterName))
                        {
                            lastPos += newLineLen;
                        }
                    }
                }
            }

            if (lastPos != text.Length)
            {
                var lastBlock = lastPos == 0 ? text : text.Slice(lastPos);
                to.Add(new PageStringFragment(lastBlock));
            }

            return(to);
        }
Example #8
0
        public static List <PageFragment> ParseTemplate(this ScriptContext context, ReadOnlyMemory <char> text)
        {
            var to = new List <PageFragment>();

            if (text.IsNullOrWhiteSpace())
            {
                return(to);
            }

            int pos;
            var lastPos = 0;

            int nextPos()
            {
                var c1 = text.IndexOf("{{", lastPos);
                var c2 = text.IndexOf("{|", lastPos);

                if (c2 == -1)
                {
                    return(c1);
                }

                return(c1 == -1 ? c2 : c1 < c2 ? c1 : c2);
            }

            while ((pos = nextPos()) != -1)
            {
                var block = text.Slice(lastPos, pos - lastPos);
                if (!block.IsNullOrEmpty())
                {
                    to.Add(new PageStringFragment(block));
                }

                var varStartPos = pos + 2;

                if (varStartPos >= text.Span.Length)
                {
                    throw new SyntaxErrorException($"Unterminated '{{{{' expression, near '{text.Slice(lastPos).DebugLiteral()}'");
                }

                if (text.Span.SafeCharEquals(varStartPos - 1, '|')) // lang expression syntax {|lang ... |} https://flow.org/en/docs/types/objects/#toc-exact-object-types
                {
                    var literal = text.Slice(varStartPos);

                    ScriptLanguage lang = null;
                    if (literal.SafeGetChar(0).IsValidVarNameChar())
                    {
                        literal = literal.ParseVarName(out var langSpan);

                        lang = context.GetScriptLanguage(langSpan.ToString());
                        if (lang != null)
                        {
                            var endPos = literal.IndexOf("|}");
                            if (endPos == -1)
                            {
                                throw new SyntaxErrorException($"Unterminated '|}}' expression, near '{text.Slice(varStartPos).DebugLiteral()}'");
                            }

                            var exprStr          = literal.Slice(0, endPos);
                            var langExprFragment = lang.Parse(context, exprStr);
                            to.AddRange(langExprFragment);
                        }
                    }
                    if (lang == null)
                    {
                        var nextLastPos = text.IndexOf("|}", varStartPos) + 2;
                        block = text.Slice(pos, nextLastPos - pos);
                        if (!block.IsNullOrEmpty())
                        {
                            to.Add(new PageStringFragment(block));
                        }
                    }

                    lastPos = text.IndexOf("|}", varStartPos) + 2;
                    continue;
                }

                var firstChar = text.Span[varStartPos];
                if (firstChar == '*') //comment
                {
                    lastPos = text.IndexOf("*}}", varStartPos) + 3;
                    if (text.Span.SafeCharEquals(lastPos, '\r'))
                    {
                        lastPos++;
                    }
                    if (text.Span.SafeCharEquals(lastPos, '\n'))
                    {
                        lastPos++;
                    }
                }
                else if (firstChar == '#') //block statement
                {
                    var literal = text.Slice(varStartPos + 1);

                    literal = literal.ParseTemplateScriptBlock(context, out var blockFragment);

                    var length = text.Length - pos - literal.Length;
                    blockFragment.OriginalText = text.Slice(pos, length);
                    lastPos = pos + length;

                    to.Add(blockFragment);
                }
                else
                {
                    var literal = text.Slice(varStartPos).Span;
                    literal = literal.ParseJsExpression(out var expr, filterExpression: true);

                    var filters = new List <JsCallExpression>();

                    if (!literal.StartsWith("}}"))
                    {
                        literal = literal.AdvancePastWhitespace();
                        if (literal.FirstCharEquals(FilterSep))
                        {
                            literal = literal.AdvancePastPipeOperator();

                            while (true)
                            {
                                literal = literal.ParseJsCallExpression(out var filter, filterExpression: true);

                                filters.Add(filter);

                                literal = literal.AdvancePastWhitespace();

                                if (literal.IsNullOrEmpty())
                                {
                                    throw new SyntaxErrorException("Unterminated filter expression");
                                }

                                if (literal.StartsWith("}}"))
                                {
                                    literal = literal.Advance(2);
                                    break;
                                }

                                if (!literal.FirstCharEquals(FilterSep))
                                {
                                    throw new SyntaxErrorException(
                                              $"Expected pipeline operator '|>' but was {literal.DebugFirstChar()}");
                                }

                                literal = literal.AdvancePastPipeOperator();
                            }
                        }
                        else if (!literal.AdvancePastWhitespace().IsNullOrEmpty())
                        {
                            throw new SyntaxErrorException($"Unexpected syntax '{literal.ToString()}', Expected pipeline operator '|>'");
                        }
                    }
                    else
                    {
                        literal = literal.Advance(2);
                    }

                    var length       = text.Length - pos - literal.Length;
                    var originalText = text.Slice(pos, length);
                    lastPos = pos + length;

                    var varFragment = new PageVariableFragment(originalText, expr, filters);
                    to.Add(varFragment);

                    var newLineLen = literal.StartsWith("\n")
                        ? 1
                        : literal.StartsWith("\r\n")
                            ? 2
                            : 0;

                    if (newLineLen > 0)
                    {
                        var lastExpr   = varFragment.FilterExpressions?.LastOrDefault();
                        var filterName = lastExpr?.Name ??
                                         varFragment?.InitialExpression?.Name ?? varFragment.Binding;
                        if ((filterName != null && context.RemoveNewLineAfterFiltersNamed.Contains(filterName)) ||
                            expr is JsVariableDeclaration)
                        {
                            lastPos += newLineLen;
                        }
                    }
                }
            }

            if (lastPos != text.Length)
            {
                var lastBlock = lastPos == 0 ? text : text.Slice(lastPos);
                to.Add(new PageStringFragment(lastBlock));
            }

            return(to);
        }
Example #9
0
        internal static JsStatement[] ParseCodeStatements(this ScriptContext context, ReadOnlyMemory <char> code)
        {
            var to = new List <JsStatement>();

            int startExpressionPos = -1;

            var cursorPos = 0;

            while (code.TryReadLine(out var line, ref cursorPos))
            {
                var lineLength = line.Length;
                line = line.TrimStart();
                var leftIndent = lineLength - line.Length;
                line = line.TrimEnd();
                var rightIndent = lineLength - leftIndent - line.Length;

                if (line.IsEmpty)
                {
                    continue;
                }

                var firstChar = line.Span[0];

                // single-line comment
                if (firstChar == '*')
                {
                    if (line.EndsWith("*"))
                    {
                        continue;
                    }
                }
                // multi-line comment
                if (line.StartsWith("{{*"))
                {
                    var endPos = code.IndexOf("*}}", cursorPos - lineLength);
                    if (endPos == -1)
                    {
                        throw new SyntaxErrorException($"Unterminated multi-line comment, near {line.DebugLiteral()}");
                    }

                    cursorPos = endPos + 3; // "*}}".Length
                    continue;
                }

                // template block statement
                if (firstChar == '{' && line.Span.SafeCharEquals(1, '{') && line.Span.SafeCharEquals(2, '#'))
                {
                    var fromLineStart = code.ToLineStart(cursorPos, lineLength).AdvancePastWhitespace();
                    var literal       = fromLineStart.Slice(3);

                    literal = literal.ParseTemplateScriptBlock(context, out var blockFragment);
                    blockFragment.OriginalText = fromLineStart.Slice(0, fromLineStart.Length - literal.Length);
                    to.Add(new JsPageBlockFragmentStatement(blockFragment));

                    cursorPos = code.Length - literal.Length;
                    continue;
                }

                // code block statement
                if (firstChar == '#')
                {
                    var fromLineStart = code.ToLineStart(cursorPos, lineLength).AdvancePastWhitespace();
                    var literal       = fromLineStart.Slice(1);

                    literal = literal.ParseCodeScriptBlock(context, out var blockFragment);
                    to.Add(new JsPageBlockFragmentStatement(blockFragment));

                    blockFragment.OriginalText = fromLineStart.Slice(0, fromLineStart.Length - literal.Length);

                    cursorPos = code.Length - literal.Length;
                    continue;
                }

                const int delim = 2; // '}}'.length
                // multi-line expression
                if (startExpressionPos >= 0)
                {
                    // multi-line end
                    if (line.EndsWith("}}"))
                    {
                        if (code.Span.SafeCharEquals(startExpressionPos, '*'))
                        {
                            if (!line.EndsWith("*}}")) // not a closing block comment, continue
                            {
                                continue;
                            }

                            // ignore multi-line comment
                        }
                        else
                        {
                            var CRLF      = code.Span.SafeCharEquals(cursorPos - 2, '\r') ? 2 : 1;
                            var exprStr   = code.Slice(startExpressionPos, cursorPos - startExpressionPos - rightIndent - delim - CRLF).Trim();
                            var afterExpr = exprStr.Span.ParseExpression(out var expr, out var filters);

                            to.AddExpression(exprStr, expr, filters);
                        }

                        startExpressionPos = -1;
                    }
                    continue;
                }

                if (firstChar == '{' && line.Span.SafeCharEquals(1, '{'))
                {
                    // single-line {{ expr }}
                    if (line.EndsWith("}}"))
                    {
                        var exprStr = code.Slice(cursorPos - lineLength + leftIndent + delim);
                        exprStr = exprStr.Slice(0, exprStr.IndexOf("}}")).Trim();
                        var afterExpr = exprStr.Span.ParseExpression(out var expr, out var filters);

                        to.AddExpression(exprStr, expr, filters);
                        continue;
                    }

                    // multi-line start
                    var CRLF = code.Span.SafeCharEquals(cursorPos - 2, '\r') ? 2 : 1;
                    startExpressionPos = cursorPos - lineLength - CRLF + leftIndent + delim;
                    continue;
                }
                else
                {
                    // treat line as an expression statement
                    var afterExpr = line.Span.ParseExpression(out var expr, out var filters);
                    afterExpr = afterExpr.AdvancePastWhitespace();

                    var isStatementDelim = afterExpr.FirstCharEquals(ScriptTemplateUtils.StatementsSep);

                    if (!afterExpr.IsEmpty && !isStatementDelim)
                    {
                        throw new SyntaxErrorException($"Unexpected syntax after expression: {afterExpr.ToString()}, near {line.DebugLiteral()}");
                    }

                    var exprSrc = line.SafeSlice(0, line.Length - afterExpr.Length);
                    to.AddExpression(exprSrc, expr, filters);

                    if (isStatementDelim)
                    {
                        cursorPos = cursorPos - afterExpr.Length + 1;
                    }
                }
            }

            return(to.ToArray());
        }
        // {{#if ...}}
        //    ^
        public static ReadOnlyMemory <char> ParseTemplateScriptBlock(this ReadOnlyMemory <char> literal, ScriptContext context, out PageBlockFragment blockFragment)
        {
            literal = literal.ParseVarName(out var blockNameSpan);

            PageBlockFragment statement;
            var blockName  = blockNameSpan.ToString();
            var endBlock   = "{{/" + blockName + "}}";
            var endExprPos = literal.IndexOf("}}");

            if (endExprPos == -1)
            {
                throw new SyntaxErrorException($"Unterminated '{blockName}' block expression, near '{literal.DebugLiteral()}'");
            }

            var blockExpr = literal.Slice(0, endExprPos).Trim();

            literal = literal.Advance(endExprPos + 2);

            if (context.ParseAsVerbatimBlock.Contains(blockName))
            {
                var endBlockPos = literal.IndexOf(endBlock);
                if (endBlockPos == -1)
                {
                    throw new SyntaxErrorException($"Unterminated end block '{endBlock}'");
                }

                var blockBody = literal.Slice(0, endBlockPos);
                literal = literal.Advance(endBlockPos + endBlock.Length).TrimFirstNewLine();

                var body = new List <PageFragment> {
                    new PageStringFragment(blockBody)
                };
                blockFragment = new PageBlockFragment(blockName, blockExpr, body);

                return(literal);
            }
            else
            {
                var parseAsCode = context.ParseAsCodeBlock.Contains(blockName);

                literal = literal.ParseTemplateBody(blockNameSpan, out var bodyText);
                List <PageFragment> bodyFragments  = null;
                JsBlockStatement    bodyStatements = null;

                if (!parseAsCode)
                {
                    bodyFragments = context.ParseTemplate(bodyText);
                }
                else
                {
                    bodyStatements = context.ParseCode(bodyText);
                }

                var elseBlocks = new List <PageElseBlock>();
                while (literal.StartsWith("{{else"))
                {
                    literal = literal.ParseTemplateElseBlock(blockName, out var elseArgument, out var elseBody);

                    var elseBlock = !parseAsCode
                        ? new PageElseBlock(elseArgument, context.ParseTemplate(elseBody))
                        : new PageElseBlock(elseArgument, context.ParseCode(elseBody));
                    elseBlocks.Add(elseBlock);
                }

                literal = literal.Advance(2 + 1 + blockName.Length + 2);

                //remove new line after partial block end tag
                literal = literal.TrimFirstNewLine();

                blockFragment = !parseAsCode
                    ? new PageBlockFragment(blockName, blockExpr, bodyFragments, elseBlocks)
                    : new PageBlockFragment(blockName, blockExpr, bodyStatements, elseBlocks);

                return(literal);
            }
        }
        public static List <PageFragment> ParseTemplate(this ScriptContext context, ReadOnlyMemory <char> text)
        {
            var to = new List <PageFragment>();

            if (text.IsNullOrWhiteSpace())
            {
                return(to);
            }

            int pos;
            var lastPos = 0;

            while ((pos = text.IndexOf("{{", lastPos)) != -1)
            {
                var block = text.Slice(lastPos, pos - lastPos);
                if (!block.IsNullOrEmpty())
                {
                    to.Add(new PageStringFragment(block));
                }

                var varStartPos = pos + 2;

                if (varStartPos >= text.Span.Length)
                {
                    throw new SyntaxErrorException($"Unterminated '{{{{' expression, near '{text.Slice(lastPos).DebugLiteral()}'");
                }

                var firstChar = text.Span[varStartPos];
                if (firstChar == '*') //comment
                {
                    lastPos = text.IndexOf("*}}", varStartPos) + 3;
                    if (text.Span.SafeCharEquals(lastPos, '\r'))
                    {
                        lastPos++;
                    }
                    if (text.Span.SafeCharEquals(lastPos, '\n'))
                    {
                        lastPos++;
                    }
                }
                else if (firstChar == '#') //block statement
                {
                    var literal = text.Slice(varStartPos + 1);

                    literal = literal.ParseTemplateScriptBlock(context, out var blockFragment);

                    var length = text.Length - pos - literal.Length;
                    blockFragment.OriginalText = text.Slice(pos, length);
                    lastPos = pos + length;

                    to.Add(blockFragment);
                }
                else
                {
                    var literal = text.Slice(varStartPos).Span;
                    literal = literal.ParseJsExpression(out var expr, filterExpression: true);

                    var filters = new List <JsCallExpression>();

                    if (!literal.StartsWith("}}"))
                    {
                        literal = literal.AdvancePastWhitespace();
                        if (literal.FirstCharEquals(FilterSep))
                        {
                            literal = literal.Advance(1);

                            while (true)
                            {
                                literal = literal.ParseJsCallExpression(out var filter, filterExpression: true);

                                filters.Add(filter);

                                literal = literal.AdvancePastWhitespace();

                                if (literal.IsNullOrEmpty())
                                {
                                    throw new SyntaxErrorException("Unterminated filter expression");
                                }

                                if (literal.StartsWith("}}"))
                                {
                                    literal = literal.Advance(2);
                                    break;
                                }

                                if (!literal.FirstCharEquals(FilterSep))
                                {
                                    throw new SyntaxErrorException(
                                              $"Expected filter separator '|' but was {literal.DebugFirstChar()}");
                                }

                                literal = literal.Advance(1);
                            }
                        }
                        else
                        {
                            if (!literal.IsNullOrEmpty())
                            {
                                literal = literal.Advance(1);
                            }
                        }
                    }
                    else
                    {
                        literal = literal.Advance(2);
                    }

                    var length       = text.Length - pos - literal.Length;
                    var originalText = text.Slice(pos, length);
                    lastPos = pos + length;

                    var varFragment = new PageVariableFragment(originalText, expr, filters);
                    to.Add(varFragment);

                    var newLineLen = literal.StartsWith("\n")
                        ? 1
                        : literal.StartsWith("\r\n")
                            ? 2
                            : 0;

                    if (newLineLen > 0)
                    {
                        var lastExpr   = varFragment.FilterExpressions?.LastOrDefault();
                        var filterName = lastExpr?.Name ??
                                         varFragment?.InitialExpression?.Name ?? varFragment.Binding;
                        if (filterName != null && context.RemoveNewLineAfterFiltersNamed.Contains(filterName))
                        {
                            lastPos += newLineLen;
                        }
                    }
                }
            }

            if (lastPos != text.Length)
            {
                var lastBlock = lastPos == 0 ? text : text.Slice(lastPos);
                to.Add(new PageStringFragment(lastBlock));
            }

            return(to);
        }