/// <summary> /// ctor /// </summary> /// <param name="inferredModel"></param> /// <param name="parserOptions"></param> /// <param name="tokens"></param> internal ExtendedParseInformation(InferredTemplateModel inferredModel, ParserOptions parserOptions, Queue <TokenPair> tokens) { InferredModel = inferredModel; ParserOptions = parserOptions; TemplateTokens = tokens; InternalTemplate = new Lazy <Action <Parser.ByteCounterStreamWriter, ContextObject> >(() => Parser.Parse(TemplateTokens, ParserOptions, ParserOptions.WithModelInference ? InferredModel : null)); }
internal static Action <ByteCounterStreamWriter, ContextObject> Parse(Queue <TokenPair> tokens, ParserOptions options, InferredTemplateModel currentScope = null) { var buildArray = new List <Action <ByteCounterStreamWriter, ContextObject> >(); while (tokens.Any()) { var currentToken = tokens.Dequeue(); switch (currentToken.Type) { case TokenType.Comment: break; case TokenType.Content: buildArray.Add(HandleContent(currentToken.Value)); break; case TokenType.CollectionOpen: buildArray.Add(HandleCollectionOpen(currentToken, tokens, options, currentScope)); break; case TokenType.ElementOpen: buildArray.Add(HandleElementOpen(currentToken, tokens, options, currentScope)); break; case TokenType.InvertedElementOpen: buildArray.Add(HandleInvertedElementOpen(currentToken, tokens, options, 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((builder, context) => { foreach (var a in buildArray.TakeWhile(e => StopOrAbortBuilding(builder, context))) { a(builder, context); } }); case TokenType.Format: buildArray.Add(ParseFormatting(currentToken, tokens, options, currentScope)); break; case TokenType.EscapedSingleValue: case TokenType.UnescapedSingleValue: buildArray.Add(HandleSingleValue(currentToken, options, currentScope)); break; } } return((builder, context) => { foreach (var a in buildArray.TakeWhile(e => StopOrAbortBuilding(builder, context))) { a(builder, context); } }); }
private static AsyncParserAction HandleSingleValue(TokenPair token, ParserOptions options, ScopeData scopeData, InferredTemplateModel scope) { scope = scope?.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Scalar); return(async(builder, context) => { //try to locate the value in the context, if it exists, append it. var c = context != null ? (await context.GetContextForPath(token.Value, scopeData)) : null; if (c?.Value != null) { await c.EnsureValue(); if (token.Type == TokenType.EscapedSingleValue && !options.DisableContentEscaping) { HandleContent(HtmlEncodeString(await c.RenderToString()))(builder, c); } else { HandleContent(await c.RenderToString())(builder, c); } } }); }
public static ExtendedParseInformation ParseWithOptions([NotNull] ParserOptions parsingOptions) { if (parsingOptions == null) { throw new ArgumentNullException(nameof(parsingOptions)); } if (parsingOptions.SourceFactory == null) { throw new ArgumentNullException(nameof(parsingOptions), "The given Stream is null"); } var tokens = new Queue <TokenPair>(Tokenizer.Tokenize(parsingOptions)); var inferredModel = new InferredTemplateModel(); var extendedParseInformation = new ExtendedParseInformation(inferredModel, parsingOptions, tokens); if (parsingOptions.WithModelInference) { //we preparse the template once to get the model var s = extendedParseInformation.InternalTemplate.Value; } return(extendedParseInformation); }
/// <summary> /// ctor /// </summary> /// <param name="inferredModel"></param> /// <param name="parserOptions"></param> /// <param name="tokens"></param> internal ExtendedParseInformation(InferredTemplateModel inferredModel, ParserOptions parserOptions, Queue <TokenPair> tokens) { InferredModel = inferredModel; ParserOptions = parserOptions; TemplateTokens = tokens; InternalTemplate = new Lazy <Parser.AsyncParserAction>( () => Parser.Parse(TemplateTokens, ParserOptions, new Parser.ScopeData(), ParserOptions.WithModelInference ? InferredModel : null)); }
private static AsyncParserAction HandleCollectionOpen(TokenPair token, Queue <TokenPair> remainder, ParserOptions options, ScopeData scopeData, InferredTemplateModel scope) { scope = scope?.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Collection); var innerTemplate = Parse(remainder, options, scopeData, scope); return(async(builder, context) => { //if we're in the same scope, just negating, then we want to use the same object var c = await context.GetContextForPath(token.Value, scopeData); //"falsey" values by Javascript standards... if (!await c.Exists()) { return; } var value = c.Value as IEnumerable; if (value != null && !(value is string) && !(value is IDictionary <string, object>)) { //Use this "lookahead" enumeration to allow the $last keyword var index = 0; var enumumerator = value.GetEnumerator(); if (!enumumerator.MoveNext()) { return; } var current = enumumerator.Current; do { var next = enumumerator.MoveNext() ? enumumerator.Current : null; var innerContext = new ContextCollection(index, next == null, options, $"[{index}]") { Value = current, Parent = c }; await innerTemplate(builder, innerContext); index++; current = next; } while (current != null && StopOrAbortBuilding(builder, context)); } else { throw new IndexedParseException( "'{0}' is used like an array by the template, but is a scalar value or object in your model.", token.Value); } }); }
private static AsyncParserAction HandlePartialDeclaration(TokenPair currentToken, Queue <TokenPair> tokens, ParserOptions options, ScopeData scopeData, InferredTemplateModel currentScope) { var partialTokens = new Queue <TokenPair>(); var token = currentToken; while (tokens.Any() && (token.Type != TokenType.PartialDeclarationClose || token.Value != currentToken.Value)) //just look for the closing tag and buffer it seperate { token = tokens.Dequeue(); partialTokens.Enqueue(token); } return(Parse(partialTokens, options, scopeData, currentScope)); //we have taken everything from the partial and created a executable function for it }
private static AsyncParserAction HandleElementOpen(TokenPair token, Queue <TokenPair> remainder, ParserOptions options, ScopeData scopeData, InferredTemplateModel scope) { scope = scope?.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.ConditionalValue); var innerTemplate = Parse(remainder, options, scopeData, scope); return(async(builder, context) => { var c = await context.GetContextForPath(token.Value, scopeData); //"falsey" values by Javascript standards... if (await c.Exists()) { await innerTemplate(builder, c); } }); }
private InferredTemplateModel GetContextForPath(Queue <string> elements) { var retval = this; if (elements.Any()) { var element = elements.Dequeue(); if (element.StartsWith("..")) { if (Parent != null) { retval = Parent.GetContextForPath(elements); } else { //Calling "../" too much may be "ok" in that if we're at root, //we may just stop recursion and traverse down the path. retval = GetContextForPath(elements); } } //TODO: handle array accessors and maybe "special" keys. else { //ALWAYS return the context, even if the value is null. InferredTemplateModel innerContext = null; if (!Children.TryGetValue(element, out innerContext)) { innerContext = new InferredTemplateModel(); innerContext.Key = element; innerContext.Parent = this; Children[element] = innerContext; } retval = innerContext.GetContextForPath(elements); } } return(retval); }
private static Action <ByteCounterStreamWriter, ContextObject> HandleElementOpen(TokenPair token, Queue <TokenPair> remainder, ParserOptions options, InferredTemplateModel scope) { scope = scope?.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.ConditionalValue); var innerTemplate = Parse(remainder, options, scope); return((builder, context) => { var c = context.GetContextForPath(token.Value); //"falsey" values by Javascript standards... if (c.Exists()) { innerTemplate(builder, c); } }); }
private static Action <ByteCounterStreamWriter, ContextObject> HandleSingleValue(TokenPair token, ParserOptions options, InferredTemplateModel scope) { scope = scope?.GetInferredModelForPath(token.Value, InferredTemplateModel.UsedAs.Scalar); return((builder, context) => { //try to locate the value in the context, if it exists, append it. var c = context?.GetContextForPath(token.Value); if (c?.Value != null) { if (token.Type == TokenType.EscapedSingleValue && !options.DisableContentEscaping) { HandleContent(HtmlEncodeString(c.ToString()))(builder, c); } else { HandleContent(c.ToString())(builder, c); } } }); }
private static Action <ByteCounterStreamWriter, ContextObject> HandleFormattingValue(TokenPair currentToken, ParserOptions options, InferredTemplateModel scope) { return((builder, context) => { scope = scope?.GetInferredModelForPath(currentToken.Value, InferredTemplateModel.UsedAs.Scalar); if (context == null) { return; } var c = context.GetContextForPath(currentToken.Value); if (currentToken.FormatString != null && currentToken.FormatString.Any()) { var argList = new List <KeyValuePair <string, object> >(); foreach (var formatterArgument in currentToken.FormatString) { object value = null; //if pre and suffixed by a $ its a reference to another field. //walk the path in the $ and use the value in the formatter var trimmedArg = formatterArgument.Argument.Trim(); if (trimmedArg.StartsWith("$") && trimmedArg.EndsWith("$")) { var formatContext = context.GetContextForPath(trimmedArg.Trim('$')); argList.Add(new KeyValuePair <string, object>(formatterArgument.Name, formatContext.Value)); } else { argList.Add(new KeyValuePair <string, object>(formatterArgument.Name, formatterArgument.Argument)); } } context.Value = c.Format(argList.ToArray()); } else { context.Value = c.Format(new KeyValuePair <string, object> [0]); } }); }
private static Action <ByteCounterStreamWriter, ContextObject> PrintFormattedValues(TokenPair currentToken, ParserOptions options, InferredTemplateModel currentScope) { return((builder, context) => { if (context == null) { return; } string value = null; if (context.Value != null) { value = context.ToString(); } HandleContent(value)(builder, context); }); }
private static Action <ByteCounterStreamWriter, ContextObject> ParseFormatting(TokenPair token, Queue <TokenPair> tokens, ParserOptions options, InferredTemplateModel currentScope = null) { var buildArray = new List <Action <ByteCounterStreamWriter, ContextObject> >(); buildArray.Add(HandleFormattingValue(token, options, currentScope)); var nonPrintToken = false; while (tokens.Any() && !nonPrintToken) { var currentToken = tokens.Peek(); switch (currentToken.Type) { case TokenType.Format: buildArray.Add(HandleFormattingValue(tokens.Dequeue(), options, currentScope)); break; case TokenType.PrintFormatted: buildArray.Add(PrintFormattedValues(tokens.Dequeue(), options, currentScope)); break; case TokenType.CollectionOpen: buildArray.Add(HandleCollectionOpen(tokens.Dequeue(), tokens, options, currentScope)); break; default: //The folloring cannot be formatted and the result of the formatting operation has used. //continue with the original Context nonPrintToken = true; break; } } return((builder, context) => { //the formatting will may change the object. Clone the current Context to leave the root one untouched var contextClone = context.Clone(); foreach (var a in buildArray.TakeWhile(e => StopOrAbortBuilding(builder, context))) { a(builder, contextClone); } }); }
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); }); }
private static AsyncParserAction HandleFormattingValue(TokenPair currentToken, InferredTemplateModel scope, ScopeData scopeData) { return(async(builder, context) => { scope = scope?.GetInferredModelForPath(currentToken.Value, InferredTemplateModel.UsedAs.Scalar); if (context == null) { return; } var c = await context.GetContextForPath(currentToken.Value, scopeData); if (currentToken.FormatString != null && currentToken.FormatString.Any()) { var argList = new List <KeyValuePair <string, object> >(); foreach (var formatterArgument in currentToken.FormatString) { //if pre and suffixed by a $ its a reference to another field. //walk the path in the $ and use the value in the formatter var trimmedArg = formatterArgument.Argument.Trim(); if (trimmedArg.StartsWith("$") && trimmedArg.EndsWith("$")) { var formatContext = await context.GetContextForPath(trimmedArg.Trim('$'), scopeData); await formatContext.EnsureValue(); argList.Add(new KeyValuePair <string, object>(formatterArgument.Name, formatContext.Value)); } else { argList.Add(new KeyValuePair <string, object>(formatterArgument.Name, formatterArgument.Argument)); } } //we do NOT await the task here. We await the task only if we need the value context.Value = c.Format(argList.ToArray()); } else { context.Value = c.Format(new KeyValuePair <string, object> [0]); } }); }
private static AsyncParserAction ParseFormatting(TokenPair token, Queue <TokenPair> tokens, ParserOptions options, ScopeData scopeData, InferredTemplateModel currentScope = null) { var buildArray = new ParserActions(); buildArray.MakeAction(HandleFormattingValue(token, currentScope, scopeData)); var nonPrintToken = false; while (tokens.Any() && !nonPrintToken) //only take as few tokens we need for formatting. { var currentToken = tokens.Peek(); switch (currentToken.Type) { case TokenType.Format: //this will invoke the formatter and copy the scope. //we must copy the scope as the formatting action might break our chain and we are no longer able to //construct a valid path up //after that there is always a PrintFormatted type that will print the "current" scope and //reset it to the origial scope before we have entered the scope buildArray.MakeAction(HandleFormattingValue(tokens.Dequeue(), currentScope, scopeData)); break; case TokenType.PrintFormatted: tokens.Dequeue(); //this must be the flow token type that has no real value execpt for a dot buildArray.MakeAction(PrintFormattedValues()); break; case TokenType.CollectionOpen: //in this case we are in a formatting expression followed by a #each. //after this we need to reset the context so handle the open here buildArray.MakeAction(HandleCollectionOpen(tokens.Dequeue(), tokens, options, scopeData, currentScope)); break; case TokenType.ElementOpen: //in this case we are in a formatting expression followed by a #. //after this we need to reset the context so handle the open here buildArray.MakeAction(HandleElementOpen(tokens.Dequeue(), tokens, options, scopeData, currentScope)); break; case TokenType.InvertedElementOpen: //in this case we are in a formatting expression followed by a ^. //after this we need to reset the context so handle the open here buildArray.MakeAction(HandleElementOpen(tokens.Dequeue(), tokens, options, scopeData, currentScope)); break; default: //The following cannot be formatted and the result of the formatting operation has used. //continue with the original Context nonPrintToken = true; break; } } return(async(builder, context) => { //the formatting will may change the object. Clone the current Context to leave the root one untouched var contextClone = context.Clone(); await buildArray.ExecuteWith(builder, contextClone); }); }