Free cookie consent management tool by TermsFeed Policy Generator

source: branches/CodeEditor/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory-5.5.0/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs @ 11700

Last change on this file since 11700 was 11700, checked in by jkarder, 9 years ago

#2077: created branch and added first version

File size: 9.8 KB
Line 
1//
2// CompositeFormatStringParser.cs
3//
4// Authors:
5//   Simon Lindgren <simon.n.lindgren@gmail.com>
6//
7// Copyright (c) 2012 Simon Lindgren
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy
10// of this software and associated documentation files (the "Software"), to deal
11// in the Software without restriction, including without limitation the rights
12// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13// copies of the Software, and to permit persons to whom the Software is
14// furnished to do so, subject to the following conditions:
15//
16// The above copyright notice and this permission notice shall be included in
17// all copies or substantial portions of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25// THE SOFTWARE.
26using System;
27using System.Collections.Generic;
28using System.Linq;
29
30namespace ICSharpCode.NRefactory.Utils
31{
32  /// <summary>
33  /// Composite format string parser.
34  /// </summary>
35  /// <remarks>
36  /// Implements a complete parser for valid strings as well as
37  /// error reporting and best-effort parsing for invalid strings.
38  /// </remarks>   
39  public class CompositeFormatStringParser
40  {
41
42    public CompositeFormatStringParser ()
43    {
44      errors = new List<IFormatStringError> ();
45    }
46
47    /// <summary>
48    /// Parse the specified format string.
49    /// </summary>
50    /// <param name='format'>
51    /// The format string.
52    /// </param>
53    public FormatStringParseResult Parse (string format)
54    {
55      if (format == null)
56        throw new ArgumentNullException ("format");
57
58      var result = new FormatStringParseResult();
59
60      // Format string syntax: http://msdn.microsoft.com/en-us/library/txafckwd.aspx
61      int textStart = 0;
62      var length = format.Length;
63      for (int i = 0; i < length; i++) {
64        // Get fixed text
65        GetText (format, ref i);
66
67        if (i < format.Length && format [i] == '{') {
68          int formatItemStart = i;
69          int index;
70          int? alignment;
71          string argumentFormat;
72          var textSegmentErrors = new List<IFormatStringError>(GetErrors());
73
74          // Try to parse the parts of the format item
75          ++i;
76          index = ParseIndex (format, ref i);
77          CheckForMissingEndBrace (format, i, length);
78
79          alignment = ParseAlignment (format, ref i, length);
80          CheckForMissingEndBrace (format, i, length);
81
82          argumentFormat = ParseSubFormatString (format, ref i, length);
83          CheckForMissingEndBrace (format, i, length);
84
85          // Check what we parsed
86          if (i == formatItemStart + 1 && (i == length || (i < length && format[i] != '}'))) {
87            // There were no format item after all, this was just an
88            // unescaped left brace, or the initial brace of an escape sequence
89            SetErrors(textSegmentErrors);
90            if (i >= length || format[i] != '{') {
91              AddError (new DefaultFormatStringError {
92                Message = "Unescaped '{'",
93                StartLocation = formatItemStart,
94                EndLocation = formatItemStart + 1,
95                OriginalText = "{",
96                SuggestedReplacementText = "{{"
97              });
98            }
99            continue;
100          }
101
102          if (formatItemStart - textStart > 0) {
103            // We have parsed a format item, end the text segment
104            var textSegment = new TextSegment (UnEscape (format.Substring (textStart, formatItemStart - textStart)));
105            textSegment.Errors = textSegmentErrors;
106            result.Segments.Add (textSegment);
107          }
108         
109          // Unclosed format items in fixed text advances i one step too far
110          if (i < length && format [i] != '}')
111            --i;
112
113          // i may actually point outside of format if there is a syntactical error
114          // if that happens, we want the last position
115          var endLocation = Math.Min (length, i + 1);
116          result.Segments.Add (new FormatItem (index, alignment, argumentFormat) {
117            StartLocation = formatItemStart,
118            EndLocation = endLocation,
119            Errors = GetErrors ()
120          });
121          ClearErrors ();
122
123          // The next potential text segment starts after this format item
124          textStart = i + 1;
125        }
126      }
127      // Handle remaining text
128      if (textStart < length) {
129        var textSegment = new TextSegment (UnEscape (format.Substring (textStart)), textStart);
130        textSegment.Errors = GetErrors();
131        result.Segments.Add (textSegment);
132
133      }
134      return result;
135    }
136
137    int ParseIndex (string format, ref int i)
138    {
139      int parsedCharacters;
140      int? maybeIndex = GetAndCheckNumber (format, ",:}", ref i, i, out parsedCharacters);
141      if (parsedCharacters == 0) {
142        AddError (new DefaultFormatStringError {
143          StartLocation = i,
144          EndLocation = i,
145          Message = "Missing index",
146          OriginalText = "",
147          SuggestedReplacementText = "0"
148        });
149      }
150      return maybeIndex ?? 0;
151    }
152
153    int? ParseAlignment(string format, ref int i, int length)
154    {
155      if (i < length && format [i] == ',') {
156        int alignmentBegin = i;
157        ++i;
158        while (i < length && char.IsWhiteSpace(format [i]))
159          ++i;
160
161        int parsedCharacters;
162        var number = GetAndCheckNumber (format, ",:}", ref i, alignmentBegin + 1, out parsedCharacters);
163        if (parsedCharacters == 0) {
164          AddError (new DefaultFormatStringError {
165            StartLocation = i,
166            EndLocation = i,
167            Message = "Missing alignment",
168            OriginalText = "",
169            SuggestedReplacementText = "0"
170          });
171        }
172        return number ?? 0;
173      }
174      return null;
175    }
176
177    string ParseSubFormatString(string format, ref int i, int length)
178    {
179      if (i < length && format [i] == ':') {
180        ++i;
181        int begin = i;
182        GetText(format, ref i, "", true);
183        var escaped = format.Substring (begin, i - begin);
184        return UnEscape (escaped);
185      }
186      return null;
187    }
188
189    void CheckForMissingEndBrace (string format, int i, int length)
190    {
191      if (i == length) {
192        int j;
193        for (j = i - 1; format[j] == '}'; j--);
194        var oddEndBraceCount = (i - j) % 2 == 1;
195        if (oddEndBraceCount) {
196          AddMissingEndBraceError(i, i, "Missing '}'", "");
197        }
198        return;
199      }
200      return;
201    }
202   
203    void GetText (string format, ref int index, string delimiters = "", bool allowEscape = false)
204    {
205      while (index < format.Length) {
206        if (format [index] == '{' || format[index] == '}') {
207          if (index + 1 < format.Length && format [index + 1] == format[index] && allowEscape)
208            ++index;
209          else
210            break;
211        } else if (delimiters.Contains(format[index].ToString())) {
212          break;
213        }
214        ++index;
215      };
216    }
217   
218    int? GetNumber (string format, ref int index)
219    {
220      if (format.Length == 0) {
221        return null;
222      }
223      int sum = 0;
224      int i = index;
225      bool positive = format [i] != '-';
226      if (!positive)
227        ++i;
228      int numberStartIndex = i;
229      while (i < format.Length && format[i] >= '0' && format[i] <= '9') {
230        sum = 10 * sum + format [i] - '0';
231        ++i;
232      }
233      if (i == numberStartIndex)
234        return null;
235
236      index = i;
237      return positive ? sum : -sum;
238    }
239
240    int? GetAndCheckNumber (string format, string delimiters, ref int index, int numberFieldStart, out int parsedCharacters)
241    {
242      int fieldIndex = index;
243      GetText (format, ref fieldIndex, delimiters);
244      int fieldEnd = fieldIndex;
245      var numberText = format.Substring(index, fieldEnd - index);
246      parsedCharacters = numberText.Length;
247      int numberLength = 0;
248      int? number = GetNumber (numberText, ref numberLength);
249      if (numberLength != parsedCharacters && fieldEnd < format.Length && delimiters.Contains (format [fieldEnd])) {
250        // Not the entire number field could be parsed
251        // The field actually ended as intended, so set the index to the end of the field
252        index = fieldEnd;
253        var suggestedNumber = (number ?? 0).ToString ();
254        AddInvalidNumberFormatError (numberFieldStart, format.Substring (numberFieldStart, index - numberFieldStart), suggestedNumber);
255      } else {
256        var endingChar = index + numberLength;
257        if (numberLength != parsedCharacters) {
258          // Not the entire number field could be parsed
259          // The field didn't end, it was cut off so we are missing an ending brace
260          index = endingChar;
261          AddMissingEndBraceError (index, index, "Missing ending '}'", "");
262        } else {
263          index = endingChar;
264        }
265      }
266      return number;
267    }
268
269    public static string UnEscape (string unEscaped)
270    {
271      return unEscaped.Replace ("{{", "{").Replace ("}}", "}");
272    }
273
274    IList<IFormatStringError> errors;
275   
276    bool hasMissingEndBrace = false;
277
278    void AddError (IFormatStringError error)
279    {
280      errors.Add (error);
281    }
282
283    void AddMissingEndBraceError(int start, int end, string message, string originalText)
284    {
285      // Only add a single missing end brace per format item
286      if (hasMissingEndBrace)
287        return;
288      AddError (new DefaultFormatStringError {
289        StartLocation = start,
290        EndLocation = end,
291        Message = message,
292        OriginalText = originalText,
293        SuggestedReplacementText = "}"
294      });
295      hasMissingEndBrace = true;
296    }
297
298    void AddInvalidNumberFormatError (int i, string number, string replacementText)
299    {
300      AddError (new DefaultFormatStringError {
301        StartLocation = i,
302        EndLocation = i + number.Length,
303        Message = string.Format ("Invalid number '{0}'", number),
304        OriginalText = number,
305        SuggestedReplacementText = replacementText
306      });
307    }
308
309    IList<IFormatStringError> GetErrors ()
310    {
311      return errors;
312    }
313   
314    void SetErrors (IList<IFormatStringError> errors)
315    {
316      this.errors = errors;
317    }
318
319    void ClearErrors ()
320    {
321      hasMissingEndBrace = false;
322      errors = new List<IFormatStringError> ();
323    }
324  }
325
326  public class FormatStringParseResult
327  {
328    public FormatStringParseResult()
329    {
330      Segments = new List<IFormatStringSegment>();
331    }
332
333    public IList<IFormatStringSegment> Segments { get; private set; }
334
335    public bool HasErrors
336    {
337      get {
338        return Segments.SelectMany(segment => segment.Errors).Any();
339      }
340    }
341  }
342}
343
Note: See TracBrowser for help on using the repository browser.