Merge branch 'sgen-disable-gc'
[mono.git] / mcs / class / IKVM.Reflection / TypeNameParser.cs
1 /*
2   Copyright (C) 2009-2011 Jeroen Frijters
3
4   This software is provided 'as-is', without any express or implied
5   warranty.  In no event will the authors be held liable for any damages
6   arising from the use of this software.
7
8   Permission is granted to anyone to use this software for any purpose,
9   including commercial applications, and to alter it and redistribute it
10   freely, subject to the following restrictions:
11
12   1. The origin of this software must not be misrepresented; you must not
13      claim that you wrote the original software. If you use this software
14      in a product, an acknowledgment in the product documentation would be
15      appreciated but is not required.
16   2. Altered source versions must be plainly marked as such, and must not be
17      misrepresented as being the original software.
18   3. This notice may not be removed or altered from any source distribution.
19
20   Jeroen Frijters
21   jeroen@frijters.net
22   
23 */
24 using System;
25 using System.Collections.Generic;
26 using System.Text;
27
28 namespace IKVM.Reflection
29 {
30         // this respresents a type name as in metadata:
31         // - ns will be null for empty the namespace (never the empty string)
32         // - the strings are not escaped
33         struct TypeName : IEquatable<TypeName>
34         {
35                 private readonly string ns;
36                 private readonly string name;
37
38                 internal TypeName(string ns, string name)
39                 {
40                         if (name == null)
41                         {
42                                 throw new ArgumentNullException("name");
43                         }
44                         this.ns = ns;
45                         this.name = name;
46                 }
47
48                 internal string Name
49                 {
50                         get { return name; }
51                 }
52
53                 internal string Namespace
54                 {
55                         get { return ns; }
56                 }
57
58                 public static bool operator ==(TypeName o1, TypeName o2)
59                 {
60                         return o1.ns == o2.ns && o1.name == o2.name;
61                 }
62
63                 public static bool operator !=(TypeName o1, TypeName o2)
64                 {
65                         return o1.ns != o2.ns || o1.name != o2.name;
66                 }
67
68                 public override int GetHashCode()
69                 {
70                         return ns == null ? name.GetHashCode() : ns.GetHashCode() * 37 + name.GetHashCode();
71                 }
72
73                 public override bool Equals(object obj)
74                 {
75                         TypeName? other = obj as TypeName?;
76                         return other != null && other.Value == this;
77                 }
78
79                 public override string ToString()
80                 {
81                         return ns == null ? name : ns + "." + name;
82                 }
83
84                 bool IEquatable<TypeName>.Equals(TypeName other)
85                 {
86                         return this == other;
87                 }
88
89                 internal static TypeName Split(string name)
90                 {
91                         int dot = name.LastIndexOf('.');
92                         if (dot == -1)
93                         {
94                                 return new TypeName(null, name);
95                         }
96                         else
97                         {
98                                 return new TypeName(name.Substring(0, dot), name.Substring(dot + 1));
99                         }
100                 }
101         }
102
103         struct TypeNameParser
104         {
105                 private const string SpecialChars = "\\+,[]*&";
106                 private const short SZARRAY = -1;
107                 private const short BYREF = -2;
108                 private const short POINTER = -3;
109                 private readonly string name;
110                 private readonly string[] nested;
111                 private readonly string assemblyName;
112                 private readonly short[] modifiers;
113                 private readonly TypeNameParser[] genericParameters;
114
115                 internal static string Escape(string name)
116                 {
117                         if (name == null)
118                         {
119                                 return null;
120                         }
121                         StringBuilder sb = null;
122                         for (int pos = 0; pos < name.Length; pos++)
123                         {
124                                 char c = name[pos];
125                                 if (SpecialChars.IndexOf(c) != -1)
126                                 {
127                                         if (sb == null)
128                                         {
129                                                 sb = new StringBuilder(name, 0, pos, name.Length + 3);
130                                         }
131                                         sb.Append('\\').Append(c);
132                                 }
133                                 else if (sb != null)
134                                 {
135                                         sb.Append(c);
136                                 }
137                         }
138                         return sb != null ? sb.ToString() : name;
139                 }
140
141                 internal static string Unescape(string name)
142                 {
143                         int pos = name.IndexOf('\\');
144                         if (pos == -1)
145                         {
146                                 return name;
147                         }
148                         StringBuilder sb = new StringBuilder(name, 0, pos, name.Length - 1);
149                         for (; pos < name.Length; pos++)
150                         {
151                                 char c = name[pos];
152                                 if (c == '\\')
153                                 {
154                                         c = name[++pos];
155                                 }
156                                 sb.Append(c);
157                         }
158                         return sb.ToString();
159                 }
160
161                 internal static TypeNameParser Parse(string typeName, bool throwOnError)
162                 {
163                         if (throwOnError)
164                         {
165                                 Parser parser = new Parser(typeName);
166                                 return new TypeNameParser(ref parser, true);
167                         }
168                         else
169                         {
170                                 try
171                                 {
172                                         Parser parser = new Parser(typeName);
173                                         return new TypeNameParser(ref parser, true);
174                                 }
175                                 catch (ArgumentException)
176                                 {
177                                         return new TypeNameParser();
178                                 }
179                         }
180                 }
181
182                 private TypeNameParser(ref Parser parser, bool withAssemblyName)
183                 {
184                         bool genericParameter = parser.pos != 0;
185                         name = parser.NextNamePart();
186                         nested = null;
187                         parser.ParseNested(ref nested);
188                         genericParameters = null;
189                         parser.ParseGenericParameters(ref genericParameters);
190                         modifiers = null;
191                         parser.ParseModifiers(ref modifiers);
192                         assemblyName = null;
193                         if (withAssemblyName)
194                         {
195                                 parser.ParseAssemblyName(genericParameter, ref assemblyName);
196                         }
197                 }
198
199                 internal bool Error
200                 {
201                         get { return name == null; }
202                 }
203
204                 internal string FirstNamePart
205                 {
206                         get { return name; }
207                 }
208
209                 internal string AssemblyName
210                 {
211                         get { return assemblyName; }
212                 }
213
214                 private struct Parser
215                 {
216                         private readonly string typeName;
217                         internal int pos;
218
219                         internal Parser(string typeName)
220                         {
221                                 this.typeName = typeName;
222                                 this.pos = 0;
223                         }
224
225                         private void Check(bool condition)
226                         {
227                                 if (!condition)
228                                 {
229                                         throw new ArgumentException("Invalid type name '" + typeName + "'");
230                                 }
231                         }
232
233                         private void Consume(char c)
234                         {
235                                 Check(pos < typeName.Length && typeName[pos++] == c);
236                         }
237
238                         private bool TryConsume(char c)
239                         {
240                                 if (pos < typeName.Length && typeName[pos] == c)
241                                 {
242                                         pos++;
243                                         return true;
244                                 }
245                                 else
246                                 {
247                                         return false;
248                                 }
249                         }
250
251                         internal string NextNamePart()
252                         {
253                                 SkipWhiteSpace();
254                                 int start = pos;
255                                 for (; pos < typeName.Length; pos++)
256                                 {
257                                         char c = typeName[pos];
258                                         if (c == '\\')
259                                         {
260                                                 pos++;
261                                                 Check(pos < typeName.Length && SpecialChars.IndexOf(typeName[pos]) != -1);
262                                         }
263                                         else if (SpecialChars.IndexOf(c) != -1)
264                                         {
265                                                 break;
266                                         }
267                                 }
268                                 Check(pos - start != 0);
269                                 if (start == 0 && pos == typeName.Length)
270                                 {
271                                         return typeName;
272                                 }
273                                 else
274                                 {
275                                         return typeName.Substring(start, pos - start);
276                                 }
277                         }
278
279                         internal void ParseNested(ref string[] nested)
280                         {
281                                 while (TryConsume('+'))
282                                 {
283                                         Add(ref nested, NextNamePart());
284                                 }
285                         }
286
287                         internal void ParseGenericParameters(ref TypeNameParser[] genericParameters)
288                         {
289                                 int saved = pos;
290                                 if (TryConsume('['))
291                                 {
292                                         SkipWhiteSpace();
293                                         if (TryConsume(']') || TryConsume('*') || TryConsume(','))
294                                         {
295                                                 // it's not a generic parameter list, but an array instead
296                                                 pos = saved;
297                                                 return;
298                                         }
299                                         do
300                                         {
301                                                 SkipWhiteSpace();
302                                                 if (TryConsume('['))
303                                                 {
304                                                         Add(ref genericParameters, new TypeNameParser(ref this, true));
305                                                         Consume(']');
306                                                 }
307                                                 else
308                                                 {
309                                                         Add(ref genericParameters, new TypeNameParser(ref this, false));
310                                                 }
311                                         }
312                                         while (TryConsume(','));
313                                         Consume(']');
314                                         SkipWhiteSpace();
315                                 }
316                         }
317
318                         internal void ParseModifiers(ref short[] modifiers)
319                         {
320                                 while (pos < typeName.Length)
321                                 {
322                                         switch (typeName[pos])
323                                         {
324                                                 case '*':
325                                                         pos++;
326                                                         Add(ref modifiers, POINTER);
327                                                         break;
328                                                 case '&':
329                                                         pos++;
330                                                         Add(ref modifiers, BYREF);
331                                                         break;
332                                                 case '[':
333                                                         pos++;
334                                                         Add(ref modifiers, ParseArray());
335                                                         Consume(']');
336                                                         break;
337                                                 default:
338                                                         return;
339                                         }
340                                         SkipWhiteSpace();
341                                 }
342                         }
343
344                         internal void ParseAssemblyName(bool genericParameter, ref string assemblyName)
345                         {
346                                 if (pos < typeName.Length)
347                                 {
348                                         if (typeName[pos] == ']' && genericParameter)
349                                         {
350                                                 // ok
351                                         }
352                                         else
353                                         {
354                                                 Consume(',');
355                                                 SkipWhiteSpace();
356                                                 if (genericParameter)
357                                                 {
358                                                         int start = pos;
359                                                         while (pos < typeName.Length)
360                                                         {
361                                                                 char c = typeName[pos];
362                                                                 if (c == '\\')
363                                                                 {
364                                                                         pos++;
365                                                                         // a backslash itself is not legal in an assembly name, so we don't need to check for an escaped backslash
366                                                                         Check(pos < typeName.Length && typeName[pos++] == ']');
367                                                                 }
368                                                                 else if (c == ']')
369                                                                 {
370                                                                         break;
371                                                                 }
372                                                                 else
373                                                                 {
374                                                                         pos++;
375                                                                 }
376                                                         }
377                                                         Check(pos < typeName.Length && typeName[pos] == ']');
378                                                         assemblyName = typeName.Substring(start, pos - start).Replace("\\]", "]");
379                                                 }
380                                                 else
381                                                 {
382                                                         // only when an assembly name is used in a generic type parameter, will it be escaped
383                                                         assemblyName = typeName.Substring(pos);
384                                                 }
385                                                 Check(assemblyName.Length != 0);
386                                         }
387                                 }
388                                 else
389                                 {
390                                         Check(!genericParameter);
391                                 }
392                         }
393
394                         private short ParseArray()
395                         {
396                                 SkipWhiteSpace();
397                                 Check(pos < typeName.Length);
398                                 char c = typeName[pos];
399                                 if (c == ']')
400                                 {
401                                         return SZARRAY;
402                                 }
403                                 else if (c == '*')
404                                 {
405                                         pos++;
406                                         SkipWhiteSpace();
407                                         return 1;
408                                 }
409                                 else
410                                 {
411                                         short rank = 1;
412                                         while (TryConsume(','))
413                                         {
414                                                 Check(rank < short.MaxValue);
415                                                 rank++;
416                                                 SkipWhiteSpace();
417                                         }
418                                         return rank;
419                                 }
420                         }
421
422                         private void SkipWhiteSpace()
423                         {
424                                 while (pos < typeName.Length && Char.IsWhiteSpace(typeName[pos]))
425                                 {
426                                         pos++;
427                                 }
428                         }
429
430                         private static void Add<T>(ref T[] array, T elem)
431                         {
432                                 if (array == null)
433                                 {
434                                         array = new T[] { elem };
435                                         return;
436                                 }
437                                 Array.Resize(ref array, array.Length + 1);
438                                 array[array.Length - 1] = elem;
439                         }
440                 }
441
442                 internal Type GetType(Universe universe, Assembly context, bool throwOnError, string originalName)
443                 {
444                         Type type;
445                         if (assemblyName != null)
446                         {
447                                 Assembly asm = universe.Load(assemblyName, context, throwOnError);
448                                 if (asm == null)
449                                 {
450                                         return null;
451                                 }
452                                 type = asm.GetTypeImpl(name);
453                         }
454                         else if (context == null)
455                         {
456                                 type = universe.Mscorlib.GetTypeImpl(name);
457                         }
458                         else
459                         {
460                                 type = context.GetTypeImpl(name);
461                                 if (type == null && context != universe.Mscorlib)
462                                 {
463                                         type = universe.Mscorlib.GetTypeImpl(name);
464                                 }
465                         }
466                         return Expand(type, context, throwOnError, originalName);
467                 }
468
469                 internal Type Expand(Type type, Assembly context, bool throwOnError, string originalName)
470                 {
471                         if (type == null)
472                         {
473                                 if (throwOnError)
474                                 {
475                                         throw new TypeLoadException(originalName);
476                                 }
477                                 return null;
478                         }
479                         if (nested != null)
480                         {
481                                 foreach (string nest in nested)
482                                 {
483                                         type = type.FindNestedType(TypeName.Split(TypeNameParser.Unescape(nest)));
484                                         if (type == null)
485                                         {
486                                                 if (throwOnError)
487                                                 {
488                                                         throw new TypeLoadException(originalName);
489                                                 }
490                                                 return null;
491                                         }
492                                 }
493                         }
494                         if (genericParameters != null)
495                         {
496                                 Type[] typeArgs = new Type[genericParameters.Length];
497                                 for (int i = 0; i < typeArgs.Length; i++)
498                                 {
499                                         typeArgs[i] = genericParameters[i].GetType(type.Assembly.universe, context, throwOnError, originalName);
500                                         if (typeArgs[i] == null)
501                                         {
502                                                 return null;
503                                         }
504                                 }
505                                 type = type.MakeGenericType(typeArgs);
506                         }
507                         if (modifiers != null)
508                         {
509                                 foreach (short modifier in modifiers)
510                                 {
511                                         switch (modifier)
512                                         {
513                                                 case SZARRAY:
514                                                         type = type.MakeArrayType();
515                                                         break;
516                                                 case BYREF:
517                                                         type = type.MakeByRefType();
518                                                         break;
519                                                 case POINTER:
520                                                         type = type.MakePointerType();
521                                                         break;
522                                                 default:
523                                                         type = type.MakeArrayType(modifier);
524                                                         break;
525                                         }
526                                 }
527                         }
528                         return type;
529                 }
530         }
531 }