Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 7259 was 7259, checked in by swagner, 12 years ago

Updated year of copyrights to 2012 (#1716)

File size: 13.4 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.Collections.Generic;
24using System.Text;
25using System.Text.RegularExpressions;
26
27namespace HeuristicLab.Persistence.Auxiliary {
28
29  /// <summary>
30  /// Error during type name parsing, thrown by <see cref="TypeNameParser"/>.
31  /// </summary>
32  public class ParseError : Exception {
33
34    /// <summary>
35    /// Initializes a new instance of the <see cref="ParseError"/> class.
36    /// </summary>
37    /// <param name="message">The message.</param>
38    public ParseError(string message) : base(message) { }
39  }
40
41  /// <summary>
42  /// Parse a .NET type name using the following grammar: 
43  ///   
44  /// <code>
45  /// TypeSpec := SimpleTypeSpec '&amp;'? 
46  ///
47  /// SimpleTypeSpec := (IDENTIFIER '.')*
48  ///                   (IDENTIFIER '+')*
49  ///                    IDENTIFIER
50  ///                   ( '`\d+[' Generics ']' )?
51  ///                   (\*|\[(\d+\.\.\d+|\d+\.\.\.|(|\*)(,(|\*))*)\])*
52  ///                   (',\s*' IDENTIFIER (',\s*' AssemblyProperty)* )? 
53  ///
54  /// Generics := '[' SimpleTypeSpec ']' (',[' SimpleTypeSpec ']')
55  ///
56  /// AssemblyProperty := 'Version=' Version
57  ///                  |  'PublicKey(Token)?=[a-fA-F0-9]+'
58  ///                  |  'Culture=[a-zA-F0-9]+'
59  ///
60  /// Version := \d+\.\d+\.\d+\.\d+
61  ///
62  /// IDENTIFIER = [_a-zA-Z][_a-ZA-Z0-9]* 
63  /// </code>
64  /// </summary>
65  public class TypeNameParser {
66
67    /*
68      TypeSpec := SimpleTypeSpec '&'?
69
70      SimpleTypeSpec := (IDENTIFIER '.')*
71                        (IDENTIFIER '+')*
72                         IDENTIFIER
73                        ( '`\d+[' Generics ']' )?
74                        (\*|\[(\d+\.\.\d+|\d+\.\.\.|(|\*)(,(|\*))*)\])*
75                        (',\s*' IDENTIFIER (',\s*' AssemblyProperty)* )?
76
77      Generics := '[' SimpleTypeSpec ']' (',[' SimpleTypeSpec ']')
78
79      AssemblyProperty := 'Version=' Version
80                 |  'PublicKey(Token)?=[a-fA-F0-9]+'
81                 |  'Culture=[a-zA-F0-9]+'
82
83      Version := \d+\.\d+\.\d+\.\d+
84
85      IDENTIFIER = [_a-zA-Z][_a-ZA-Z0-9]*
86    */
87
88
89    private class Token {
90      private static Dictionary<string, string> tokens =
91        new Dictionary<string, string> {
92          {"-", "DASH"},
93          {"&", "AMPERSAND"},
94          {".", "DOT"},
95          {"+", "PLUS"},
96          {",", "COMMA"},
97          {"[", "OPEN_BRACKET"},
98          {"]", "CLOSE_BRACKET"},
99          {"*", "ASTERISK"},
100          {" ", "SPACE"},
101          {"=", "EQUALS"},
102          {"`", "BACKTICK"} };
103      private static Regex NumberRegex = new Regex("^\\d+$");
104      private static Regex IdentifierRegex = new Regex("^[_a-zA-Z][_a-zA-Z0-9]*$");
105      private static Regex TokenRegex = new Regex("[-&.+,\\[\\]* =`]|[a-f0-9]+|\\d+|[_a-zA-Z][_a-zA-Z0-9]*");
106      public string Name { get; private set; }
107      public string Value { get; private set; }
108      public bool IsIdentifier { get; private set; }
109      public int? Number { get; private set; }
110      public int Position { get; private set; }
111      private Token(string value, int pos) {
112        Position = pos;
113        if (tokens.ContainsKey(value)) {
114          Name = tokens[value];
115        } else if (NumberRegex.IsMatch(value)) {
116          Number = int.Parse(value);
117        } else {
118          Value = value;
119          IsIdentifier = IdentifierRegex.IsMatch(value);
120        }
121      }
122      public static IEnumerable<Token> Tokenize(string s) {
123        int pos = 0;
124        foreach (Match m in TokenRegex.Matches(s)) {
125          yield return new Token(m.Value, pos);
126          pos += m.Length;
127        }
128      }
129      public override string ToString() {
130        if (Name != null)
131          return "Token(" + Name + ")";
132        if (Value != null)
133          return "\"" + Value + "\"";
134        if (Number != null)
135          return Number.ToString();
136        return "<empty>";
137      }
138    }
139
140    private Queue<Token> tokens;
141    private int Position {
142      get {
143        if (tokens.Count == 0)
144          return -1;
145        else
146          return tokens.Peek().Position;
147      }
148    }
149
150    private TypeNameParser(string s) {
151      tokens = new Queue<Token>(Token.Tokenize(s));
152    }
153
154    /// <summary>
155    /// Parses the specified typename string as obtained by
156    /// <c>System.Object.GetType().FullName"</c>.
157    /// </summary>
158    /// <param name="s">The typename string.</param>
159    /// <returns>A <see cref="TypeName"/> representing the type name.</returns>
160    public static TypeName Parse(string s) {
161      TypeNameParser p = new TypeNameParser(s);
162      try {
163        return p.TransformTypeSpec();
164      }
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.