From 101294e3943464fb829c3ea33afe95550d479c73 Mon Sep 17 00:00:00 2001 From: Simon Lindgren Date: Sat, 7 Jul 2012 17:12:10 +0200 Subject: [PATCH] [Utils] CompositeFormatStringParser: More error handling + refactoring of the code. --- .../CompositeFormatStringParserTests.cs | 35 +++- .../CompositeFormatStringParser.cs | 157 +++++++++--------- 2 files changed, 109 insertions(+), 83 deletions(-) diff --git a/ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs b/ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs index feb4aaf3aa..590c8576e5 100644 --- a/ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs +++ b/ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs @@ -170,6 +170,15 @@ namespace ICSharpCode.NRefactory.Utils ErrorTest(errors[0], "{", "{{", 0, 1); } + [Test] + public void UnescapedOpenBracesInFixedText() + { + var segments = ParseTest("a { { a", new TextSegment("a { { a")); + var errors = SegmentTest(2, segments.First()); + ErrorTest(errors[0], "{", "{{", 2, 3); + ErrorTest(errors[1], "{", "{{", 4, 5); + } + [Test] public void UnescapedLoneEndingBrace() { @@ -193,9 +202,10 @@ namespace ICSharpCode.NRefactory.Utils { var segments = ParseTest("Some text {0,", new TextSegment("Some text "), - new FormatItem(0) { StartLocation = 10, EndLocation = 13 }); - var errors = SegmentTest(1, segments.Skip(1).First()); - ErrorTest(errors[0], ",", "}", 12, 13); + new FormatItem(0, 0) { StartLocation = 10, EndLocation = 13 }); + var errors = SegmentTest(2, segments.Skip(1).First()); + ErrorTest(errors[0], "", "0", 13, 13); + ErrorTest(errors[1], "", "}", 13, 13); } [Test] @@ -203,9 +213,10 @@ namespace ICSharpCode.NRefactory.Utils { var segments = ParseTest("Some text {0, ", new TextSegment("Some text "), - new FormatItem(0) { StartLocation = 10, EndLocation = 16 }); - var errors = SegmentTest(1, segments.Skip(1).First()); - ErrorTest(errors[0], ", ", "}", 12, 16); + new FormatItem(0, 0) { StartLocation = 10, EndLocation = 16 }); + var errors = SegmentTest(2, segments.Skip(1).First()); + ErrorTest(errors[0], "", "0", 16, 16); + ErrorTest(errors[1], "", "}", 16, 16); } [Test] @@ -321,11 +332,19 @@ namespace ICSharpCode.NRefactory.Utils var segments = ParseTest("Text {0 Text {1}", new TextSegment("Text "), new FormatItem(0) { StartLocation = 5, EndLocation = 7 }, - new TextSegment(" Text ", 7), - new FormatItem(1) { StartLocation = 13, EndLocation = 16 }); + new TextSegment(" Text ", 7), + new FormatItem(1) { StartLocation = 13, EndLocation = 16 }); var errors = SegmentTest(1, segments.Skip(1).First()); ErrorTest(errors[0], "", "}", 7, 7); } + + [Test] + public void EndWithEscapedBrace() + { + var segments = ParseTest("{0:}}", new FormatItem(0, null, "}") { StartLocation = 0, EndLocation = 5 }); + var errors = SegmentTest(1, segments.First()); + ErrorTest(errors[0], "", "}", 5, 5); + } } } diff --git a/ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs b/ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs index d4de9d12a4..8581270a5a 100644 --- a/ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs +++ b/ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs @@ -25,7 +25,6 @@ // THE SOFTWARE. using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; namespace ICSharpCode.NRefactory.Utils @@ -59,76 +58,74 @@ namespace ICSharpCode.NRefactory.Utils var result = new FormatStringParseResult(); // Format string syntax: http://msdn.microsoft.com/en-us/library/txafckwd.aspx - int start = 0; + int textStart = 0; var length = format.Length; for (int i = 0; i < length; i++) { - if (format [i] == '{') { - if (i + 1 == length) { - // This is the end of the string. - var textSegment = new TextSegment (format.Substring (start, i - start + 1), start) { - Errors = { - new DefaultFormatStringError { - StartLocation = i, - EndLocation = i + 1, - Message = "Curly braces need to be escaped", - OriginalText = "{", - SuggestedReplacementText = "{{" - } - } - }; - result.Segments.Add(textSegment); - return result; - } else if (format [i + 1] == '{') { - // Escape sequence; we're still in a text segment - // Skip ahead to the char after the escape sequence - ++i; - continue; - } else { - // This is the end of the text segment and the start of a FormatItem - if (i - start > 0) { - result.Segments.Add(new TextSegment (UnEscape (format.Substring (start, i - start)))); - start = i; - } - } + // Get fixed text + GetText (format, ref i); + if (i < format.Length && format [i] == '{') { + int formatItemStart = i; int index; int? alignment = null; string argumentFormat = null; + var textSegmentErrors = new List(GetErrors()); - // Index + // Try to parse the parts of the format item ++i; - index = ParseIndex(format, ref i); + index = ParseIndex (format, ref i); CheckForMissingEndBrace (format, i, length); - // Alignment - alignment = ParseAlignment(format, ref i, length); + alignment = ParseAlignment (format, ref i, length); CheckForMissingEndBrace (format, i, length); - // Format string - argumentFormat = ParseSubFormatString(format, ref i, length); + argumentFormat = ParseSubFormatString (format, ref i, length); CheckForMissingEndBrace (format, i, length); - // Handle unclosed format items in the middle of fixed text - if (i < length && format[i] != '}') + // Check what we parsed + if (i == formatItemStart + 1 && (i == length || (i < length && format [i] != '}'))) { + // There were no format item after all, this was just an + // unescaped left brace + SetErrors(textSegmentErrors); + AddError (new DefaultFormatStringError { + Message = "Unescaped '{'", + StartLocation = formatItemStart, + EndLocation = formatItemStart + 1, + OriginalText = "{", + SuggestedReplacementText = "{{" + }); + continue; + } else if (formatItemStart - textStart > 0) { + // We have parsed a format item, end the text segment + var textSegment = new TextSegment (UnEscape (format.Substring (textStart, formatItemStart - textStart))); + textSegment.Errors = textSegmentErrors; + result.Segments.Add (textSegment); + } + + // Unclosed format items in fixed text gets advances i one step too far + if (i < length && format [i] != '}') --i; - // i may actually point outside of format; if that happens, we want the last position + // i may actually point outside of format if there is a syntactical error + // if that happens, we want the last position var endLocation = Math.Min (length, i + 1); - var errors = GetErrors (); - result.Segments.Add(new FormatItem (index, alignment, argumentFormat) { - StartLocation = start, + result.Segments.Add (new FormatItem (index, alignment, argumentFormat) { + StartLocation = formatItemStart, EndLocation = endLocation, - Errors = errors + Errors = GetErrors () }); ClearErrors (); // The next potential text segment starts after this format item - start = i + 1; + textStart = i + 1; } } // Handle remaining text - if (start < length) { - result.Segments.Add(new TextSegment (UnEscape (format.Substring (start)), start)); + if (textStart < length) { + var textSegment = new TextSegment (UnEscape (format.Substring (textStart)), textStart); + textSegment.Errors = GetErrors(); + result.Segments.Add (textSegment); + } return result; } @@ -156,24 +153,19 @@ namespace ICSharpCode.NRefactory.Utils ++i; while (i < length && char.IsWhiteSpace(format [i])) ++i; - if (i == length) { - var originalText = format.Substring (alignmentBegin); - var message = string.Format ("Unexpected end of string: '{0}'", originalText); - AddMissingEndBraceError(alignmentBegin, i, message, originalText); - } else { - int parsedCharacters; - var number = GetAndCheckNumber(format, ",:}", ref i, alignmentBegin + 1, out parsedCharacters); - if (parsedCharacters == 0) { - AddError (new DefaultFormatStringError { - StartLocation = i, - EndLocation = i, - Message = "Missing alignment", - OriginalText = "", - SuggestedReplacementText = "0" - }); - } - return number ?? 0; + + int parsedCharacters; + var number = GetAndCheckNumber (format, ",:}", ref i, alignmentBegin + 1, out parsedCharacters); + if (parsedCharacters == 0) { + AddError (new DefaultFormatStringError { + StartLocation = i, + EndLocation = i, + Message = "Missing alignment", + OriginalText = "", + SuggestedReplacementText = "0" + }); } + return number ?? 0; } return null; } @@ -183,7 +175,8 @@ namespace ICSharpCode.NRefactory.Utils if (i < length && format [i] == ':') { ++i; int begin = i; - while (i < length) { + GetText(format, ref i); + /*while (i < length) { char c = format [i]; if (c != '}') { ++i; @@ -197,7 +190,7 @@ namespace ICSharpCode.NRefactory.Utils // This is the end of the FormatItem break; } - } + }*/ var escaped = format.Substring (begin, i - begin); return UnEscape (escaped); } @@ -206,23 +199,31 @@ namespace ICSharpCode.NRefactory.Utils void CheckForMissingEndBrace (string format, int i, int length) { - if (i == length && format [length - 1] != '}') { - AddMissingEndBraceError(i, i, "Missing '}'", ""); + if (i == length) {// && format [length - 1] != '}') { + int j; + for (j = i - 1; format[j] == '}'; j--); + var oddEndBraceCount = (i - j) % 2 == 1; + if (oddEndBraceCount) { + AddMissingEndBraceError(i, i, "Missing '}'", ""); + } return; } return; } - string GetText (string format, string delimiters, ref int index) + void GetText (string format, ref int index, string delimiters = "") { - int start = index; - while (index < format.Length && !delimiters.Contains(format[index].ToString())) { - if (format[index] == '{' && (index + 1 < format.Length && format[index + 1] != '{')) + while (index < format.Length) {// && !delimiters.Contains(format[index].ToString())) { + if (format [index] == '{' || format[index] == '}') { + if (index + 1 < format.Length && format [index + 1] == format[index]) + ++index; + else + break; + } else if (delimiters.Contains(format[index].ToString())) { break; + } ++index; - } - - return format.Substring (start, index - start); + }; } int? GetNumber (string format, ref int index) @@ -250,8 +251,9 @@ namespace ICSharpCode.NRefactory.Utils int? GetAndCheckNumber (string format, string delimiters, ref int index, int numberFieldStart, out int parsedCharacters) { int fieldIndex = index; - var numberText = GetText (format, delimiters, ref fieldIndex); + GetText (format, ref fieldIndex, delimiters); int fieldEnd = fieldIndex; + var numberText = format.Substring(index, fieldEnd - index); parsedCharacters = numberText.Length; int numberLength = 0; int? number = GetNumber (numberText, ref numberLength); @@ -317,6 +319,11 @@ namespace ICSharpCode.NRefactory.Utils { return errors; } + + void SetErrors (IList errors) + { + this.errors = errors; + } void ClearErrors () {