private List <DocumentToken> ReadReplacementBody(TokenReader <DocumentToken> reader, DocumentToken replacementKeyToken)
        {
            List <DocumentToken> replacementContents = new List <DocumentToken>();

            int numOpenReplacements = 1;

            while (reader.CanAdvance())
            {
                if (reader.Peek().TokenType == DocumentTokenType.BeginReplacementSegment)
                {
                    numOpenReplacements++;
                }
                else if (reader.Peek().TokenType == DocumentTokenType.QuickTerminateReplacementSegment)
                {
                    numOpenReplacements--;

                    if (numOpenReplacements == 0)
                    {
                        throw Unexpected(reader.Peek());
                    }
                }
                else if (reader.Peek().TokenType == DocumentTokenType.BeginTerminateReplacementSegment)
                {
                    numOpenReplacements--;

                    if (numOpenReplacements == 0)
                    {
                        AdvanceAndExpectConstantType(reader, DocumentTokenType.BeginTerminateReplacementSegment);
                        AdvanceAndExpect(reader, DocumentTokenType.ReplacementKey, replacementKeyToken.Value, skipWhitespace: true);
                        AdvanceAndExpectConstantType(reader, DocumentTokenType.EndReplacementSegment);
                        break;
                    }
                }

                replacementContents.Add(reader.Advance());
            }

            if (numOpenReplacements != 0)
            {
                throw Unexpected("end of '" + replacementKeyToken.Value + "' replacement");
            }

            return(replacementContents);
        }
        private void ParseReplacement(TokenReader <DocumentToken> reader, List <IDocumentExpression> ret)
        {
            var openToken           = AdvanceAndExpectConstantType(reader, DocumentTokenType.BeginReplacementSegment);
            var replacementKeyToken = AdvanceAndExpect(reader, DocumentTokenType.ReplacementKey, "replacement key", skipWhitespace: true);

            List <DocumentToken> parameters = new List <DocumentToken>();
            List <DocumentToken> body       = new List <DocumentToken>();

            while (reader.CanAdvance(skipWhitespace: true) && reader.Peek(skipWhitespace: true).TokenType == DocumentTokenType.ReplacementParameter)
            {
                var paramToken = reader.Advance(skipWhitespace: true);
                parameters.Add(paramToken);
            }


            DocumentToken closeReplacementToken;

            if (reader.TryAdvance(out closeReplacementToken, skipWhitespace: true) == false)
            {
                throw Unexpected(string.Format("'{0}' or '{1}'", DocumentToken.GetTokenTypeValue(DocumentTokenType.EndReplacementSegment), DocumentToken.GetTokenTypeValue(DocumentTokenType.QuickTerminateReplacementSegment)));
            }

            if (closeReplacementToken.TokenType == DocumentTokenType.EndReplacementSegment)
            {
                body.AddRange(ReadReplacementBody(reader, replacementKeyToken));
            }
            else if (closeReplacementToken.TokenType == DocumentTokenType.QuickTerminateReplacementSegment)
            {
                // do nothing, there is no body when the quick termination replacement segment is used
            }
            else
            {
                throw Unexpected(string.Format("'{0}' or '{1}'", DocumentToken.GetTokenTypeValue(DocumentTokenType.EndReplacementSegment), DocumentToken.GetTokenTypeValue(DocumentTokenType.QuickTerminateReplacementSegment)), closeReplacementToken);
            }

            IDocumentExpressionProvider provider;

            if (this.expressionProviders.TryGetValue(replacementKeyToken.Value, out provider) == false)
            {
                provider = new EvalExpressionProvider();
            }

            var context = new DocumentExpressionContext
            {
                OpenToken           = openToken,
                CloseToken          = closeReplacementToken,
                Parameters          = parameters.AsReadOnly(),
                Body                = body.AsReadOnly(),
                ReplacementKeyToken = replacementKeyToken,
            };

            var expression = provider.CreateExpression(context);

            ret.Add(expression);
        }
        /// <summary>
        /// Parses the given tokens into document expressions that can then be evaluated against a data context.
        /// </summary>
        /// <param name="tokens">The tokens to parse</param>
        /// <returns>a list of document expressions</returns>
        public List <IDocumentExpression> Parse(IEnumerable <DocumentToken> tokens)
        {
            List <IDocumentExpression> ret = new List <IDocumentExpression>();

            TokenReader <DocumentToken> reader = new TokenReader <DocumentToken>(tokens);

            while (reader.CanAdvance())
            {
                if (reader.Peek().TokenType == DocumentTokenType.BeginReplacementSegment)
                {
                    ParseReplacement(reader, ret);
                }
                else
                {
                    var plain = new PlainTextDocumentExpression(reader.Advance());
                    ret.Add(plain);
                }
            }

            return(ret);
        }