Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.Persistence/3.3/Auxiliary/TypeNameParser.cs @ 13326

Last change on this file since 13326 was 12009, checked in by ascheibe, 9 years ago

#2212 updated copyright year

File size: 14.2 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 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.Collections.Generic;
24using System.Linq;
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> or
160    /// <c>System.Object.GetType().AssemblyQualifiedName</c>.
161    /// </summary>
162    /// <param name="s">The typename string.</param>
163    /// <returns>A <see cref="TypeName"/> representing the type name.</returns>
164    public static TypeName Parse(string s) {
165      TypeNameParser p = new TypeNameParser(s);
166      try {
167        return p.TransformTypeSpec();
168      }
169      catch (ParseError x) {
170        if (p.Position > 0)
171          throw new ParseError(String.Format(
172            "Could not parse typename: {0}\n\"{1}====>{2}<===={3}",
173            x.Message,
174            s.Substring(0, p.Position),
175            s[p.Position],
176            s.Substring(p.Position, s.Length - p.Position)));
177        else
178          throw new ParseError(String.Format(
179            "Could not parse typenname \"{0}\" at end of input: {1}",
180            s,
181            x.Message));
182      }
183    }
184
185    private TypeName TransformTypeSpec() {
186      TypeName t = TransformSimpleTypeSpec();
187      t.IsReference = ConsumeToken(Token.Symbol.Ampersand);
188      return t;
189    }
190
191    private TypeName TransformSimpleTypeSpec() {
192      var nameSpace = new List<string> { ConsumeIdentifier() };
193      while (ConsumeToken(Token.Symbol.Dot))
194        nameSpace.Add(ConsumeIdentifier());
195      var className = new List<string>();
196      if (nameSpace.Count > 0) {
197        className.Add(nameSpace[nameSpace.Count - 1]);
198        nameSpace.RemoveAt(nameSpace.Count - 1);
199      }
200      var genericArgCounts = new List<int> {
201        ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0
202      };
203      while (ConsumeToken(Token.Symbol.Plus)) {
204        className.Add(ConsumeIdentifier());
205        genericArgCounts.Add(ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0);
206      }
207      var nGenericArgs = genericArgCounts.Sum();
208      var typeName = new TypeName(
209        string.Join(".", nameSpace.ToArray()),
210        string.Join("+", className.ToArray()),
211        nGenericArgs > genericArgCounts.Last() ? genericArgCounts : null);
212      if (nGenericArgs > 0 && ConsumeToken(Token.Symbol.OpenBracket, true)) {
213        typeName.GenericArgs.AddRange(TransformGenerics());
214        ConsumeToken(Token.Symbol.CloseBracket, true);
215      }
216      var pointerOrArray = new StringBuilder();
217      while (true) {
218        if (ConsumeToken(Token.Symbol.Asterisk)) {
219          pointerOrArray.Append("*");
220        } else if (ConsumeToken(Token.Symbol.OpenBracket)) {
221          pointerOrArray.Append('[');
222          ParseDimension(pointerOrArray);
223          while (ConsumeToken(Token.Symbol.Comma)) {
224            pointerOrArray.Append(",");
225            ParseDimension(pointerOrArray);
226          }
227          ConsumeToken(Token.Symbol.CloseBracket, true);
228          pointerOrArray.Append(']');
229        } else {
230          break;
231        }
232      }
233      typeName.MemoryMagic = pointerOrArray.ToString();
234      if (ConsumeComma()) {
235        var sb = new StringBuilder();
236        sb.Append(ConsumeIdentifier());
237        while (CanConsumeToken(Token.Symbol.Dot) ||
238          CanConsumeToken(Token.Symbol.Dash) ||
239          CanConsumeNumber() ||
240          CanConsumeIdentifier()) {
241          if (ConsumeToken(Token.Symbol.Dot))
242            sb.Append('.');
243          else if (ConsumeToken(Token.Symbol.Dash))
244            sb.Append('-');
245          else if (CanConsumeNumber())
246            sb.Append(ConsumeNumber());
247          else
248            sb.Append(ConsumeIdentifier());
249        }
250        typeName.AssemblyName = sb.ToString();
251        while (ConsumeComma()) {
252          KeyValuePair<string, string> property =
253            TransformAssemblyProperty();
254          typeName.AssemblyAttribues.Add(property.Key, property.Value);
255        }
256      }
257      return typeName;
258    }
259
260    private void ParseDimension(StringBuilder sb) {
261      if (ConsumeToken(Token.Symbol.Asterisk)) {
262        sb.Append("*");
263      } else if (CanConsumeNumber()) {
264        sb.Append(ConsumeNumber());
265        ConsumeToken(Token.Symbol.Dot, true);
266        ConsumeToken(Token.Symbol.Dot, true);
267        if (ConsumeToken(Token.Symbol.Dot)) {
268          sb.Append("...");
269        } else {
270          sb.Append("..").Append(ConsumeNumber());
271        }
272      }
273    }
274
275    private IEnumerable<TypeName> TransformGenerics() {
276      ConsumeToken(Token.Symbol.OpenBracket, true);
277      yield return TransformSimpleTypeSpec();
278      ConsumeToken(Token.Symbol.CloseBracket, true);
279      while (ConsumeToken(Token.Symbol.Comma)) {
280        ConsumeToken(Token.Symbol.OpenBracket, true);
281        yield return TransformSimpleTypeSpec();
282        ConsumeToken(Token.Symbol.CloseBracket, true);
283      }
284    }
285
286    private KeyValuePair<string, string> TransformAssemblyProperty() {
287      if (ConsumeIdentifier("Version")) {
288        ConsumeToken(Token.Symbol.Equals, true);
289        return new KeyValuePair<string, string>(
290          "Version",
291          TransformVersion());
292      } else if (ConsumeIdentifier("PublicKey")) {
293        return ConsumeAssignment("PublicKey");
294      } else if (ConsumeIdentifier("PublicKeyToken")) {
295        return ConsumeAssignment("PublicKeyToken");
296      } else if (ConsumeIdentifier("Culture")) {
297        return ConsumeAssignment("Culture");
298      } else if (ConsumeIdentifier("Custom")) {
299        return ConsumeAssignment("Custom");
300      } else {
301        throw new ParseError(String.Format(
302          "Invalid assembly property \"{0}\"",
303          tokens.Peek().ToString()));
304      }
305    }
306
307    private KeyValuePair<string, string> ConsumeAssignment(string name) {
308      ConsumeToken(Token.Symbol.Equals, true);
309      return new KeyValuePair<string, string>(name, ConsumeToken());
310    }
311
312    private string TransformVersion() {
313      StringBuilder version = new StringBuilder();
314      version.Append(ConsumeNumber());
315      ConsumeToken(Token.Symbol.Dot);
316      version.Append('.').Append(ConsumeNumber());
317      ConsumeToken(Token.Symbol.Dot);
318      version.Append('.').Append(ConsumeNumber());
319      ConsumeToken(Token.Symbol.Dot);
320      version.Append('.').Append(ConsumeNumber());
321      return version.ToString();
322    }
323
324    private bool CanConsumeNumber() {
325      if (tokens.Count == 0)
326        return false;
327      return tokens.Peek().Number != null;
328    }
329
330    private int ConsumeNumber() {
331      if (tokens.Count == 0)
332        throw new ParseError("End of input while expecting number");
333      if (tokens.Peek().Number != null)
334        return (int)tokens.Dequeue().Number;
335      throw new ParseError(string.Format(
336        "Number expected, found \"{0}\" instead",
337        tokens.Peek().ToString()));
338    }
339
340    private bool ConsumeIdentifier(string value) {
341      if (tokens.Count == 0)
342        return false;
343      if (tokens.Peek().Value == value && tokens.Peek().IsIdentifier) {
344        tokens.Dequeue();
345        return true;
346      } else {
347        return false;
348      }
349    }
350
351    private bool CanConsumeIdentifier() {
352      return tokens.Count > 0 && tokens.Peek().Value != null;
353    }
354
355    private string ConsumeIdentifier() {
356      if (tokens.Count == 0)
357        throw new ParseError("End of input while expecting identifier");
358      if (tokens.Peek().Value != null && tokens.Peek().IsIdentifier)
359        return tokens.Dequeue().Value;
360      throw new ParseError(String.Format(
361        "Identifier expected, found \"{0}\" instead",
362        tokens.Peek().ToString()));
363    }
364
365    private string ConsumeToken() {
366      if (tokens.Count == 0)
367        throw new ParseError("End of input while expecting token");
368      if (tokens.Peek().Value != null)
369        return tokens.Dequeue().Value;
370      throw new ParseError(String.Format(
371        "Token expected, found \"{0}\" instead",
372        tokens.Peek().ToString()));
373    }
374
375    private bool ConsumeComma() {
376      if (ConsumeToken(Token.Symbol.Comma)) {
377        while (ConsumeToken(Token.Symbol.Space)) ;
378        return true;
379      } else {
380        return false;
381      }
382    }
383
384    private bool ConsumeToken(Token.Symbol symbol) {
385      return ConsumeToken(symbol, false);
386    }
387
388    private bool CanConsumeToken(Token.Symbol symbol) {
389      if (tokens.Count == 0)
390        return false;
391      if (tokens.Peek().Name == symbol)
392        return true;
393      return false;
394    }
395
396    private bool ConsumeToken(Token.Symbol symbol, bool force) {
397      if (tokens.Count == 0)
398        if (force)
399          throw new ParseError(String.Format(
400            "end of input while expecting token \"{0}\"",
401            symbol));
402        else
403          return false;
404      if (tokens.Peek().Name == symbol) {
405        tokens.Dequeue();
406        return true;
407      } else if (force) {
408        throw new ParseError(String.Format(
409          "expected \"{0}\" found \"{1}\"",
410          symbol,
411          tokens.Peek().ToString()));
412      } else {
413        return false;
414      }
415    }
416  }
417}
Note: See TracBrowser for help on using the repository browser.