Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 3743 was 3743, checked in by gkronber, 14 years ago

Fixed GPL license headers #893

File size: 13.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 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.Text;
24using System.Text.RegularExpressions;
25using System.Reflection.Emit;
26using System.Collections.Generic;
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 '+')*
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 '+')*
73                         IDENTIFIER
74                        ( '`\d+[' 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      private static Dictionary<string, string> tokens =
92        new Dictionary<string, string> {
93          {"-", "DASH"},
94          {"&", "AMPERSAND"},
95          {".", "DOT"},
96          {"+", "PLUS"},
97          {",", "COMMA"},
98          {"[", "OPEN_BRACKET"},
99          {"]", "CLOSE_BRACKET"},
100          {"*", "ASTERISK"},
101          {" ", "SPACE"},
102          {"=", "EQUALS"},
103          {"`", "BACKTICK"} };
104      private static Regex NumberRegex = new Regex("^\\d+$");
105      private static Regex IdentifierRegex = new Regex("^[_a-zA-Z][_a-zA-Z0-9]*$");
106      private static Regex TokenRegex = new Regex("[-&.+,\\[\\]* =`]|[a-f0-9]+|\\d+|[_a-zA-Z][_a-zA-Z0-9]*");
107      public string Name { get; private set; }
108      public string Value { get; private set; }
109      public bool IsIdentifier { get; private set; }
110      public int? Number { get; private set; }
111      public int Position { get; private set; }
112      private Token(string value, int pos) {
113        Position = pos;
114        if (tokens.ContainsKey(value)) {
115          Name = tokens[value];
116        } else if (NumberRegex.IsMatch(value)) {
117          Number = int.Parse(value);
118        } else {
119          Value = value;
120          IsIdentifier = IdentifierRegex.IsMatch(value);
121        }
122      }
123      public static IEnumerable<Token> Tokenize(string s) {       
124        int pos = 0;
125        foreach (Match m in TokenRegex.Matches(s)) {
126          yield return new Token(m.Value, pos);
127          pos += m.Length;
128        }
129      }
130      public override string ToString() {
131        if (Name != null)
132          return "Token(" + Name + ")";
133        if (Value != null)
134          return "\"" + Value + "\"";
135        if (Number != null)
136          return Number.ToString();
137        return "<empty>";
138      }
139    }
140
141    private Queue<Token> tokens;
142    private int Position {
143      get {
144        if (tokens.Count == 0)
145          return -1;
146        else
147          return tokens.Peek().Position;
148      }
149    }
150
151    private TypeNameParser(string s) {
152      tokens = new Queue<Token>(Token.Tokenize(s));
153    }
154
155    /// <summary>
156    /// Parses the specified typename string as obtained by
157    /// <c>System.Object.GetType().FullName"</c>.
158    /// </summary>
159    /// <param name="s">The typename string.</param>
160    /// <returns>A <see cref="TypeName"/> representing the type name.</returns>
161    public static TypeName Parse(string s) {
162      TypeNameParser p = new TypeNameParser(s);
163      try {
164        return p.TransformTypeSpec();
165      } catch (ParseError x) {
166        if (p.Position > 0)
167          throw new ParseError(String.Format(
168            "Could not parse typename: {0}\n\"{1}====>{2}<===={3}",
169            x.Message,
170            s.Substring(0, p.Position),
171            s[p.Position],
172            s.Substring(p.Position, s.Length - p.Position)));
173        else
174          throw new ParseError(String.Format(
175            "Could not parse typenname \"{0}\" at end of input: {1}",
176            s,
177            x.Message));
178      }
179    }
180
181    private TypeName TransformTypeSpec() {
182      TypeName t = TransformSimpleTypeSpec();
183      t.IsReference = ConsumeToken("AMPERSAND");
184      return t;
185    }
186
187    private TypeName TransformSimpleTypeSpec() {
188      List<string> nameSpace = new List<string>();
189      nameSpace.Add(ConsumeIdentifier());
190      while (ConsumeToken("DOT"))
191        nameSpace.Add(ConsumeIdentifier());
192      List<string> className = new List<string>();
193      if (nameSpace.Count > 0) {
194        className.Add(nameSpace[nameSpace.Count - 1]);
195        nameSpace.RemoveAt(nameSpace.Count - 1);
196      }
197      while (ConsumeToken("PLUS"))
198        className.Add(ConsumeIdentifier());
199      TypeName typeName = new TypeName(
200        string.Join(".", nameSpace.ToArray()),
201        string.Join("+", className.ToArray()));
202      if (ConsumeToken("BACKTICK")) {
203        int nGenericArgs = ConsumeNumber();
204        if (ConsumeToken("OPEN_BRACKET") &&
205          CanConsumeToken("OPEN_BRACKET")) {
206          typeName.GenericArgs.AddRange(TransformGenerics());
207          ConsumeToken("CLOSE_BRACKET", true);
208        }
209      }
210      StringBuilder pointerOrArray = new StringBuilder();
211      while (true) {
212        if (ConsumeToken("ASTERSIK")) {
213          pointerOrArray.Append("*");
214        } else if (ConsumeToken("OPEN_BRACKET")) {
215          pointerOrArray.Append('[');
216          ParseDimension(pointerOrArray);
217          while (ConsumeToken("COMMA")) {
218            pointerOrArray.Append(",");
219            ParseDimension(pointerOrArray);
220          }
221          ConsumeToken("CLOSE_BRACKET", true);
222          pointerOrArray.Append(']');
223        } else {
224          break;
225        }
226      }
227      typeName.MemoryMagic = pointerOrArray.ToString();
228      if (ConsumeComma()) {
229        StringBuilder sb = new StringBuilder();
230        sb.Append(ConsumeIdentifier());
231        while (CanConsumeToken("DOT") ||
232          CanConsumeToken("DASH") ||
233          CanConsumeNumber() ||
234          CanConsumeIdentifier()) {
235          if (ConsumeToken("DOT"))
236            sb.Append('.');
237          else if (ConsumeToken("DASH"))
238            sb.Append('-');
239          else if (CanConsumeNumber())
240            sb.Append(ConsumeNumber());
241          else
242            sb.Append(ConsumeIdentifier());
243        }
244        typeName.AssemblyName = sb.ToString();
245        while (ConsumeComma()) {
246          KeyValuePair<string, string> property =
247            TransformAssemblyProperty();
248          typeName.AssemblyAttribues.Add(property.Key, property.Value);
249        }
250      }
251      return typeName;
252    }
253
254    private void ParseDimension(StringBuilder sb) {
255      if (ConsumeToken("ASTERSIK")) {
256        sb.Append("*");
257      } else if (CanConsumeNumber()) {
258        sb.Append(ConsumeNumber());
259        ConsumeToken("DOT", true);
260        ConsumeToken("DOT", true);
261        if (ConsumeToken("DOT")) {
262          sb.Append("...");
263        } else {
264          sb.Append("..").Append(ConsumeNumber());
265        }
266      }
267    }
268
269    private IEnumerable<TypeName> TransformGenerics() {
270      ConsumeToken("OPEN_BRACKET", true);
271      yield return TransformSimpleTypeSpec();
272      ConsumeToken("CLOSE_BRACKET", true);
273      while (ConsumeToken("COMMA")) {
274        ConsumeToken("OPEN_BRACKET", true);
275        yield return TransformSimpleTypeSpec();
276        ConsumeToken("CLOSE_BRACKET", true);
277      }
278    }
279
280    private KeyValuePair<string, string> TransformAssemblyProperty() {
281      if (ConsumeIdentifier("Version")) {
282        ConsumeToken("EQUALS", true);
283        return new KeyValuePair<string, string>(
284          "Version",
285          TransformVersion());
286      } else if (ConsumeIdentifier("PublicKey")) {
287        return ConsumeAssignment("PublicKey");
288      } else if (ConsumeIdentifier("PublicKeyToken")) {
289        return ConsumeAssignment("PublicKeyToken");
290      } else if (ConsumeIdentifier("Culture")) {
291        return ConsumeAssignment("Culture");
292      } else if (ConsumeIdentifier("Custom")) {
293        return ConsumeAssignment("Custom");
294      } else {
295        throw new ParseError(String.Format(
296          "Invalid assembly property \"{0}\"",
297          tokens.Peek().ToString()));
298      }
299    }
300
301    private KeyValuePair<string, string> ConsumeAssignment(string name) {
302      ConsumeToken("EQUALS", true);
303      return new KeyValuePair<string, string>(name, ConsumeToken());
304    }
305
306    private string TransformVersion() {
307      StringBuilder version = new StringBuilder();
308      version.Append(ConsumeNumber());
309      ConsumeToken("DOT");
310      version.Append('.').Append(ConsumeNumber());
311      ConsumeToken("DOT");
312      version.Append('.').Append(ConsumeNumber());
313      ConsumeToken("DOT");
314      version.Append('.').Append(ConsumeNumber());
315      return version.ToString();
316    }
317
318    private bool CanConsumeNumber() {
319      if (tokens.Count == 0)
320        return false;
321      return tokens.Peek().Number != null;
322    }
323
324    private int ConsumeNumber() {
325      if (tokens.Count == 0)
326        throw new ParseError("End of input while expecting number");
327      if (tokens.Peek().Number != null)
328        return (int)tokens.Dequeue().Number;
329      throw new ParseError(string.Format(
330        "Number expected, found \"{0}\" instead",
331        tokens.Peek().ToString()));
332    }
333
334    private bool ConsumeIdentifier(string value) {
335      if (tokens.Count == 0)
336        return false;
337      if (tokens.Peek().Value == value && tokens.Peek().IsIdentifier) {
338        tokens.Dequeue();
339        return true;
340      } else {
341        return false;
342      }
343    }
344
345    private bool CanConsumeIdentifier() {
346      return tokens.Count > 0 && tokens.Peek().Value != null;
347    }
348
349    private string ConsumeIdentifier() {
350      if (tokens.Count == 0)
351        throw new ParseError("End of input while expecting identifier");
352      if (tokens.Peek().Value != null && tokens.Peek().IsIdentifier)
353        return tokens.Dequeue().Value;
354      throw new ParseError(String.Format(
355        "Identifier expected, found \"{0}\" instead",
356        tokens.Peek().ToString()));
357    }
358
359    private string ConsumeToken() {
360      if (tokens.Count == 0)
361        throw new ParseError("End of input while expecting token");
362      if (tokens.Peek().Value != null)
363        return tokens.Dequeue().Value;
364      throw new ParseError(String.Format(
365        "Token expected, found \"{0}\" instead",
366        tokens.Peek().ToString()));
367    }
368
369    private bool ConsumeComma() {
370      if (ConsumeToken("COMMA")) {
371        while (ConsumeToken("SPACE")) ;
372        return true;
373      } else {
374        return false;
375      }
376    }
377
378    private bool ConsumeToken(string name) {
379      return ConsumeToken(name, false);
380    }
381
382    private bool CanConsumeToken(string name) {
383      if (tokens.Count == 0)
384        return false;
385      if (tokens.Peek().Name == name)
386        return true;
387      return false;
388    }
389
390    private bool ConsumeToken(string name, bool force) {
391      if (tokens.Count == 0)
392        if (force)
393          throw new ParseError(String.Format(
394            "end of input while expecting token \"{0}\"",
395            name));
396        else
397          return false;
398      if (tokens.Peek().Name == name) {
399        tokens.Dequeue();
400        return true;
401      } else if (force) {
402        throw new ParseError(String.Format(
403          "expected \"{0}\" found \"{1}\"",
404          name,
405          tokens.Peek().ToString()));
406      } else {
407        return false;
408      }
409    }
410  }
411}
Note: See TracBrowser for help on using the repository browser.