/// <summary> /// Parses the Tokens into a Document. /// </summary> /// <param name="tokens">The tokens.</param> /// <param name="options">The options.</param> /// <returns></returns> internal static IDocumentItem Parse(Queue <TokenPair> tokens, ParserOptions options) { var buildStack = new Stack <FormattingScope>(); //instead of recursive calling the parse function we stack the current document buildStack.Push(new FormattingScope(new MorestachioDocument(), false)); while (tokens.Any()) { var currentToken = tokens.Dequeue(); var currentDocumentItem = buildStack.Peek(); //get the latest document if (currentToken.Type == TokenType.Comment) { //just ignore this part and print nothing } else if (currentToken.Type == TokenType.Content) { currentDocumentItem.Item1.Add(new ContentDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type == TokenType.CollectionOpen) { var nestedDocument = new CollectionDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new FormattingScope(nestedDocument, false)); currentDocumentItem.Item1.Add(nestedDocument); } else if (currentToken.Type == TokenType.ElementOpen) { var nestedDocument = new ExpressionScopeDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new FormattingScope(nestedDocument, false)); currentDocumentItem.Item1.Add(nestedDocument); } else if (currentToken.Type == TokenType.InvertedElementOpen) { var invertedScope = new InvertedExpressionScopeDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new FormattingScope(invertedScope, false)); currentDocumentItem.Item1.Add(invertedScope); } else if (currentToken.Type == TokenType.CollectionClose || currentToken.Type == TokenType.ElementClose) { // remove the last document from the stack and go back to the parents buildStack.Pop(); if (buildStack.Peek().Item2) //is the remaining scope a formatting one. If it is pop it and return to its parent { buildStack.Pop(); } } else if (currentToken.Type == TokenType.PrintFormatted) { currentDocumentItem.Item1.Add(new PrintFormattedItem()); buildStack.Pop(); //Print formatted can only be followed by a Format and if not the parser should have not emited it } else if (currentToken.Type == TokenType.Format) { if (buildStack.Peek().Item2) { buildStack.Pop(); } var formatterItem = new IsolatedContextDocumentItem() { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new FormattingScope(formatterItem, true)); formatterItem.Add(new CallFormatterDocumentItem(currentToken.FormatString, currentToken.Value)); currentDocumentItem.Item1.Add(formatterItem); } else if (currentToken.Type == TokenType.EscapedSingleValue || currentToken.Type == TokenType.UnescapedSingleValue) { currentDocumentItem.Item1.Add(new PathDocumentItem(currentToken.Value, currentToken.Type == TokenType.EscapedSingleValue) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type == TokenType.PartialDeclarationOpen) { // currently same named partials will override each other // to allow recursive calls of partials we first have to declare the partial and then load it as we would parse // -the partial as a whole and then add it to the list would lead to unknown calls of partials inside the partial var nestedDocument = new MorestachioDocument(); buildStack.Push(new FormattingScope(nestedDocument, false)); currentDocumentItem.Item1.Add(new PartialDocumentItem(currentToken.Value, nestedDocument) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type == TokenType.PartialDeclarationClose) { currentDocumentItem.Item1.Add(new RenderPartialDoneDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); buildStack.Pop(); } else if (currentToken.Type == TokenType.RenderPartial) { currentDocumentItem.Item1.Add(new RenderPartialDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); } } if (buildStack.Count != 1) { //var invalidScopedElements = buildStack //throw new MorestachioSyntaxError(new Tokenizer.CharacterLocation(){Character = }, ); throw new InvalidOperationException("There is an Error with the Parser. The Parser still contains unscoped builds: " + buildStack.Select(e => e.Item1.Kind).Aggregate((e, f) => e + ", " + f)); } return(buildStack.Pop().Item1); }
internal MorestachioDocumentInfo([NotNull] ParserOptions options, [CanBeNull] IDocumentItem document, [CanBeNull] IEnumerable <IMorestachioError> errors) { ParserOptions = options ?? throw new ArgumentNullException(nameof(options)); Document = document; Errors = errors ?? Enumerable.Empty <IMorestachioError>(); }
public static IEnumerable <TokenPair> Tokenize(ParserOptions parserOptions) { var templateString = parserOptions.Template ?? ""; var matches = TokenFinder.Matches(templateString); var scopestack = new Stack <Tuple <string, int> >(); var idx = 0; var parseErrors = new List <IndexedParseException>(); var lines = new List <int>(); foreach (Match m in matches) { //yield front content. if (m.Index > idx) { yield return(new TokenPair(TokenType.Content, templateString.Substring(idx, m.Index - idx))); } if (m.Value.StartsWith("{{#each")) { scopestack.Push(Tuple.Create(m.Value, m.Index)); var token = m.Value.TrimStart('{').TrimEnd('}').TrimStart('#').Trim(); token = token.Substring(4); if (token.StartsWith(" ") && token.Trim() != "") { token = token.Trim(); if (FormatInExpressionFinder.IsMatch(token)) { foreach (var tokenizeFormattable in TokenizeFormattables(token, templateString, m, lines, parseErrors)) { yield return(tokenizeFormattable); } yield return(new TokenPair(TokenType.CollectionOpen, ".")); } else { yield return(new TokenPair(TokenType.CollectionOpen, Validated(token, templateString, m.Index, lines, parseErrors).Trim())); } } else { var location = HumanizeCharacterLocation(templateString, m.Index, lines); parseErrors.Add(new IndexedParseException(location, @"The 'each' block being opened requires a model path to be specified in the form '{{{{#each <name>}}}}'.")); } } else if (m.Value.StartsWith("{{/each")) { if (m.Value != "{{/each}}") { var location = HumanizeCharacterLocation(templateString, m.Index, lines); parseErrors.Add(new IndexedParseException(location, @"The syntax to close the 'each' block should be: '{{{{/each}}}}'.")); } else if (scopestack.Any() && scopestack.Peek().Item1.StartsWith("{{#each")) { var token = scopestack.Pop().Item1; yield return(new TokenPair(TokenType.CollectionClose, token)); } else { var location = HumanizeCharacterLocation(templateString, m.Index, lines); parseErrors.Add(new IndexedParseException(location, @"An 'each' block is being closed, but no corresponding opening element ('{{{{#each <name>}}}}') was detected.")); } } else if (m.Value.StartsWith("{{#")) { //open group var token = m.Value.TrimStart('{').TrimEnd('}').TrimStart('#').Trim(); if (scopestack.Any() && scopestack.Peek().Item1 == token) { yield return(new TokenPair(TokenType.ElementClose, Validated(token, templateString, m.Index, lines, parseErrors))); } else { scopestack.Push(Tuple.Create(token, m.Index)); } yield return(new TokenPair(TokenType.ElementOpen, Validated(token, templateString, m.Index, lines, parseErrors))); } else if (m.Value.StartsWith("{{^")) { //open inverted group var token = m.Value.TrimStart('{').TrimEnd('}').TrimStart('^').Trim(); if (scopestack.Any() && scopestack.Peek().Item1 == token) { yield return(new TokenPair(TokenType.ElementClose, Validated(token, templateString, m.Index, lines, parseErrors))); } else { scopestack.Push(Tuple.Create(token, m.Index)); } yield return(new TokenPair(TokenType.InvertedElementOpen, Validated(token, templateString, m.Index, lines, parseErrors))); } else if (m.Value.StartsWith("{{/")) { var token = m.Value.TrimStart('{').TrimEnd('}').TrimStart('/').Trim(); //close group if (scopestack.Any() && scopestack.Peek().Item1 == token) { scopestack.Pop(); yield return(new TokenPair(TokenType.ElementClose, Validated(token, templateString, m.Index, lines, parseErrors))); } else { var location = HumanizeCharacterLocation(templateString, m.Index, lines); parseErrors.Add(new IndexedParseException(location, "It appears that open and closing elements are mismatched.")); } } else if (m.Value.StartsWith("{{{") | m.Value.StartsWith("{{&")) { //escaped single element var token = m.Value.TrimStart('{').TrimEnd('}').TrimStart('&').Trim(); yield return(new TokenPair(TokenType.UnescapedSingleValue, Validated(token, templateString, m.Index, lines, parseErrors))); } else if (m.Value.StartsWith("{{!")) { //it's a comment drop this on the floor, no need to even yield it. } else { //unsingle value. var token = m.Value.TrimStart('{').TrimEnd('}').Trim(); if (FormatInExpressionFinder.IsMatch(token)) { foreach (var tokenizeFormattable in TokenizeFormattables(token, templateString, m, lines, parseErrors)) { yield return(tokenizeFormattable); } yield return(new TokenPair(TokenType.PrintFormatted, ".")); } else { yield return(new TokenPair(TokenType.EscapedSingleValue, Validated(token, templateString, m.Index, lines, parseErrors))); } } //move forward in the string. idx = m.Index + m.Length; } if (idx < templateString.Length) { yield return(new TokenPair(TokenType.Content, templateString.Substring(idx))); } #region Assert that any scopes opened must be closed. if (scopestack.Any()) { var scopes = scopestack.Select(k => { var value = k.Item1.Trim('{', '#', '}'); if (value.StartsWith("each ")) { value = value.Substring(5); } return(new { scope = value, location = HumanizeCharacterLocation(templateString, k.Item2, lines) }); }).Reverse() .ToArray(); parseErrors.AddRange(scopes.Select(unclosedScope => new IndexedParseException(unclosedScope.location, "A scope block to the following path was opened but not closed: '{0}', please close it using the appropriate syntax.", unclosedScope.scope))); } #endregion //We want to throw an aggregate template exception, but in due time. if (!parseErrors.Any()) { yield break; } var innerExceptions = parseErrors.OrderBy(k => k.LineNumber).ThenBy(k => k.CharacterOnLine).ToArray(); throw new AggregateException(innerExceptions); }
/// <summary> /// Parses the Tokens into a Document. /// </summary> /// <param name="tokenizerResult">The result of an Tokenizer.Tokenize call.</param> /// <param name="options">The ParserOptions</param> /// <returns></returns> public static IDocumentItem Parse(TokenizerResult tokenizerResult, ParserOptions options) { var buildStack = new Stack <DocumentScope>(); //this is the scope id that determines a scope that is using let or alias variables int variableScope = 1; var getScope = new Func <int>(() => variableScope++); //instead of recursive calling the parse function we stack the current document buildStack.Push(new DocumentScope(new MorestachioDocument(), () => 0)); DocumentScope GetVariabeScope() { return(buildStack.FirstOrDefault(e => e.VariableScopeNumber != -1)); } foreach (var currentToken in tokenizerResult.Tokens) { var currentDocumentItem = buildStack.Peek(); //get the latest document if (currentToken.Type.Equals(TokenType.Content)) { currentDocumentItem.Document.Add(new ContentDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.If)) { var nestedDocument = new IfExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.IfNot)) { var nestedDocument = new IfNotExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.Else)) { var nestedDocument = new ElseExpressionScopeDocumentItem() { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.CollectionOpen)) { var nestedDocument = new EachDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.WhileLoopOpen)) { var nestedDocument = new WhileLoopDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.DoLoopOpen)) { var nestedDocument = new DoLoopDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.ElementOpen)) { var nestedDocument = new ExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.RepeatLoopOpen)) { var nestedDocument = new RepeatDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.InvertedElementOpen)) { var invertedScope = new InvertedExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(invertedScope, getScope)); currentDocumentItem.Document.Add(invertedScope); } else if (currentToken.Type.Equals(TokenType.CollectionClose) || currentToken.Type.Equals(TokenType.ElementClose) || currentToken.Type.Equals(TokenType.IfClose) || currentToken.Type.Equals(TokenType.ElseClose) || currentToken.Type.Equals(TokenType.WhileLoopClose) || currentToken.Type.Equals(TokenType.DoLoopClose) || currentToken.Type.Equals(TokenType.RepeatLoopClose)) { DocumentScope scope = buildStack.Peek(); if (scope.HasAlias) //are we in a alias then remove it { foreach (var scopeLocalVariable in scope.LocalVariables) { currentDocumentItem.Document.Add(new RemoveAliasDocumentItem(scopeLocalVariable, scope.VariableScopeNumber)); } } // remove the last document from the stack and go back to the parents buildStack.Pop(); } else if (currentToken.Type.Equals(TokenType.EscapedSingleValue) || currentToken.Type.Equals(TokenType.UnescapedSingleValue)) { currentDocumentItem.Document.Add(new PathDocumentItem(currentToken.MorestachioExpression, currentToken.Type.Equals(TokenType.EscapedSingleValue)) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationOpen)) { // currently same named partials will override each other // to allow recursive calls of partials we first have to declare the partial and then load it as we would parse // -the partial as a whole and then add it to the list would lead to unknown calls of partials inside the partial var nestedDocument = new MorestachioDocument(); buildStack.Push(new DocumentScope(nestedDocument, getScope)); currentDocumentItem.Document.Add(new PartialDocumentItem(currentToken.Value, nestedDocument) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationClose)) { buildStack.Pop(); } else if (currentToken.Type.Equals(TokenType.RenderPartial)) { currentDocumentItem.Document.Add(new RenderPartialDocumentItem(currentToken.Value, currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation, }); } else if (currentToken.Type.Equals(TokenType.Alias)) { var scope = GetVariabeScope(); var aliasDocumentItem = new AliasDocumentItem(currentToken.Value, scope.VariableScopeNumber) { ExpressionStart = currentToken.TokenLocation }; currentDocumentItem.Document.Add(aliasDocumentItem); currentDocumentItem.LocalVariables.Add(currentToken.Value); } else if (currentToken.Type.Equals(TokenType.VariableVar)) { var evaluateVariableDocumentItem = new EvaluateVariableDocumentItem(currentToken.Value, currentToken.MorestachioExpression); currentDocumentItem.Document.Add(evaluateVariableDocumentItem); } else if (currentToken.Type.Equals(TokenType.WriteLineBreak)) { currentDocumentItem.Document.Add(new TextEditDocumentItem(new AppendLineBreakTextOperation())); } else if (currentToken.Type.Equals(TokenType.TrimLineBreak)) { currentDocumentItem.Document.Add(new TextEditDocumentItem(new TrimLineBreakTextOperation() { LineBreaks = 1 })); } else if (currentToken.Type.Equals(TokenType.TrimLineBreaks)) { currentDocumentItem.Document.Add(new TextEditDocumentItem(new TrimLineBreakTextOperation() { LineBreaks = -1 })); } else if (currentToken.Type.Equals(TokenType.TrimEverything)) { currentDocumentItem.Document.Add(new TextEditDocumentItem(new TrimAllWhitespacesTextOperation())); } else if (currentToken.Type.Equals(TokenType.VariableLet)) { var scope = 0; if (buildStack.Count > 1) { scope = GetVariabeScope() .VariableScopeNumber; } var evaluateVariableDocumentItem = new EvaluateVariableDocumentItem(currentToken.Value, currentToken.MorestachioExpression, scope); currentDocumentItem.Document.Add(evaluateVariableDocumentItem); if (buildStack.Count > 1) { currentDocumentItem.LocalVariables.Add(currentToken.Value); } } else if (currentToken.Type.Equals(TokenType.Comment) || currentToken.Type.Equals(TokenType.BlockComment)) { //just ignore this part and print nothing } else { var customDocumentItemProvider = options.CustomDocumentItemProviders.FirstOrDefault(e => e.ShouldParse(currentToken, options)); var documentItem = customDocumentItemProvider?.Parse(currentToken, options, buildStack, getScope); if (documentItem != null) { currentDocumentItem.Document.Add(documentItem); } } } if (buildStack.Count != 1) { //var invalidScopedElements = buildStack //throw new MorestachioSyntaxError(new Tokenizer.CharacterLocation(){Character = }, ); throw new InvalidOperationException( "There is an Error with the Parser. The Parser still contains unscoped builds: " + buildStack.Select(e => e.Document.GetType().Name).Aggregate((e, f) => e + ", " + f)); } return(buildStack.Pop().Document); }
/// <summary> /// Initializes a new instance of the <see cref="MorestachioDocumentInfo"/> class. /// </summary> /// <param name="options">The options.</param> /// <param name="document">The document.</param> public MorestachioDocumentInfo([NotNull] ParserOptions options, [NotNull] IDocumentItem document) : this(options, document ?? throw new ArgumentNullException(nameof(document)), null) { }
public async Task <MorestachioDocumentResult> CreateAsync([NotNull] object data, CancellationToken token) { if (Errors.Any()) { throw new AggregateException("You cannot Create this Template as there are one or more Errors. See Inner Exception for more infos.", Errors.Select(e => e.GetException())).Flatten(); } if (Document is MorestachioDocument morestachioDocument && morestachioDocument.MorestachioVersion != MorestachioDocument.GetMorestachioVersion()) { throw new InvalidOperationException($"The supplied version in the Morestachio document " + $"'{morestachioDocument.MorestachioVersion}'" + $" is not compatible with the current morestachio version of " + $"'{MorestachioDocument.GetMorestachioVersion()}'"); } var timeoutCancellation = new CancellationTokenSource(); if (ParserOptions.Timeout != TimeSpan.Zero) { timeoutCancellation.CancelAfter(ParserOptions.Timeout); var anyCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutCancellation.Token); token = anyCancellationToken.Token; } var sourceStream = ParserOptions.SourceFactory(); try { if (sourceStream == null) { throw new NullReferenceException("The created stream is null."); } if (!sourceStream.CanWrite) { throw new InvalidOperationException($"The stream '{sourceStream.GetType()}' is ReadOnly."); } using (var byteCounterStream = new ByteCounterStream(sourceStream, ParserOptions.Encoding, BufferSize, true, ParserOptions)) { var context = new ContextObject(ParserOptions, "", null) { Value = data, CancellationToken = token }; using (var scopeData = new ScopeData()) { await MorestachioDocument.ProcessItemsAndChildren(new[] { Document }, byteCounterStream, context, scopeData); } } if (timeoutCancellation.IsCancellationRequested) { sourceStream.Dispose(); throw new TimeoutException($"The requested timeout of '{ParserOptions.Timeout:g}' for report generation was reached."); } } catch { //If there is any exception while generating the template we must dispose any data written to the stream as it will never returned and might //create a memory leak with this. This is also true for a timeout sourceStream?.Dispose(); throw; } return(new MorestachioDocumentResult() { Stream = sourceStream }); }
public static MorestachioDocumentInfo ParseWithOptions([NotNull] ParserOptions parsingOptions) { return(ParseWithOptionsAsync(parsingOptions).Result); }
/// <summary> /// Parses the Tokens into a Document. /// </summary> /// <param name="tokens">The tokens.</param> /// <returns></returns> internal static IDocumentItem Parse(Queue <TokenPair> tokens, ParserOptions options) { var buildStack = new Stack <DocumentScope>(); //instead of recursive calling the parse function we stack the current document buildStack.Push(new DocumentScope(new MorestachioDocument())); while (tokens.Any()) { var currentToken = tokens.Dequeue(); var currentDocumentItem = buildStack.Peek(); //get the latest document if (currentToken.Type.Equals(TokenType.Comment)) { //just ignore this part and print nothing } else if (currentToken.Type.Equals(TokenType.Content)) { currentDocumentItem.Document.Add(new ContentDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.If)) { var nestedDocument = new IfExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.IfNot)) { var nestedDocument = new IfNotExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.Else)) { var nestedDocument = new ElseExpressionScopeDocumentItem() { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.CollectionOpen)) { var nestedDocument = new EachDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.WhileLoopOpen)) { var nestedDocument = new WhileLoopDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.DoLoopOpen)) { var nestedDocument = new DoLoopDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.ElementOpen)) { var nestedDocument = new ExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(nestedDocument); } else if (currentToken.Type.Equals(TokenType.InvertedElementOpen)) { var invertedScope = new InvertedExpressionScopeDocumentItem(currentToken.MorestachioExpression) { ExpressionStart = currentToken.TokenLocation }; buildStack.Push(new DocumentScope(invertedScope)); currentDocumentItem.Document.Add(invertedScope); } else if (currentToken.Type.Equals(TokenType.CollectionClose) || currentToken.Type.Equals(TokenType.ElementClose) || currentToken.Type.Equals(TokenType.IfClose) || currentToken.Type.Equals(TokenType.ElseClose) || currentToken.Type.Equals(TokenType.WhileLoopClose) || currentToken.Type.Equals(TokenType.DoLoopClose)) { if (buildStack.Peek().HasAlias) //are we in a alias then remove it { currentDocumentItem.Document.Add(new RemoveAliasDocumentItem(buildStack.Peek().AliasName)); buildStack.Pop(); } // remove the last document from the stack and go back to the parents buildStack.Pop(); } else if (currentToken.Type.Equals(TokenType.EscapedSingleValue) || currentToken.Type.Equals(TokenType.UnescapedSingleValue)) { currentDocumentItem.Document.Add(new PathDocumentItem(currentToken.MorestachioExpression, currentToken.Type.Equals(TokenType.EscapedSingleValue)) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationOpen)) { // currently same named partials will override each other // to allow recursive calls of partials we first have to declare the partial and then load it as we would parse // -the partial as a whole and then add it to the list would lead to unknown calls of partials inside the partial var nestedDocument = new MorestachioDocument(); buildStack.Push(new DocumentScope(nestedDocument)); currentDocumentItem.Document.Add(new PartialDocumentItem(currentToken.Value, nestedDocument) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationClose)) { currentDocumentItem.Document.Add(new RenderPartialDoneDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); buildStack.Pop(); } else if (currentToken.Type.Equals(TokenType.RenderPartial)) { currentDocumentItem.Document.Add(new RenderPartialDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }); } else if (currentToken.Type.Equals(TokenType.Alias)) { var aliasDocumentItem = new AliasDocumentItem(currentToken.Value) { ExpressionStart = currentToken.TokenLocation }; currentDocumentItem.Document.Add(aliasDocumentItem); buildStack.Push(new DocumentScope(aliasDocumentItem, currentToken.Value)); } else if (currentToken.Type.Equals(TokenType.VariableDeclaration)) { var evaluateVariableDocumentItem = new EvaluateVariableDocumentItem(currentToken.Value, currentToken.MorestachioExpression); currentDocumentItem.Document.Add(evaluateVariableDocumentItem); } else { var customDocumentItemProvider = options.CustomDocumentItemProviders.FirstOrDefault(e => e.ShouldParse(currentToken, options)); if (customDocumentItemProvider != null) { var documentItem = customDocumentItemProvider.Parse(currentToken, options, buildStack); currentDocumentItem.Document.Add(documentItem); } } } if (buildStack.Count != 1) { //var invalidScopedElements = buildStack //throw new MorestachioSyntaxError(new Tokenizer.CharacterLocation(){Character = }, ); throw new InvalidOperationException( "There is an Error with the Parser. The Parser still contains unscoped builds: " + buildStack.Select(e => e.Document.Kind).Aggregate((e, f) => e + ", " + f)); } return(buildStack.Pop().Document); }
/// <summary> /// Parses the Tokens into a Document. /// </summary> /// <param name="tokenizerResult">The result of an Tokenizer.Tokenize call.</param> /// <param name="options">The ParserOptions</param> /// <returns></returns> public static IDocumentItem Parse(TokenizerResult tokenizerResult, ParserOptions options) { var buildStack = new Stack <DocumentScope>(); //this is the scope id that determines a scope that is using let or alias variables int variableScope = 1; var getScope = new Func <int>(() => variableScope++); //instead of recursive calling the parse function we stack the current document buildStack.Push(new DocumentScope(new MorestachioDocument(), () => 0)); var textEdits = new List <TextEditDocumentItem>(); DocumentScope GetVariableScope() { return(buildStack.FirstOrDefault(e => e.VariableScopeNumber != -1)); } IEnumerable <ITokenOption> GetPublicOptions(TokenPair token) { var publicOptions = token.TokenOptions?.Where(e => e.Persistent).ToArray(); return(publicOptions?.Length > 0 ? publicOptions : null); } bool TryAdd(IDocumentItem document, IDocumentItem child) { if (document is IBlockDocumentItem block) { block.Add(child); return(true); } return(false); } void CloseScope(Stack <DocumentScope> documentScopes, TokenPair currentToken, DocumentScope currentDocumentItem) { DocumentScope scope = documentScopes.Peek(); if (!(scope.Document is IBlockDocumentItem blockDocument)) { throw new InvalidOperationException( $"Closing an token '{currentToken.Type}' at '{currentToken.TokenLocation}'" + $" that is not of type '{typeof(IBlockDocumentItem)}' is not possible."); } blockDocument.BlockClosingOptions = GetPublicOptions(currentToken); if (scope.HasAlias) //are we in a alias then remove it { foreach (var scopeLocalVariable in scope.LocalVariables) { TryAdd(currentDocumentItem.Document, new RemoveAliasDocumentItem(currentToken.TokenLocation, scopeLocalVariable, scope.VariableScopeNumber, null)); } } // remove the last document from the stack and go back to the parents documentScopes.Pop(); } foreach (var currentToken in tokenizerResult) { var currentDocumentItem = buildStack.Peek(); //get the latest document if (currentToken.Type.Equals(TokenType.Content)) { var contentDocumentItem = new ContentDocumentItem(currentToken.TokenLocation, currentToken.Value, GetPublicOptions(currentToken)); TryAdd(currentDocumentItem.Document, contentDocumentItem); if (tokenizerResult.Previous.HasValue) { if (tokenizerResult.Previous.Value.FindOption <bool>("Embedded.TrimTailing")) { TryAdd(contentDocumentItem, new TextEditDocumentItem(tokenizerResult.Previous.Value.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = 0, LineBreakTrimDirection = LineBreakTrimDirection.Begin }, EmbeddedInstructionOrigin.Previous, GetPublicOptions(currentToken))); } if (tokenizerResult.Previous.Value.FindOption <bool>("Embedded.TrimAllTailing")) { TryAdd(contentDocumentItem, new TextEditDocumentItem(tokenizerResult.Previous.Value.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = -1, LineBreakTrimDirection = LineBreakTrimDirection.Begin }, EmbeddedInstructionOrigin.Previous, GetPublicOptions(currentToken))); } } if (tokenizerResult.Next.HasValue) { if (tokenizerResult.Next.Value.FindOption <bool>("Embedded.TrimLeading")) { TryAdd(contentDocumentItem, new TextEditDocumentItem(tokenizerResult.Next.Value.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = 0, LineBreakTrimDirection = LineBreakTrimDirection.End }, EmbeddedInstructionOrigin.Next, GetPublicOptions(currentToken))); } if (tokenizerResult.Next.Value.FindOption <bool>("Embedded.TrimAllLeading")) { TryAdd(contentDocumentItem, new TextEditDocumentItem(tokenizerResult.Next.Value.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = -1, LineBreakTrimDirection = LineBreakTrimDirection.End }, EmbeddedInstructionOrigin.Next, GetPublicOptions(currentToken))); } } foreach (var textEditDocumentItem in textEdits) { TryAdd(contentDocumentItem, textEditDocumentItem); } textEdits.Clear(); } else if (currentToken.Type.Equals(TokenType.If)) { var nestedDocument = new IfExpressionScopeDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken), false); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.IfNot)) { var nestedDocument = new IfExpressionScopeDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken), true); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.Else)) { var nestedDocument = new ElseExpressionScopeDocumentItem(currentToken.TokenLocation, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); if (currentDocumentItem.Document is IfExpressionScopeDocumentItem ifDocument) { ifDocument.Add(nestedDocument); } } else if (currentToken.Type.Equals(TokenType.ElseIf)) { var nestedDocument = new ElseIfExpressionScopeDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); var documentScope = new DocumentScope(nestedDocument, getScope); buildStack.Push(documentScope); if (currentDocumentItem.Document is IfExpressionScopeDocumentItem ifDocument) { ifDocument.Add(nestedDocument); } //AddIfDocument(currentToken, documentScope); } else if (currentToken.Type.Equals(TokenType.CollectionOpen)) { var nestedDocument = new EachDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.SwitchOpen)) { var nestedDocument = new SwitchDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, currentToken.FindOption <bool>("ScopeTo"), GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.SwitchCaseOpen)) { var nestedDocument = new SwitchCaseDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.SwitchDefaultOpen)) { var nestedDocument = new SwitchDefaultDocumentItem(currentToken.TokenLocation, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.WhileLoopOpen)) { var nestedDocument = new WhileLoopDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.DoLoopOpen)) { var nestedDocument = new DoLoopDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.ElementOpen)) { var nestedDocument = new ExpressionScopeDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.RepeatLoopOpen)) { var nestedDocument = new RepeatDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.InvertedElementOpen)) { var nestedDocument = new InvertedExpressionScopeDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(nestedDocument, getScope)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.CollectionClose) || currentToken.Type.Equals(TokenType.ElementClose) || currentToken.Type.Equals(TokenType.IfClose) || currentToken.Type.Equals(TokenType.ElseClose) || currentToken.Type.Equals(TokenType.ElseIfClose) || currentToken.Type.Equals(TokenType.WhileLoopClose) || currentToken.Type.Equals(TokenType.DoLoopClose) || currentToken.Type.Equals(TokenType.RepeatLoopClose) || currentToken.Type.Equals(TokenType.SwitchCaseClose) || currentToken.Type.Equals(TokenType.SwitchDefaultClose) || currentToken.Type.Equals(TokenType.SwitchClose)) { CloseScope(buildStack, currentToken, currentDocumentItem); } else if (currentToken.Type.Equals(TokenType.EscapedSingleValue) || currentToken.Type.Equals(TokenType.UnescapedSingleValue)) { var nestedDocument = new PathDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, currentToken.Type.Equals(TokenType.EscapedSingleValue), GetPublicOptions(currentToken)); TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationOpen)) { // currently same named partials will override each other // to allow recursive calls of partials we first have to declare the partial and then load it as we would parse // -the partial as a whole and then add it to the list would lead to unknown calls of partials inside the partial var partialDocumentItem = new PartialDocumentItem(currentToken.TokenLocation, currentToken.Value, GetPublicOptions(currentToken)); buildStack.Push(new DocumentScope(partialDocumentItem, getScope)); TryAdd(currentDocumentItem.Document, partialDocumentItem); } else if (currentToken.Type.Equals(TokenType.PartialDeclarationClose)) { CloseScope(buildStack, currentToken, currentDocumentItem); //buildStack.Pop(); } else if (currentToken.Type.Equals(TokenType.RenderPartial)) { TryAdd(currentDocumentItem.Document, new RenderPartialDocumentItem(currentToken.TokenLocation, currentToken.Value, currentToken.MorestachioExpression, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.ImportPartial)) { TryAdd(currentDocumentItem.Document, new ImportPartialDocumentItem(currentToken.TokenLocation, currentToken.MorestachioExpression, currentToken.FindOption <IMorestachioExpression>("Context"), GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.IsolationScopeOpen)) { var nestedDocument = new IsolationScopeDocumentItem(currentToken.TokenLocation, currentToken.FindOption <IsolationOptions>("IsolationType"), currentToken.FindOption <IMorestachioExpression>("IsolationScopeArg"), GetPublicOptions(currentToken)); TryAdd(currentDocumentItem.Document, nestedDocument); buildStack.Push(new DocumentScope(nestedDocument, getScope)); } else if (currentToken.Type.Equals(TokenType.IsolationScopeClose)) { CloseScope(buildStack, currentToken, currentDocumentItem); } else if (currentToken.Type.Equals(TokenType.Alias)) { var scope = GetVariableScope(); var nestedDocument = new AliasDocumentItem(currentToken.TokenLocation, currentToken.Value, scope.VariableScopeNumber, GetPublicOptions(currentToken)); TryAdd(currentDocumentItem.Document, nestedDocument); currentDocumentItem.LocalVariables.Add(currentToken.Value); } else if (currentToken.Type.Equals(TokenType.VariableVar)) { EvaluateVariableDocumentItem nestedDocument; var isolationParent = buildStack.FirstOrDefault(e => e.Document is IsolationScopeDocumentItem doc && doc.Isolation.HasFlag(IsolationOptions.VariableIsolation)); if (isolationParent != null) { nestedDocument = new EvaluateVariableDocumentItem(currentToken.TokenLocation, currentToken.Value, currentToken.MorestachioExpression, isolationParent.VariableScopeNumber, GetPublicOptions(currentToken)); isolationParent.LocalVariables.Add(currentToken.Value); } else { nestedDocument = new EvaluateVariableDocumentItem(currentToken.TokenLocation, currentToken.Value, currentToken.MorestachioExpression, GetPublicOptions(currentToken)); } TryAdd(currentDocumentItem.Document, nestedDocument); } else if (currentToken.Type.Equals(TokenType.VariableLet)) { var scope = 0; if (buildStack.Count > 1) { scope = GetVariableScope() .VariableScopeNumber; } var nestedDocument = new EvaluateLetVariableDocumentItem(currentToken.TokenLocation, currentToken.Value, currentToken.MorestachioExpression, scope, GetPublicOptions(currentToken)); TryAdd(currentDocumentItem.Document, nestedDocument); if (buildStack.Count > 1) { currentDocumentItem.LocalVariables.Add(currentToken.Value); } } else if (currentToken.Type.Equals(TokenType.WriteLineBreak)) { TryAdd(currentDocumentItem.Document, new TextEditDocumentItem(currentToken.TokenLocation, new AppendLineBreakTextOperation(), currentToken.IsEmbeddedToken, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.TrimLineBreak)) { textEdits.Add(new TextEditDocumentItem(currentToken.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = 1, LineBreakTrimDirection = LineBreakTrimDirection.Begin }, currentToken.IsEmbeddedToken, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.TrimLineBreaks)) { textEdits.Add(new TextEditDocumentItem(currentToken.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = currentToken.FindOption <bool>("All") ? -1 : 0, LineBreakTrimDirection = LineBreakTrimDirection.Begin }, currentToken.IsEmbeddedToken, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.TrimPrependedLineBreaks)) { textEdits.Add(new TextEditDocumentItem(currentToken.TokenLocation, new TrimLineBreakTextOperation() { LineBreaks = currentToken.FindOption <bool>("All") ? -1 : 0, LineBreakTrimDirection = LineBreakTrimDirection.End }, currentToken.IsEmbeddedToken, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.TrimEverything)) { textEdits.Add(new TextEditDocumentItem(currentToken.TokenLocation, new TrimAllWhitespacesTextOperation(), currentToken.IsEmbeddedToken, GetPublicOptions(currentToken))); } else if (currentToken.Type.Equals(TokenType.Comment) || currentToken.Type.Equals(TokenType.BlockComment)) { //just ignore this part and print nothing if (options.TokenizeComments) { TryAdd(currentDocumentItem.Document, new CommentDocumentItem(currentToken.TokenLocation, currentToken.Value, GetPublicOptions(currentToken), currentToken.Type.Equals(TokenType.BlockComment))); } } else { var tokenOptions = GetPublicOptions(currentToken); var customDocumentItemProvider = options.CustomDocumentItemProviders.FindTokenProvider(currentToken, options, tokenOptions); var nestedDocument = customDocumentItemProvider?.Parse(currentToken, options, buildStack, getScope, tokenOptions); if (nestedDocument != null) { TryAdd(currentDocumentItem.Document, nestedDocument); } } } if (buildStack.Count != 1) { //var invalidScopedElements = buildStack //throw new MorestachioSyntaxError(new Tokenizer.CharacterLocation(){Character = }, ); throw new InvalidOperationException( "There is an Error with the Parser. The Parser still contains unscoped builds: " + buildStack.Select(e => e.Document.GetType().Name).Aggregate((e, f) => e + ", " + f)); } return(buildStack.Pop().Document); }
/// <summary> /// Initializes a new instance of the <see cref="ContextObject"/> class. /// </summary> /// <param name="options">The options.</param> public ContextObject([NotNull] ParserOptions options) { Options = options; }
internal static AsyncParserAction Parse(Queue <TokenPair> tokens, ParserOptions options, ScopeData scopeData, InferredTemplateModel currentScope = null) { var buildArray = new ParserActions(); while (tokens.Any()) { var currentToken = tokens.Dequeue(); switch (currentToken.Type) { case TokenType.Comment: break; case TokenType.Content: buildArray.MakeAction(HandleContent(currentToken.Value)); break; case TokenType.CollectionOpen: buildArray.MakeAction(HandleCollectionOpen(currentToken, tokens, options, scopeData, currentScope)); break; case TokenType.ElementOpen: buildArray.MakeAction(HandleElementOpen(currentToken, tokens, options, scopeData, currentScope)); break; case TokenType.InvertedElementOpen: buildArray.MakeAction(HandleInvertedElementOpen(currentToken, tokens, options, scopeData, currentScope)); break; case TokenType.CollectionClose: case TokenType.ElementClose: // This should immediately return if we're in the element scope, // -and if we're not, this should have been detected by the tokenizer! return(async(builder, context) => { await buildArray.ExecuteWith(builder, context); }); case TokenType.Format: buildArray.MakeAction(ParseFormatting(currentToken, tokens, options, scopeData, currentScope)); break; case TokenType.EscapedSingleValue: case TokenType.UnescapedSingleValue: buildArray.MakeAction(HandleSingleValue(currentToken, options, scopeData, currentScope)); break; case TokenType.PartialDeclarationOpen: // currently same named partials will override each other // to allow recursive calls of partials we first have to declare the partial and then load it as we would parse // -the partial as a whole and then add it to the list would lead to unknown calls of partials inside the partial AsyncParserAction handlePartialDeclaration = null; scopeData.Partials[currentToken.Value] = async(outputStream, context) => { if (handlePartialDeclaration != null) { await handlePartialDeclaration(outputStream, context); } else { throw new InvalidOperationException($"Partial '{currentToken.Value}' was executed before created was completed."); } }; handlePartialDeclaration = HandlePartialDeclaration(currentToken, tokens, options, scopeData, currentScope); break; case TokenType.RenderPartial: var partialName = currentToken.Value; var partialCode = scopeData.Partials[partialName]; buildArray.MakeAction(async(a, f) => { var currentPartial = partialName + "_" + scopeData.PartialDepth.Count; scopeData.PartialDepth.Push(currentPartial); if (scopeData.PartialDepth.Count >= options.PartialStackSize) { switch (options.StackOverflowBehavior) { case ParserOptions.PartialStackOverflowBehavior.FailWithException: throw new MustachioStackOverflowException( $"You have exceeded the maximum stack Size for nested Partial calls of '{options.PartialStackSize}'. See Data for call stack") { Data = { { "Callstack", scopeData.PartialDepth } } }; break; case ParserOptions.PartialStackOverflowBehavior.FailSilent: break; default: throw new ArgumentOutOfRangeException(); } } else { await partialCode(a, f); } scopeData.PartialDepth.Pop(); }); break; } } return(async(builder, context) => { await buildArray.ExecuteWith(builder, context); }); }
public async MorestachioDocumentResultPromise CreateAsync([NotNull] object data, CancellationToken token) { if (Errors.Any()) { throw new AggregateException("You cannot Create this Template as there are one or more Errors. See Inner Exception for more infos.", Errors.Select(e => e.GetException())).Flatten(); } if (Document is MorestachioDocument morestachioDocument && morestachioDocument.MorestachioVersion != MorestachioDocument.GetMorestachioVersion()) { throw new InvalidOperationException($"The supplied version in the Morestachio document " + $"'{morestachioDocument.MorestachioVersion}'" + $" is not compatible with the current morestachio version of " + $"'{MorestachioDocument.GetMorestachioVersion()}'"); } var timeoutCancellation = new CancellationTokenSource(); if (ParserOptions.Timeout != TimeSpan.Zero) { timeoutCancellation.CancelAfter(ParserOptions.Timeout); var anyCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutCancellation.Token); token = anyCancellationToken.Token; } PerformanceProfiler profiler = null; using (var byteCounterStream = ParserOptions.StreamFactory.GetByteCounterStream(ParserOptions)) { if (byteCounterStream?.Stream == null) { throw new NullReferenceException("The created stream is null."); } var context = ParserOptions.CreateContextObject("", token, data); var scopeData = new ScopeData(); try { if (ParserOptions.ProfileExecution) { scopeData.Profiler = profiler = new PerformanceProfiler(true); } await MorestachioDocument.ProcessItemsAndChildren(new[] { Document }, byteCounterStream, context, scopeData); if (timeoutCancellation.IsCancellationRequested) { throw new TimeoutException($"The requested timeout of '{ParserOptions.Timeout:g}' for template generation was reached."); } } finally { if (!CaptureVariables) { scopeData.Dispose(); scopeData.Variables.Clear(); } } return(new MorestachioDocumentResult(byteCounterStream.Stream, profiler, scopeData.Variables.ToDictionary(e => e.Key, e => scopeData.GetFromVariable(e.Value).Value))); } }