public MessageData Parse(TextReader reader) { this.MessageData = new MessageData(this.FileName); this.Errors = new List <Error>(); using (ReaderTextProvider textProvider = new ReaderTextProvider(reader)) { var statements = StatementParser.ParseStatements(new Position(0, 0, 0), textProvider); Message currentMessage = null; foreach (var statement in statements) { if (statement.HasError) { // Propagate the error... do we bother with the line otherwise? foreach (var token in statement.Tokens) { if (token.Errors != null) { foreach (var error in token.Errors) { this.AddError(error.Range, error.Message); } } } continue; } IList <Token <ParserTokenType> > tokens = statement.Tokens; switch (statement.StatementType) { case StatementType.MessageTypeDefiniton: Token <ParserTokenType> messageTypeName = tokens.ElementAtOrDefault(1); Token <ParserTokenType> rangeStart = tokens.ElementAtOrDefault(2); Token <ParserTokenType> rangeEnd = tokens.ElementAtOrDefault(3); System.Diagnostics.Debug.Assert(messageTypeName.TokenType == ParserTokenType.MessageTypeDefinition); System.Diagnostics.Debug.Assert(rangeStart.TokenType == ParserTokenType.MessageTypeRange); System.Diagnostics.Debug.Assert(rangeEnd.TokenType == ParserTokenType.MessageTypeRange); this.MessageData.Types.Add(new MessageType(messageTypeName.Value, int.Parse(rangeStart.Value), int.Parse(rangeEnd.Value))); break; case StatementType.Message: Token <ParserTokenType> messageTypeToken = tokens.ElementAtOrDefault(0); Token <ParserTokenType> messageName = tokens.ElementAtOrDefault(1); Token <ParserTokenType> messageIdToken = tokens.ElementAtOrDefault(2); System.Diagnostics.Debug.Assert(messageTypeToken.TokenType == ParserTokenType.MessageType); System.Diagnostics.Debug.Assert(messageName.TokenType == ParserTokenType.MessageName); System.Diagnostics.Debug.Assert(messageIdToken == null || messageIdToken.TokenType == ParserTokenType.MessageTypeRange); // Find the message type... MessageType messageType = this.MessageData.Types.FirstOrDefault(t => string.Equals(t.Name, messageTypeToken.Value, StringComparison.Ordinal)); if (messageType == null) { this.AddError( messageTypeToken, "Unknown message type '{0}'. Message types must be defined before use.", messageTypeToken.Value); } int messageId = -1; if (messageIdToken != null) { messageId = int.Parse(messageIdToken.Value); } Message existing = this.MessageData.Messages.FirstOrDefault( m => string.Equals(m.Name, messageName.Value, StringComparison.OrdinalIgnoreCase)); if (existing != null) { this.AddError( messageName, "There is already a message with name '{0}'. Message names must differ by more than just case.", existing.Name); } // create a new message... currentMessage = new Message(statement.Range.Start.Line, messageType, messageName.Value, messageId); if (statement.HasError) { string errors = string.Join(", ", statement.AllTokens.SelectMany(t => t.Errors).Select(e => e.Message)); currentMessage.Error = errors; // What about earlier this.AddError? } this.MessageData.Messages.Add(currentMessage); break; case StatementType.MessageInstance: if (currentMessage == null) { this.AddError(statement, "You must define a message before any instances."); break; } StringBuilder originalMessage = new StringBuilder(); StringBuilder processedMessage = new StringBuilder(); List <ParamData> paramList = new List <ParamData>(); int tokenIndex = 0; while (tokenIndex < tokens.Count) { Token <ParserTokenType> token = tokens[tokenIndex]; switch (token.TokenType) { case ParserTokenType.Value: originalMessage.Append(token.Value); processedMessage.Append(token.Value); ++tokenIndex; break; case ParserTokenType.Escape: originalMessage.Append(token.Value); string raw = null; switch (token.Value[1]) { case 'n': raw = "\n"; break; case 'r': raw = "\r"; break; case 't': raw = "\t"; break; case 'v': raw = "\v"; break; case '\\': raw = "\\"; break; case '{': raw = "{{"; // TODO: if there end up being no replacements, we need to remove the doubled braces! break; case '}': raw = "}}"; // TODO: if there end up being no replacements, we need to remove the doubled braces! break; default: this.AddError( token, "Unrecognized escape sequence '{0}'.", token.Value); break; } if (raw != null) { processedMessage.Append(raw); } ++tokenIndex; break; case ParserTokenType.LeftBrace: // Collect the replacement group... ++tokenIndex; List <Token <ParserTokenType> > replacementTokens = tokens.Skip(tokenIndex).TakeWhile(t => t.TokenType != ParserTokenType.RightBrace).ToList(); tokenIndex += replacementTokens.Count + 1; // skip replacement tokens and the right brace // Before parsing, we aggregate all of the original values up to the // close brace. It's just easier/cleaner that way. originalMessage.Append("{"); foreach (Token <ParserTokenType> tempToken in replacementTokens) { originalMessage.Append(tempToken.Value); } originalMessage.Append("}"); // Now get the tokens... Token <ParserTokenType> replacementType = replacementTokens.FirstOrDefault(t => t.TokenType == ParserTokenType.ReplacementType); Token <ParserTokenType> replacementPosition = replacementTokens.FirstOrDefault(t => t.TokenType == ParserTokenType.ReplacementPosition); Token <ParserTokenType> replacementName = replacementTokens.FirstOrDefault(t => t.TokenType == ParserTokenType.ReplacementName); Token <ParserTokenType> replacementAlignment = replacementTokens.FirstOrDefault(t => t.TokenType == ParserTokenType.ReplacementAlignment); Token <ParserTokenType> replacementFormat = replacementTokens.FirstOrDefault(t => t.TokenType == ParserTokenType.ReplacementFormat); if (replacementName != null) { // Now add the parsed replacement parameter info and update the strings... int paramIndex = paramList.FindIndex(p => string.Equals(p.Name, replacementName.Value, StringComparison.Ordinal)); if (paramIndex == -1) { Type type; string paramType = replacementType != null ? replacementType.Value : "string"; // Type.GetType() only finds proper .NET type names, the C# aliases like "int" and "string" aren't // understood. We do our best to find what the author is asking for... switch (paramType) { case "int": type = typeof(int); break; case "uint": type = typeof(uint); break; case "string": type = typeof(string); break; case "object": type = typeof(object); break; default: type = Type.GetType(paramType, false, true); break; } if (type == null) { // Couldn't find it? Try prefixing "System."! type = Type.GetType(string.Concat("System.", paramType), false, true); } if (type == null) { this.AddError( replacementType, "Could not parse parameter type '{0}'.", replacementType.Value); type = typeof(object); } // Get the position (if specified)... int position = -1; if (replacementPosition != null) { position = int.Parse(replacementPosition.Value); if (paramList.Any(p => p.Position == position)) { this.AddError( replacementPosition, "Replacement parameter position '{0}' is already in use.", replacementPosition.Value); position = -1; } } paramList.Add(new ParamData(replacementName.Value, type, position)); paramIndex = paramList.Count - 1; } else { // TODO:? Verify type/position match, if specified? } // Update the reformatted string (pass 1). We have to make 2 passes because we // don't necessarily know the parameter order until they've all been read! processedMessage.Append("{"); processedMessage.Append(replacementName.Value); if (replacementAlignment != null) { processedMessage.Append(","); processedMessage.Append(replacementAlignment.Value); } if (replacementFormat != null) { processedMessage.Append(":"); processedMessage.Append(replacementFormat.Value); } processedMessage.Append("}"); } break; } } // Create the instance... // Determine the final parameter order, now that we have all of the parameters. int paramCount = paramList.Count; for (int position = 0; position < paramCount; ++position) { if (paramList.FirstOrDefault(p => p.Position == position) == null) { // Update the position of the first auto-numbered parameter. var param = paramList.FirstOrDefault(p => p.Position == -1); if (param != null) { param.Position = position; } else { this.AddError( statement.Range, "There is no parameter {0}!", position); } } } // Replace the parameter names with their final position, longest parameter name first, to ensure // we don't make partial replacements! paramList.OrderByDescending(p => p.Name.Length).ForEach(p => { processedMessage.Replace(string.Concat("{", p.Name), string.Concat("{", p.Position.ToString())); }); // And finally, if there were no replacements, un-double any braces we might have created from // the "\{" and "\}" escapes... if (paramList.Count == 0) { processedMessage.Replace("{{", "{"); processedMessage.Replace("}}", "}"); } Instance instance = new Instance( statement.Range.Start.Line, originalMessage.ToString(), processedMessage.ToString(), paramList.OrderBy(p => p.Position).Select(p => new Tuple <string, Type>(p.Name, p.Type)).ToList()); if (statement.HasError) { string errors = string.Join(", ", statement.AllTokens.SelectMany(t => t.Errors).Select(e => e.Message)); instance.Error = errors; } currentMessage.Instances.Add(instance); break; case StatementType.Ignorable: // No-op! break; case StatementType.Unknown: // Error! ////this.AddError( //// this.currentToken, //// "Unexpected {0}: '{1}'.", //// this.currentToken.TokenType, //// this.currentToken.Value); default: break; } } } // Name all the instances... foreach (Message message in this.MessageData.Messages) { message.NameInstances(); } return(this.MessageData); }
/// <summary> /// Scans the given SnapshotSpan for potential matches for this classification. /// </summary> /// <param name="span">The span currently being classified</param> /// <returns>A list of ClassificationSpans that represent spans identified to be of this classification</returns> public IList <ClassificationSpan> GetClassificationSpans(SnapshotSpan span) { List <ClassificationSpan> classifications = new List <ClassificationSpan>(); try { using (ReaderTextProvider textProvider = new ReaderTextProvider(new TextSnapshotToTextReader(span.Snapshot))) { // We parse the whole buffer for statements, not just the current line, // because we need to know if we're on a continuation line. // TODO: Cache the "beginning of logical line" points so that we // don't have to re-parse everything each time? IEnumerable <Statement <StatementType, ParserTokenType> > statements = null; switch (this.language) { case ParserLanguage.Rtype: statements = RtypeStatementParser.ParseStatements(new Position(0, 0, 0), textProvider); break; case ParserLanguage.Xml: statements = XmlStatementParser.ParseStatements(new Position(0, 0, 0), textProvider); break; } int spanStart = span.Start.Position; int spanEnd = span.End.Position; foreach (var statement in statements) { if (statement.Range.End.Offset <= spanStart) { continue; } if (statement.Range.Start.Offset >= spanEnd) { break; } // for a comment statement, we don't have to look at the // individual tokens... if (statement.StatementType == StatementType.Comment) { var classification = this.commentType; // Ensure the returned span doesn't extend past the request! var classifiedSpan = span.Intersection(span.Snapshot.CreateSpanFromSwix(statement.Range)); if (classifiedSpan.HasValue) { classifications.Add(new ClassificationSpan(classifiedSpan.Value, classification)); } } else { foreach (var token in statement.AllTokens) { if (token.Range.End.Offset <= span.Start.Position) { continue; } if (token.Range.Start.Offset >= span.End.Position) { break; } IClassificationType classification = null; switch (token.TokenType) { case ParserTokenType.Unknown: classification = this.delimiterType; break; case ParserTokenType.Whitespace: classification = this.whitespaceType; break; case ParserTokenType.Comment: classification = this.commentType; break; case ParserTokenType.UseKeyword: classification = this.keywordType; break; case ParserTokenType.Object: classification = this.objectType; break; case ParserTokenType.PropertyName: classification = this.propertyType; break; case ParserTokenType.Equals: classification = this.assignmentType; break; case ParserTokenType.PropertyValue: case ParserTokenType.DoubleQuote: case ParserTokenType.SingleQuote: classification = this.valueType; break; case ParserTokenType.NamespacePrefix: classification = this.namespacePrefixType; break; case ParserTokenType.NamespacePrefixDeclaration: // TODO break; case ParserTokenType.NamespaceDeclaration: // TODO break; case ParserTokenType.AttachedPropertyObject: classification = this.attachableObjectType; break; case ParserTokenType.LeftAngle: case ParserTokenType.RightAngle: case ParserTokenType.Colon: case ParserTokenType.Slash: case ParserTokenType.Period: classification = this.delimiterType; break; default: break; } if (classification != null) { // Ensure the returned span doesn't extend past the request! var classifiedSpan = span.Intersection(span.Snapshot.CreateSpanFromSwix(token.Range)); if (classifiedSpan.HasValue) { classifications.Add(new ClassificationSpan(classifiedSpan.Value, classification)); } } } } } } } catch (Exception) { } return(classifications); }