Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Persistence/3.3/Auxiliary/TypeStringBuilder.cs @ 1780

Last change on this file since 1780 was 1780, checked in by epitzer, 15 years ago

Store full version information with serialized data, only fall back to version invariant format if loading fails with full version. Also check, that loaded version is newer than requested version if possible. (#613)

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