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
RevLine 
[3743]1#region License Information
2/* HeuristicLab
[12009]3 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[3743]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;
[9018]23using System.Collections.Generic;
[9005]24using System.Linq;
[1703]25using System.Text;
[1776]26using System.Text.RegularExpressions;
[1703]27
28namespace HeuristicLab.Persistence.Auxiliary {
29
[3016]30  /// <summary>
31  /// Error during type name parsing, thrown by <see cref="TypeNameParser"/>.
32  /// </summary>
[1776]33  public class ParseError : Exception {
[3016]34
35    /// <summary>
36    /// Initializes a new instance of the <see cref="ParseError"/> class.
37    /// </summary>
38    /// <param name="message">The message.</param>
[1776]39    public ParseError(string message) : base(message) { }
40  }
[1703]41
[3004]42  /// <summary>
43  /// Parse a .NET type name using the following grammar: 
44  ///   
[3016]45  /// <code>
[3004]46  /// TypeSpec := SimpleTypeSpec '&amp;'? 
47  ///
48  /// SimpleTypeSpec := (IDENTIFIER '.')*
[9005]49  ///                   (IDENTIFIER '`\d+'? '+')*
[3004]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]* 
[3016]64  /// </code>
[3004]65  /// </summary>
[1776]66  public class TypeNameParser {
67
68    /*
69      TypeSpec := SimpleTypeSpec '&'?
70
71      SimpleTypeSpec := (IDENTIFIER '.')*
[9005]72                        (IDENTIFIER '`\d+'?
73                        ( '+' IDENTIFIER '`\d+'? )*
74                        ( '[' Generics ']' )?
[1776]75                        (\*|\[(\d+\.\.\d+|\d+\.\.\.|(|\*)(,(|\*))*)\])*
76                        (',\s*' IDENTIFIER (',\s*' AssemblyProperty)* )?
77
[9005]78      Generics := '[' SimpleTypeSpec ']' (',[' SimpleTypeSpec ']')*
[1776]79
80      AssemblyProperty := 'Version=' Version
81                 |  'PublicKey(Token)?=[a-fA-F0-9]+'
82                 |  'Culture=[a-zA-F0-9]+'
83
[2859]84      Version := \d+\.\d+\.\d+\.\d+
[1776]85
[2737]86      IDENTIFIER = [_a-zA-Z][_a-ZA-Z0-9]*
[1776]87    */
88
89
[3004]90    private class Token {
[9018]91      public enum Symbol { None, Dash, Ampersand, Dot, Plus, Comma, OpenBracket, CloseBracket, Asterisk, Space, Equals, Backtick }
[9005]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; }
[1776]109      public string Value { get; private set; }
[2859]110      public bool IsIdentifier { get; private set; }
[1776]111      public int? Number { get; private set; }
[1779]112      public int Position { get; private set; }
113      private Token(string value, int pos) {
114        Position = pos;
[9005]115        Name = Symbol.None;
116        if (TOKENS.ContainsKey(value)) {
117          Name = TOKENS[value];
[1776]118        } else if (NumberRegex.IsMatch(value)) {
119          Number = int.Parse(value);
120        } else {
121          Value = value;
[2859]122          IsIdentifier = IdentifierRegex.IsMatch(value);
[1776]123        }
124      }
[4068]125      public static IEnumerable<Token> Tokenize(string s) {
[1779]126        int pos = 0;
[1776]127        foreach (Match m in TokenRegex.Matches(s)) {
[1779]128          yield return new Token(m.Value, pos);
129          pos += m.Length;
[1776]130        }
131      }
132      public override string ToString() {
[9005]133        if (Name != Symbol.None)
[1776]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
[1779]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    }
[1776]152
153    private TypeNameParser(string s) {
154      tokens = new Queue<Token>(Token.Tokenize(s));
155    }
156
[3016]157    /// <summary>
158    /// Parses the specified typename string as obtained by
[9018]159    /// <c>System.Object.GetType().FullName</c> or
160    /// <c>System.Object.GetType().AssemblyQualifiedName</c>.
[3016]161    /// </summary>
162    /// <param name="s">The typename string.</param>
163    /// <returns>A <see cref="TypeName"/> representing the type name.</returns>
[1779]164    public static TypeName Parse(string s) {
[1776]165      TypeNameParser p = new TypeNameParser(s);
[1779]166      try {
167        return p.TransformTypeSpec();
[4068]168      }
169      catch (ParseError x) {
[1779]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      }
[1776]183    }
184
[1779]185    private TypeName TransformTypeSpec() {
186      TypeName t = TransformSimpleTypeSpec();
[9005]187      t.IsReference = ConsumeToken(Token.Symbol.Ampersand);
[1779]188      return t;
[1776]189    }
190
[1779]191    private TypeName TransformSimpleTypeSpec() {
[9018]192      var nameSpace = new List<string> { ConsumeIdentifier() };
[9005]193      while (ConsumeToken(Token.Symbol.Dot))
[1776]194        nameSpace.Add(ConsumeIdentifier());
[9005]195      var className = new List<string>();
[1776]196      if (nameSpace.Count > 0) {
[1779]197        className.Add(nameSpace[nameSpace.Count - 1]);
[1776]198        nameSpace.RemoveAt(nameSpace.Count - 1);
199      }
[9005]200      var genericArgCounts = new List<int> {
201        ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0
202      };
[9018]203      while (ConsumeToken(Token.Symbol.Plus)) {
[1779]204        className.Add(ConsumeIdentifier());
[9005]205        genericArgCounts.Add(ConsumeToken(Token.Symbol.Backtick) ? ConsumeNumber() : 0);
206      }
207      var nGenericArgs = genericArgCounts.Sum();
208      var typeName = new TypeName(
[1779]209        string.Join(".", nameSpace.ToArray()),
[9005]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);
[1776]215      }
[9005]216      var pointerOrArray = new StringBuilder();
[1776]217      while (true) {
[9005]218        if (ConsumeToken(Token.Symbol.Asterisk)) {
[1776]219          pointerOrArray.Append("*");
[9005]220        } else if (ConsumeToken(Token.Symbol.OpenBracket)) {
[1779]221          pointerOrArray.Append('[');
[1776]222          ParseDimension(pointerOrArray);
[9005]223          while (ConsumeToken(Token.Symbol.Comma)) {
[1776]224            pointerOrArray.Append(",");
225            ParseDimension(pointerOrArray);
226          }
[9005]227          ConsumeToken(Token.Symbol.CloseBracket, true);
[1779]228          pointerOrArray.Append(']');
[1776]229        } else {
230          break;
231        }
232      }
[1779]233      typeName.MemoryMagic = pointerOrArray.ToString();
[1776]234      if (ConsumeComma()) {
[9005]235        var sb = new StringBuilder();
[1779]236        sb.Append(ConsumeIdentifier());
[9005]237        while (CanConsumeToken(Token.Symbol.Dot) ||
238          CanConsumeToken(Token.Symbol.Dash) ||
[1779]239          CanConsumeNumber() ||
240          CanConsumeIdentifier()) {
[9005]241          if (ConsumeToken(Token.Symbol.Dot))
[1779]242            sb.Append('.');
[9005]243          else if (ConsumeToken(Token.Symbol.Dash))
[1779]244            sb.Append('-');
245          else if (CanConsumeNumber())
246            sb.Append(ConsumeNumber());
247          else
248            sb.Append(ConsumeIdentifier());
249        }
250        typeName.AssemblyName = sb.ToString();
[1776]251        while (ConsumeComma()) {
[1779]252          KeyValuePair<string, string> property =
253            TransformAssemblyProperty();
254          typeName.AssemblyAttribues.Add(property.Key, property.Value);
[1776]255        }
256      }
[1779]257      return typeName;
[1776]258    }
259
260    private void ParseDimension(StringBuilder sb) {
[9005]261      if (ConsumeToken(Token.Symbol.Asterisk)) {
[1776]262        sb.Append("*");
263      } else if (CanConsumeNumber()) {
264        sb.Append(ConsumeNumber());
[9005]265        ConsumeToken(Token.Symbol.Dot, true);
266        ConsumeToken(Token.Symbol.Dot, true);
267        if (ConsumeToken(Token.Symbol.Dot)) {
[1776]268          sb.Append("...");
269        } else {
270          sb.Append("..").Append(ConsumeNumber());
271        }
272      }
273    }
274
[1779]275    private IEnumerable<TypeName> TransformGenerics() {
[9005]276      ConsumeToken(Token.Symbol.OpenBracket, true);
[1779]277      yield return TransformSimpleTypeSpec();
[9005]278      ConsumeToken(Token.Symbol.CloseBracket, true);
279      while (ConsumeToken(Token.Symbol.Comma)) {
280        ConsumeToken(Token.Symbol.OpenBracket, true);
[1779]281        yield return TransformSimpleTypeSpec();
[9005]282        ConsumeToken(Token.Symbol.CloseBracket, true);
[1776]283      }
284    }
285
[1779]286    private KeyValuePair<string, string> TransformAssemblyProperty() {
[1776]287      if (ConsumeIdentifier("Version")) {
[9005]288        ConsumeToken(Token.Symbol.Equals, true);
[1779]289        return new KeyValuePair<string, string>(
290          "Version",
291          TransformVersion());
[1776]292      } else if (ConsumeIdentifier("PublicKey")) {
[1795]293        return ConsumeAssignment("PublicKey");
[1776]294      } else if (ConsumeIdentifier("PublicKeyToken")) {
[1795]295        return ConsumeAssignment("PublicKeyToken");
[1776]296      } else if (ConsumeIdentifier("Culture")) {
[1795]297        return ConsumeAssignment("Culture");
[1776]298      } else if (ConsumeIdentifier("Custom")) {
[1795]299        return ConsumeAssignment("Custom");
[1776]300      } else {
301        throw new ParseError(String.Format(
302          "Invalid assembly property \"{0}\"",
303          tokens.Peek().ToString()));
304      }
305    }
[1795]306
307    private KeyValuePair<string, string> ConsumeAssignment(string name) {
[9005]308      ConsumeToken(Token.Symbol.Equals, true);
[2859]309      return new KeyValuePair<string, string>(name, ConsumeToken());
[1795]310    }
311
[1776]312    private string TransformVersion() {
[1779]313      StringBuilder version = new StringBuilder();
314      version.Append(ConsumeNumber());
[9005]315      ConsumeToken(Token.Symbol.Dot);
[1779]316      version.Append('.').Append(ConsumeNumber());
[9005]317      ConsumeToken(Token.Symbol.Dot);
[1779]318      version.Append('.').Append(ConsumeNumber());
[9005]319      ConsumeToken(Token.Symbol.Dot);
[1779]320      version.Append('.').Append(ConsumeNumber());
321      return version.ToString();
[1776]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;
[2859]343      if (tokens.Peek().Value == value && tokens.Peek().IsIdentifier) {
[1776]344        tokens.Dequeue();
345        return true;
346      } else {
347        return false;
348      }
349    }
350
[1779]351    private bool CanConsumeIdentifier() {
352      return tokens.Count > 0 && tokens.Peek().Value != null;
353    }
354
[1776]355    private string ConsumeIdentifier() {
356      if (tokens.Count == 0)
357        throw new ParseError("End of input while expecting identifier");
[2859]358      if (tokens.Peek().Value != null && tokens.Peek().IsIdentifier)
[1776]359        return tokens.Dequeue().Value;
360      throw new ParseError(String.Format(
361        "Identifier expected, found \"{0}\" instead",
362        tokens.Peek().ToString()));
363    }
364
[2859]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
[1776]375    private bool ConsumeComma() {
[9005]376      if (ConsumeToken(Token.Symbol.Comma)) {
377        while (ConsumeToken(Token.Symbol.Space)) ;
[1776]378        return true;
379      } else {
380        return false;
381      }
382    }
383
[9005]384    private bool ConsumeToken(Token.Symbol symbol) {
385      return ConsumeToken(symbol, false);
[1776]386    }
387
[9005]388    private bool CanConsumeToken(Token.Symbol symbol) {
[1779]389      if (tokens.Count == 0)
390        return false;
[9005]391      if (tokens.Peek().Name == symbol)
[1779]392        return true;
393      return false;
394    }
395
[9005]396    private bool ConsumeToken(Token.Symbol symbol, bool force) {
[1776]397      if (tokens.Count == 0)
398        if (force)
399          throw new ParseError(String.Format(
400            "end of input while expecting token \"{0}\"",
[9005]401            symbol));
[1776]402        else
403          return false;
[9005]404      if (tokens.Peek().Name == symbol) {
[1776]405        tokens.Dequeue();
406        return true;
407      } else if (force) {
408        throw new ParseError(String.Format(
409          "expected \"{0}\" found \"{1}\"",
[9005]410          symbol,
[1776]411          tokens.Peek().ToString()));
412      } else {
413        return false;
414      }
415    }
416  }
[1703]417}
Note: See TracBrowser for help on using the repository browser.