Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Persistence/3.3/Auxiliary/TypeNameParser.cs @ 9005

Last change on this file since 9005 was 9005, checked in by epitzer, 12 years ago

#1990 Enable correct parsing of generics and nested classes.

File size: 14.1 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Linq;
24using System.Collections.Generic;
25using System.Text;
26using System.Text.RegularExpressions;
27
28namespace HeuristicLab.Persistence.Auxiliary {
29
30  /// <summary>
31  /// Error during type name parsing, thrown by <see cref="TypeNameParser"/>.
32  /// </summary>
33  public class ParseError : Exception {
34
35    /// <summary>
36    /// Initializes a new instance of the <see cref="ParseError"/> class.
37    /// </summary>
38    /// <param name="message">The message.</param>
39    public ParseError(string message) : base(message) { }
40  }
41
42  /// <summary>
43  /// Parse a .NET type name using the following grammar: 
44  ///   
45  /// <code>
46  /// TypeSpec := SimpleTypeSpec '&amp;'? 
47  ///
48  /// SimpleTypeSpec := (IDENTIFIER '.')*
49  ///                   (IDENTIFIER '`\d+'? '+')*
50  ///                    IDENTIFIER
51  ///                   ( '`\d+[' Generics ']' )?
52  ///                   (\*|\[(\d+\.\.\d+|\d+\.\.\.|(|\*)(,(|\*))*)\])*
53  ///                   (',\s*' IDENTIFIER (',\s*' AssemblyProperty)* )? 
54  ///
55  /// Generics := '[' SimpleTypeSpec ']' (',[' SimpleTypeSpec ']')
56  ///
57  /// AssemblyProperty := 'Version=' Version
58  ///                  |  'PublicKey(Token)?=[a-fA-F0-9]+'
59  ///                  |  'Culture=[a-zA-F0-9]+'
60  ///
61  /// Version := \d+\.\d+\.\d+\.\d+
62  ///
63  /// IDENTIFIER = [_a-zA-Z][_a-ZA-Z0-9]* 
64  /// </code>
65  /// </summary>
66  public class TypeNameParser {
67
68    /*
69      TypeSpec := SimpleTypeSpec '&'?
70
71      SimpleTypeSpec := (IDENTIFIER '.')*
72                        (IDENTIFIER '`\d+'?
73                        ( '+' IDENTIFIER '`\d+'? )*
74                        ( '[' Generics ']' )?
75                        (\*|\[(\d+\.\.\d+|\d+\.\.\.|(|\*)(,(|\*))*)\])*
76                        (',\s*' IDENTIFIER (',\s*' AssemblyProperty)* )?
77
78      Generics := '[' SimpleTypeSpec ']' (',[' SimpleTypeSpec ']')*
79
80      AssemblyProperty := 'Version=' Version
81                 |  'PublicKey(Token)?=[a-fA-F0-9]+'
82                 |  'Culture=[a-zA-F0-9]+'
83
84      Version := \d+\.\d+\.\d+\.\d+
85
86      IDENTIFIER = [_a-zA-Z][_a-ZA-Z0-9]*
87    */
88
89
90    private class Token {
91      public enum Symbol {None, Dash, Ampersand, Dot, Plus, Comma, OpenBracket, CloseBracket, Asterisk, Space, Equals, Backtick}
92      private static readonly Dictionary<string, Symbol> TOKENS =
93        new Dictionary<string, Symbol> {
94          {"-", Symbol.Dash},
95          {"&", Symbol.Ampersand},
96          {".", Symbol.Dot},
97          {"+", Symbol.Plus},
98          {",", Symbol.Comma},
99          {"[", Symbol.OpenBracket},
100          {"]", Symbol.CloseBracket},
101          {"*", Symbol.Asterisk},
102          {" ", Symbol.Space},
103          {"=", Symbol.Equals},
104          {"`", Symbol.Backtick}};
105      private static readonly Regex NumberRegex = new Regex("^\\d+$");
106      private static readonly Regex IdentifierRegex = new Regex("^[_a-zA-Z][_a-zA-Z0-9]*$");
107      private static readonly Regex TokenRegex = new Regex("[-&.+,\\[\\]* =`]|[a-f0-9]+|\\d+|[_a-zA-Z][_a-zA-Z0-9]*");
108      public Symbol Name { get; private set; }
109      public string Value { get; private set; }
110      public bool IsIdentifier { get; private set; }
111      public int? Number { get; private set; }
112      public int Position { get; private set; }
113      private Token(string value, int pos) {
114        Position = pos;
115        Name = Symbol.None;
116        if (TOKENS.ContainsKey(value)) {
117          Name = TOKENS[value];
118        } else if (NumberRegex.IsMatch(value)) {
119          Number = int.Parse(value);
120        } else {
121          Value = value;
122          IsIdentifier = IdentifierRegex.IsMatch(value);
123        }
124      }
125      public static IEnumerable<Token> Tokenize(string s) {
126        int pos = 0;
127        foreach (Match m in TokenRegex.Matches(s)) {
128          yield return new Token(m.Value, pos);
129          pos += m.Length;
130        }
131      }
132      public override string ToString() {
133        if (Name != Symbol.None)
134          return "Token(" + Name + ")";
135        if (Value != null)
136          return "\"" + Value + "\"";
137        if (Number != null)
138          return Number.ToString();
139        return "<empty>";
140      }
141    }
142
143    private Queue<Token> tokens;
144    private int Position {
145      get {
146        if (tokens.Count == 0)
147          return -1;
148        else
149          return tokens.Peek().Position;
150      }
151    }
152
153    private TypeNameParser(string s) {
154      tokens = new Queue<Token>(Token.Tokenize(s));
155    }
156
157    /// <summary>
158    /// Parses the specified typename string as obtained by
159    /// <c>System.Object.GetType().FullName"</c>.
160    /// </summary>
161    /// <param name="s">The typename string.</param>
162    /// <returns>A <see cref="TypeName"/> representing the type name.</returns>
163    public static TypeName Parse(string s) {
164      TypeNameParser p = new TypeNameParser(s);
165      try {
166        return p.TransformTypeSpec();
167      }
168      catch (ParseError x) {
169        if (p.Position > 0)
170          throw new ParseError(String.Format(
171            "Could not parse typename: {0}\n\"{1}====>{2}<===={3}",
172            x.Message,
173            s.Substring(0, p.Position),
174            s[p.Position],
175            s.Substring(p.Position, s.Length - p.Position)));
176        else
177          throw new ParseError(String.Format(
178            "Could not parse typenname \"{0}\" at end of input: {1}",
179            s,
180            x.Message));
181      }
182    }
183
184    private TypeName TransformTypeSpec() {
185      TypeName t = TransformSimpleTypeSpec();
186      t.IsReference = ConsumeToken(Token.Symbol.Ampersand);
187      return t;
188    }
189
190    private TypeName TransformSimpleTypeSpec() {
191      var nameSpace = new List<string> {ConsumeIdentifier()};
192      while (ConsumeToken(Token.Symbol.Dot))
193        nameSpace.Add(ConsumeIdentifier());
194      var className = new List<string>();
195      if (nameSpace.Count > 0) {
196        className.Add(nameSpace[nameSpace.Count - 1]);
197        nameSpace.RemoveAt(nameSpace.Count - 1);
198      }
199      var genericArgCounts = new List<int> {
200        ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0
201      };
202      while(ConsumeToken(Token.Symbol.Plus)) {
203        className.Add(ConsumeIdentifier());
204        genericArgCounts.Add(ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0);
205      }
206      var nGenericArgs = genericArgCounts.Sum();
207      var typeName = new TypeName(
208        string.Join(".", nameSpace.ToArray()),
209        string.Join("+", className.ToArray()),
210        nGenericArgs > genericArgCounts.Last() ? genericArgCounts : null);
211      if (nGenericArgs > 0 && ConsumeToken(Token.Symbol.OpenBracket, true)) {
212        typeName.GenericArgs.AddRange(TransformGenerics());
213        ConsumeToken(Token.Symbol.CloseBracket, true);
214      }
215      var pointerOrArray = new StringBuilder();
216      while (true) {
217        if (ConsumeToken(Token.Symbol.Asterisk)) {
218          pointerOrArray.Append("*");
219        } else if (ConsumeToken(Token.Symbol.OpenBracket)) {
220          pointerOrArray.Append('[');
221          ParseDimension(pointerOrArray);
222          while (ConsumeToken(Token.Symbol.Comma)) {
223            pointerOrArray.Append(",");
224            ParseDimension(pointerOrArray);
225          }
226          ConsumeToken(Token.Symbol.CloseBracket, true);
227          pointerOrArray.Append(']');
228        } else {
229          break;
230        }
231      }
232      typeName.MemoryMagic = pointerOrArray.ToString();
233      if (ConsumeComma()) {
234        var sb = new StringBuilder();
235        sb.Append(ConsumeIdentifier());
236        while (CanConsumeToken(Token.Symbol.Dot) ||
237          CanConsumeToken(Token.Symbol.Dash) ||
238          CanConsumeNumber() ||
239          CanConsumeIdentifier()) {
240          if (ConsumeToken(Token.Symbol.Dot))
241            sb.Append('.');
242          else if (ConsumeToken(Token.Symbol.Dash))
243            sb.Append('-');
244          else if (CanConsumeNumber())
245            sb.Append(ConsumeNumber());
246          else
247            sb.Append(ConsumeIdentifier());
248        }
249        typeName.AssemblyName = sb.ToString();
250        while (ConsumeComma()) {
251          KeyValuePair<string, string> property =
252            TransformAssemblyProperty();
253          typeName.AssemblyAttribues.Add(property.Key, property.Value);
254        }
255      }
256      return typeName;
257    }
258
259    private void ParseDimension(StringBuilder sb) {
260      if (ConsumeToken(Token.Symbol.Asterisk)) {
261        sb.Append("*");
262      } else if (CanConsumeNumber()) {
263        sb.Append(ConsumeNumber());
264        ConsumeToken(Token.Symbol.Dot, true);
265        ConsumeToken(Token.Symbol.Dot, true);
266        if (ConsumeToken(Token.Symbol.Dot)) {
267          sb.Append("...");
268        } else {
269          sb.Append("..").Append(ConsumeNumber());
270        }
271      }
272    }
273
274    private IEnumerable<TypeName> TransformGenerics() {
275      ConsumeToken(Token.Symbol.OpenBracket, true);
276      yield return TransformSimpleTypeSpec();
277      ConsumeToken(Token.Symbol.CloseBracket, true);
278      while (ConsumeToken(Token.Symbol.Comma)) {
279        ConsumeToken(Token.Symbol.OpenBracket, true);
280        yield return TransformSimpleTypeSpec();
281        ConsumeToken(Token.Symbol.CloseBracket, true);
282      }
283    }
284
285    private KeyValuePair<string, string> TransformAssemblyProperty() {
286      if (ConsumeIdentifier("Version")) {
287        ConsumeToken(Token.Symbol.Equals, true);
288        return new KeyValuePair<string, string>(
289          "Version",
290          TransformVersion());
291      } else if (ConsumeIdentifier("PublicKey")) {
292        return ConsumeAssignment("PublicKey");
293      } else if (ConsumeIdentifier("PublicKeyToken")) {
294        return ConsumeAssignment("PublicKeyToken");
295      } else if (ConsumeIdentifier("Culture")) {
296        return ConsumeAssignment("Culture");
297      } else if (ConsumeIdentifier("Custom")) {
298        return ConsumeAssignment("Custom");
299      } else {
300        throw new ParseError(String.Format(
301          "Invalid assembly property \"{0}\"",
302          tokens.Peek().ToString()));
303      }
304    }
305
306    private KeyValuePair<string, string> ConsumeAssignment(string name) {
307      ConsumeToken(Token.Symbol.Equals, true);
308      return new KeyValuePair<string, string>(name, ConsumeToken());
309    }
310
311    private string TransformVersion() {
312      StringBuilder version = new StringBuilder();
313      version.Append(ConsumeNumber());
314      ConsumeToken(Token.Symbol.Dot);
315      version.Append('.').Append(ConsumeNumber());
316      ConsumeToken(Token.Symbol.Dot);
317      version.Append('.').Append(ConsumeNumber());
318      ConsumeToken(Token.Symbol.Dot);
319      version.Append('.').Append(ConsumeNumber());
320      return version.ToString();
321    }
322
323    private bool CanConsumeNumber() {
324      if (tokens.Count == 0)
325        return false;
326      return tokens.Peek().Number != null;
327    }
328
329    private int ConsumeNumber() {
330      if (tokens.Count == 0)
331        throw new ParseError("End of input while expecting number");
332      if (tokens.Peek().Number != null)
333        return (int)tokens.Dequeue().Number;
334      throw new ParseError(string.Format(
335        "Number expected, found \"{0}\" instead",
336        tokens.Peek().ToString()));
337    }
338
339    private bool ConsumeIdentifier(string value) {
340      if (tokens.Count == 0)
341        return false;
342      if (tokens.Peek().Value == value && tokens.Peek().IsIdentifier) {
343        tokens.Dequeue();
344        return true;
345      } else {
346        return false;
347      }
348    }
349
350    private bool CanConsumeIdentifier() {
351      return tokens.Count > 0 && tokens.Peek().Value != null;
352    }
353
354    private string ConsumeIdentifier() {
355      if (tokens.Count == 0)
356        throw new ParseError("End of input while expecting identifier");
357      if (tokens.Peek().Value != null && tokens.Peek().IsIdentifier)
358        return tokens.Dequeue().Value;
359      throw new ParseError(String.Format(
360        "Identifier expected, found \"{0}\" instead",
361        tokens.Peek().ToString()));
362    }
363
364    private string ConsumeToken() {
365      if (tokens.Count == 0)
366        throw new ParseError("End of input while expecting token");
367      if (tokens.Peek().Value != null)
368        return tokens.Dequeue().Value;
369      throw new ParseError(String.Format(
370        "Token expected, found \"{0}\" instead",
371        tokens.Peek().ToString()));
372    }
373
374    private bool ConsumeComma() {
375      if (ConsumeToken(Token.Symbol.Comma)) {
376        while (ConsumeToken(Token.Symbol.Space)) ;
377        return true;
378      } else {
379        return false;
380      }
381    }
382
383    private bool ConsumeToken(Token.Symbol symbol) {
384      return ConsumeToken(symbol, false);
385    }
386
387    private bool CanConsumeToken(Token.Symbol symbol) {
388      if (tokens.Count == 0)
389        return false;
390      if (tokens.Peek().Name == symbol)
391        return true;
392      return false;
393    }
394
395    private bool ConsumeToken(Token.Symbol symbol, bool force) {
396      if (tokens.Count == 0)
397        if (force)
398          throw new ParseError(String.Format(
399            "end of input while expecting token \"{0}\"",
400            symbol));
401        else
402          return false;
403      if (tokens.Peek().Name == symbol) {
404        tokens.Dequeue();
405        return true;
406      } else if (force) {
407        throw new ParseError(String.Format(
408          "expected \"{0}\" found \"{1}\"",
409          symbol,
410          tokens.Peek().ToString()));
411      } else {
412        return false;
413      }
414    }
415  }
416}
Note: See TracBrowser for help on using the repository browser.