Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 3016 was 3016, checked in by epitzer, 14 years ago

Update API docs. (#548)

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