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); }
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; }
/// <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; }
/// <summary> /// Deserializes the given frame given a known width and height. /// </summary> /// <param name="serializedFrame">the frame data</param> /// <param name="width">the known width of the frame</param> /// <param name="height">the known height of the frame</param> /// <returns>a deserialized frame that's either a raw frame or a diff frame, depending on what was in the serialized string</returns> public ConsoleBitmapFrame DeserializeFrame(string serializedFrame, int width, int height) { var tokens = tokenizer.Tokenize(serializedFrame); var reader = new TokenReader<Token>(tokens); reader.Expect("["); var timestampToken = reader.Advance(); var timestamp = new TimeSpan(long.Parse(timestampToken.Value)); reader.Expect("]"); reader.Expect("["); reader.Advance(); var isDiff = reader.Current.Value == "Diff"; reader.Expect("]"); if (isDiff) { var diffFrame = new ConsoleBitmapDiffFrame() { Timestamp = timestamp, Diffs = new System.Collections.Generic.List<ConsoleBitmapPixelDiff>() }; var lastBackground = ConsoleString.DefaultBackgroundColor; var lastForeground = ConsoleString.DefaultForegroundColor; while (reader.CanAdvance(skipWhitespace: true)) { reader.Expect("[", skipWhiteSpace: true); if (reader.Peek().Value.StartsWith("F=") || reader.Peek().Value.StartsWith("B=")) { reader.Advance(); var match = ColorSpecifierRegex.Match(reader.Current.Value); if (match.Success == false) throw new FormatException($"Unexpected token {reader.Current.Value} at position {reader.Current.Position} "); var isForeground = match.Groups["ForB"].Value == "F"; if (isForeground) { if (Enum.TryParse(match.Groups["color"].Value, out ConsoleColor c)) { lastForeground = (RGB)c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastForeground) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } else { if (Enum.TryParse(match.Groups["color"].Value, out ConsoleColor c)) { lastBackground = (RGB)c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastBackground) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } reader.Expect("]"); } else { var match = PixelDiffRegex.Match(reader.Advance().Value); if (match.Success == false) throw new FormatException("Could not parse pixel diff"); var valGroup = match.Groups["val"].Value; char? nextChar = valGroup.Length == 1 ? valGroup[0] : valGroup == "OB" ? '[' : valGroup == "CB" ? ']' : new char?(); if (nextChar.HasValue == false) throw new FormatException($"Unexpected token {nextChar} @ {reader.Position}"); diffFrame.Diffs.Add(new ConsoleBitmapPixelDiff() { X = int.Parse(match.Groups["x"].Value), Y = int.Parse(match.Groups["y"].Value), Value = new ConsoleCharacter(nextChar.Value, lastForeground, lastBackground), }); reader.Expect("]"); } } return diffFrame; } else { var rawFrame = new ConsoleBitmapRawFrame() { Timestamp = timestamp, Pixels = new ConsoleCharacter[width][] }; for (var i = 0; i < width; i++) { rawFrame.Pixels[i] = new ConsoleCharacter[height]; } var x = 0; var y = 0; var lastFg = ConsoleString.DefaultForegroundColor; var lastBg = ConsoleString.DefaultBackgroundColor; while (reader.CanAdvance(skipWhitespace:true)) { reader.Expect("[", skipWhiteSpace:true); var next = reader.Advance(); var match = ColorSpecifierRegex.Match(next.Value); if (match.Success) { var isForeground = match.Groups["ForB"].Value == "F"; if (isForeground) { if(Enum.TryParse<ConsoleColor>(match.Groups["color"].Value, out ConsoleColor c)) { lastFg = c; } else if(RGB.TryParse(match.Groups["color"].Value, out lastFg) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } else { if (Enum.TryParse<ConsoleColor>(match.Groups["color"].Value, out ConsoleColor c)) { lastBg = c; } else if (RGB.TryParse(match.Groups["color"].Value, out lastBg) == false) { throw new ArgumentException($"Expected a color @ {reader.Position}"); } } } else { char? nextChar = next.Value.Length == 1 ? next.Value[0] : next.Value == "OB" ? '[' : next.Value == "CB" ? ']' : new char?(); if (nextChar.HasValue == false) throw new FormatException($"Unexpected token {nextChar} @ {next.Position}"); rawFrame.Pixels[x][y++] = new ConsoleCharacter(nextChar.Value, lastFg, lastBg); if (y == height) { y = 0; x++; } } reader.Expect("]"); } return rawFrame; } }
/// <summary> /// Creates either an if or an ifnot expression, based on its configuration, using the given document info. /// </summary> /// <param name="context">The context that contains information about the document being rendered</param> /// <returns>The expression, either an if or an ifnot expression</returns> public IDocumentExpression CreateExpression(DocumentExpressionContext context) { TokenReader<DocumentToken> reader = new TokenReader<DocumentToken>(context.Parameters); DocumentToken ifExpressionToken; if(reader.TryAdvance(out ifExpressionToken, skipWhitespace: true) == false) { throw new DocumentRenderException("missing if expression", context.ReplacementKeyToken); } if (reader.CanAdvance(skipWhitespace: true)) { throw new DocumentRenderException("unexpected parameters after if expression", reader.Advance(skipWhitespace: true)); } return not ? new IfNotExpression(ifExpressionToken, context.Body) : new IfExpression(ifExpressionToken, context.Body); }
/// <summary> /// Parses an object path expression from a string. /// </summary> /// <param name="expression">The expression text to parse</param> /// <returns>The parsed expression</returns> public static ObjectPathExpression Parse(string expression) { if (expression == null) throw new ArgumentNullException("path cannot be null"); if (expression.Length == 0) throw new FormatException("Cannot parse empty string"); Tokenizer<ObjectPathToken> tokenizer = new Tokenizer<ObjectPathToken>(); tokenizer.TokenFactory = ObjectPathToken.TokenFactoryImpl; tokenizer.WhitespaceBehavior = WhitespaceBehavior.DelimitAndInclude; tokenizer.DoubleQuoteBehavior = DoubleQuoteBehavior.IncludeQuotedTokensAsStringLiterals; tokenizer.Delimiters.Add("["); tokenizer.Delimiters.Add("]"); tokenizer.Delimiters.Add("."); List<ObjectPathToken> tokens = tokenizer.Tokenize(expression); TokenReader<ObjectPathToken> reader = new TokenReader<ObjectPathToken>(tokens); List<IObjectPathElement> pathElements = new List<IObjectPathElement>(); bool lastTokenWasNavigation = false; while (reader.CanAdvance(skipWhitespace: true)) { var currentToken = reader.Advance(skipWhitespace: true); if (lastTokenWasNavigation == true && currentToken.TokenType == ObjectPathTokenType.IndexerOpen) { throw new FormatException("Expected property, got '['" + " at " + currentToken.Position); } lastTokenWasNavigation = false; if(pathElements.Count == 0 && currentToken.TokenType == ObjectPathTokenType.NavigationElement) { throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position); } if (currentToken.TokenType == ObjectPathTokenType.IndexerClose || currentToken.TokenType == ObjectPathTokenType.StringLiteral) { throw new FormatException("Expected property or index, got '" + currentToken.Value + "'" + " at " + currentToken.Position); } if (currentToken.TokenType == ObjectPathTokenType.IndexerOpen) { // read index value if (reader.TryAdvance(out currentToken,skipWhitespace: true) == false) throw new FormatException("Expected index value, got end of string"); if (currentToken.TokenType == ObjectPathTokenType.Identifier || currentToken.TokenType == ObjectPathTokenType.StringLiteral) { string indexValueText = currentToken.Value; if(currentToken.TokenType == ObjectPathTokenType.StringLiteral) { indexValueText = indexValueText.Substring(1, indexValueText.Length - 2); } object indexValue; int indexValueInt; if (int.TryParse(indexValueText, out indexValueInt) == false) { indexValue = indexValueText; } else { indexValue = indexValueInt; } // read index close if (reader.TryAdvance(out currentToken, skipWhitespace: true) == false) throw new FormatException("Expected ']', got end of string"); if (currentToken.TokenType != ObjectPathTokenType.IndexerClose) throw new FormatException("Expected ']', got '" + currentToken.Value + "' at " + currentToken.Position); IndexerPathElement el = new IndexerPathElement(indexValue); pathElements.Add(el); if (reader.TryAdvance(out currentToken, skipWhitespace: true)) { if (currentToken.TokenType != ObjectPathTokenType.NavigationElement) throw new FormatException("Expected '.', got '" + currentToken.Value + "' at " + currentToken.Position); if (reader.CanAdvance(skipWhitespace: true) == false) throw new FormatException("Expected property, got end of string"); lastTokenWasNavigation = true; } } else { throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position); } } else if(currentToken.TokenType == ObjectPathTokenType.Identifier) { PropertyPathElement el = new PropertyPathElement(currentToken.Value); pathElements.Add(el); } else if(currentToken.TokenType == ObjectPathTokenType.NavigationElement) { // do nothing } else { throw new ArgumentException("Unexpected token '" + currentToken.Value + "' at " + currentToken.Position); } } return new ObjectPathExpression(pathElements); }
/// <summary> /// Creates a table expression from the given document info. /// </summary> /// <param name="context">The context that contains information about the document being rendered</param> /// <returns>The created document expression</returns> public IDocumentExpression CreateExpression(DocumentExpressionContext context) { if (context.Body.Count > 0) { throw new DocumentRenderException("table tags can't have a body", context.ReplacementKeyToken); } TokenReader<DocumentToken> reader = new TokenReader<DocumentToken>(context.Parameters); DocumentToken variableExpressionToken; if (reader.TryAdvance(out variableExpressionToken, skipWhitespace: true) == false) { throw new DocumentRenderException("missing collection expression after table tag", context.ReplacementKeyToken); } List<DocumentToken> columns = new List<DocumentToken>(); bool showDefaults = true; bool showPossibilities = true; while(reader.CanAdvance(skipWhitespace: true)) { var nextToken = reader.Advance(skipWhitespace: true); if (nextToken.Value == "-HideDefaults") { showDefaults = false; } else if(nextToken.Value == "-HideEnumValues") { showPossibilities = false; } else { columns.Add(nextToken); } } if (columns.Count == 0) { throw new DocumentRenderException("table elements need to have at least one column parameter", context.ReplacementKeyToken); } return new TableExpression(variableExpressionToken, columns, context) { ShowDefaultValuesForArguments = showDefaults, ShowPossibleValuesForArguments = showPossibilities }; }