Merge pull request #301 from directhex/master
[mono.git] / mcs / class / IKVM.Reflection / TypeNameParser.cs
1 /*
2   Copyright (C) 2009-2012 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.Diagnostics;
27 using System.Text;
28
29 namespace IKVM.Reflection
30 {
31         // this respresents a type name as in metadata:
32         // - ns will be null for empty the namespace (never the empty string)
33         // - the strings are not escaped
34         struct TypeName : IEquatable<TypeName>
35         {
36                 private readonly string ns;
37                 private readonly string name;
38
39                 internal TypeName(string ns, string name)
40                 {
41                         if (name == null)
42                         {
43                                 throw new ArgumentNullException("name");
44                         }
45                         this.ns = ns;
46                         this.name = name;
47                 }
48
49                 internal string Name
50                 {
51                         get { return name; }
52                 }
53
54                 internal string Namespace
55                 {
56                         get { return ns; }
57                 }
58
59                 public static bool operator ==(TypeName o1, TypeName o2)
60                 {
61                         return o1.ns == o2.ns && o1.name == o2.name;
62                 }
63
64                 public static bool operator !=(TypeName o1, TypeName o2)
65                 {
66                         return o1.ns != o2.ns || o1.name != o2.name;
67                 }
68
69                 public override int GetHashCode()
70                 {
71                         return ns == null ? name.GetHashCode() : ns.GetHashCode() * 37 + name.GetHashCode();
72                 }
73
74                 public override bool Equals(object obj)
75                 {
76                         TypeName? other = obj as TypeName?;
77                         return other != null && other.Value == this;
78                 }
79
80                 public override string ToString()
81                 {
82                         return ns == null ? name : ns + "." + name;
83                 }
84
85                 bool IEquatable<TypeName>.Equals(TypeName other)
86                 {
87                         return this == other;
88                 }
89
90                 internal TypeName ToLowerInvariant()
91                 {
92                         return new TypeName(ns == null ? null : ns.ToLowerInvariant(), name.ToLowerInvariant());
93                 }
94
95                 internal static TypeName Split(string name)
96                 {
97                         int dot = name.LastIndexOf('.');
98                         if (dot == -1)
99                         {
100                                 return new TypeName(null, name);
101                         }
102                         else
103                         {
104                                 return new TypeName(name.Substring(0, dot), name.Substring(dot + 1));
105                         }
106                 }
107         }
108
109         struct TypeNameParser
110         {
111                 private const string SpecialChars = "\\+,[]*&";
112                 private const short SZARRAY = -1;
113                 private const short BYREF = -2;
114                 private const short POINTER = -3;
115                 private readonly string name;
116                 private readonly string[] nested;
117                 private readonly string assemblyName;
118                 private readonly short[] modifiers;
119                 private readonly TypeNameParser[] genericParameters;
120
121                 internal static string Escape(string name)
122                 {
123                         if (name == null)
124                         {
125                                 return null;
126                         }
127                         StringBuilder sb = null;
128                         for (int pos = 0; pos < name.Length; pos++)
129                         {
130                                 char c = name[pos];
131                                 switch (c)
132                                 {
133                                         case '\\':
134                                         case '+':
135                                         case ',':
136                                         case '[':
137                                         case ']':
138                                         case '*':
139                                         case '&':
140                                                 if (sb == null)
141                                                 {
142                                                         sb = new StringBuilder(name, 0, pos, name.Length + 3);
143                                                 }
144                                                 sb.Append("\\").Append(c);
145                                                 break;
146                                         default:
147                                                 if (sb != null)
148                                                 {
149                                                         sb.Append(c);
150                                                 }
151                                                 break;
152                                 }
153                         }
154                         return sb != null ? sb.ToString() : name;
155                 }
156
157                 internal static string Unescape(string name)
158                 {
159                         int pos = name.IndexOf('\\');
160                         if (pos == -1)
161                         {
162                                 return name;
163                         }
164                         StringBuilder sb = new StringBuilder(name, 0, pos, name.Length - 1);
165                         for (; pos < name.Length; pos++)
166                         {
167                                 char c = name[pos];
168                                 if (c == '\\')
169                                 {
170                                         c = name[++pos];
171                                 }
172                                 sb.Append(c);
173                         }
174                         return sb.ToString();
175                 }
176
177                 internal static TypeNameParser Parse(string typeName, bool throwOnError)
178                 {
179                         if (throwOnError)
180                         {
181                                 Parser parser = new Parser(typeName);
182                                 return new TypeNameParser(ref parser, true);
183                         }
184                         else
185                         {
186                                 try
187                                 {
188                                         Parser parser = new Parser(typeName);
189                                         return new TypeNameParser(ref parser, true);
190                                 }
191                                 catch (ArgumentException)
192                                 {
193                                         return new TypeNameParser();
194                                 }
195                         }
196                 }
197
198                 private TypeNameParser(ref Parser parser, bool withAssemblyName)
199                 {
200                         bool genericParameter = parser.pos != 0;
201                         name = parser.NextNamePart();
202                         nested = null;
203                         parser.ParseNested(ref nested);
204                         genericParameters = null;
205                         parser.ParseGenericParameters(ref genericParameters);
206                         modifiers = null;
207                         parser.ParseModifiers(ref modifiers);
208                         assemblyName = null;
209                         if (withAssemblyName)
210                         {
211                                 parser.ParseAssemblyName(genericParameter, ref assemblyName);
212                         }
213                 }
214
215                 internal bool Error
216                 {
217                         get { return name == null; }
218                 }
219
220                 internal string FirstNamePart
221                 {
222                         get { return name; }
223                 }
224
225                 internal string AssemblyName
226                 {
227                         get { return assemblyName; }
228                 }
229
230                 private struct Parser
231                 {
232                         private readonly string typeName;
233                         internal int pos;
234
235                         internal Parser(string typeName)
236                         {
237                                 this.typeName = typeName;
238                                 this.pos = 0;
239                         }
240
241                         private void Check(bool condition)
242                         {
243                                 if (!condition)
244                                 {
245                                         throw new ArgumentException("Invalid type name '" + typeName + "'");
246                                 }
247                         }
248
249                         private void Consume(char c)
250                         {
251                                 Check(pos < typeName.Length && typeName[pos++] == c);
252                         }
253
254                         private bool TryConsume(char c)
255                         {
256                                 if (pos < typeName.Length && typeName[pos] == c)
257                                 {
258                                         pos++;
259                                         return true;
260                                 }
261                                 else
262                                 {
263                                         return false;
264                                 }
265                         }
266
267                         internal string NextNamePart()
268                         {
269                                 SkipWhiteSpace();
270                                 int start = pos;
271                                 for (; pos < typeName.Length; pos++)
272                                 {
273                                         char c = typeName[pos];
274                                         if (c == '\\')
275                                         {
276                                                 pos++;
277                                                 Check(pos < typeName.Length && SpecialChars.IndexOf(typeName[pos]) != -1);
278                                         }
279                                         else if (SpecialChars.IndexOf(c) != -1)
280                                         {
281                                                 break;
282                                         }
283                                 }
284                                 Check(pos - start != 0);
285                                 if (start == 0 && pos == typeName.Length)
286                                 {
287                                         return typeName;
288                                 }
289                                 else
290                                 {
291                                         return typeName.Substring(start, pos - start);
292                                 }
293                         }
294
295                         internal void ParseNested(ref string[] nested)
296                         {
297                                 while (TryConsume('+'))
298                                 {
299                                         Add(ref nested, NextNamePart());
300                                 }
301                         }
302
303                         internal void ParseGenericParameters(ref TypeNameParser[] genericParameters)
304                         {
305                                 int saved = pos;
306                                 if (TryConsume('['))
307                                 {
308                                         SkipWhiteSpace();
309                                         if (TryConsume(']') || TryConsume('*') || TryConsume(','))
310                                         {
311                                                 // it's not a generic parameter list, but an array instead
312                                                 pos = saved;
313                                                 return;
314                                         }
315                                         do
316                                         {
317                                                 SkipWhiteSpace();
318                                                 if (TryConsume('['))
319                                                 {
320                                                         Add(ref genericParameters, new TypeNameParser(ref this, true));
321                                                         Consume(']');
322                                                 }
323                                                 else
324                                                 {
325                                                         Add(ref genericParameters, new TypeNameParser(ref this, false));
326                                                 }
327                                         }
328                                         while (TryConsume(','));
329                                         Consume(']');
330                                         SkipWhiteSpace();
331                                 }
332                         }
333
334                         internal void ParseModifiers(ref short[] modifiers)
335                         {
336                                 while (pos < typeName.Length)
337                                 {
338                                         switch (typeName[pos])
339                                         {
340                                                 case '*':
341                                                         pos++;
342                                                         Add(ref modifiers, POINTER);
343                                                         break;
344                                                 case '&':
345                                                         pos++;
346                                                         Add(ref modifiers, BYREF);
347                                                         break;
348                                                 case '[':
349                                                         pos++;
350                                                         Add(ref modifiers, ParseArray());
351                                                         Consume(']');
352                                                         break;
353                                                 default:
354                                                         return;
355                                         }
356                                         SkipWhiteSpace();
357                                 }
358                         }
359
360                         internal void ParseAssemblyName(bool genericParameter, ref string assemblyName)
361                         {
362                                 if (pos < typeName.Length)
363                                 {
364                                         if (typeName[pos] == ']' && genericParameter)
365                                         {
366                                                 // ok
367                                         }
368                                         else
369                                         {
370                                                 Consume(',');
371                                                 SkipWhiteSpace();
372                                                 if (genericParameter)
373                                                 {
374                                                         int start = pos;
375                                                         while (pos < typeName.Length)
376                                                         {
377                                                                 char c = typeName[pos];
378                                                                 if (c == '\\')
379                                                                 {
380                                                                         pos++;
381                                                                         // a backslash itself is not legal in an assembly name, so we don't need to check for an escaped backslash
382                                                                         Check(pos < typeName.Length && typeName[pos++] == ']');
383                                                                 }
384                                                                 else if (c == ']')
385                                                                 {
386                                                                         break;
387                                                                 }
388                                                                 else
389                                                                 {
390                                                                         pos++;
391                                                                 }
392                                                         }
393                                                         Check(pos < typeName.Length && typeName[pos] == ']');
394                                                         assemblyName = typeName.Substring(start, pos - start).Replace("\\]", "]");
395                                                 }
396                                                 else
397                                                 {
398                                                         // only when an assembly name is used in a generic type parameter, will it be escaped
399                                                         assemblyName = typeName.Substring(pos);
400                                                 }
401                                                 Check(assemblyName.Length != 0);
402                                         }
403                                 }
404                                 else
405                                 {
406                                         Check(!genericParameter);
407                                 }
408                         }
409
410                         private short ParseArray()
411                         {
412                                 SkipWhiteSpace();
413                                 Check(pos < typeName.Length);
414                                 char c = typeName[pos];
415                                 if (c == ']')
416                                 {
417                                         return SZARRAY;
418                                 }
419                                 else if (c == '*')
420                                 {
421                                         pos++;
422                                         SkipWhiteSpace();
423                                         return 1;
424                                 }
425                                 else
426                                 {
427                                         short rank = 1;
428                                         while (TryConsume(','))
429                                         {
430                                                 Check(rank < short.MaxValue);
431                                                 rank++;
432                                                 SkipWhiteSpace();
433                                         }
434                                         return rank;
435                                 }
436                         }
437
438                         private void SkipWhiteSpace()
439                         {
440                                 while (pos < typeName.Length && Char.IsWhiteSpace(typeName[pos]))
441                                 {
442                                         pos++;
443                                 }
444                         }
445
446                         private static void Add<T>(ref T[] array, T elem)
447                         {
448                                 if (array == null)
449                                 {
450                                         array = new T[] { elem };
451                                         return;
452                                 }
453                                 Array.Resize(ref array, array.Length + 1);
454                                 array[array.Length - 1] = elem;
455                         }
456                 }
457
458                 internal Type GetType(Universe universe, Assembly context, bool throwOnError, string originalName, bool resolve, bool ignoreCase)
459                 {
460                         Debug.Assert(!resolve || !ignoreCase);
461                         TypeName name = TypeName.Split(this.name);
462                         Type type;
463                         if (assemblyName != null)
464                         {
465                                 Assembly asm = universe.Load(assemblyName, context, throwOnError);
466                                 if (asm == null)
467                                 {
468                                         return null;
469                                 }
470                                 if (resolve)
471                                 {
472                                         type = asm.ResolveType(name);
473                                 }
474                                 else if (ignoreCase)
475                                 {
476                                         type = asm.FindTypeIgnoreCase(name.ToLowerInvariant());
477                                 }
478                                 else
479                                 {
480                                         type = asm.FindType(name);
481                                 }
482                         }
483                         else if (context == null)
484                         {
485                                 if (resolve)
486                                 {
487                                         type = universe.Mscorlib.ResolveType(name);
488                                 }
489                                 else if (ignoreCase)
490                                 {
491                                         type = universe.Mscorlib.FindTypeIgnoreCase(name.ToLowerInvariant());
492                                 }
493                                 else
494                                 {
495                                         type = universe.Mscorlib.FindType(name);
496                                 }
497                         }
498                         else
499                         {
500                                 if (ignoreCase)
501                                 {
502                                         name = name.ToLowerInvariant();
503                                         type = context.FindTypeIgnoreCase(name);
504                                 }
505                                 else
506                                 {
507                                         type = context.FindType(name);
508                                 }
509                                 if (type == null && context != universe.Mscorlib)
510                                 {
511                                         if (ignoreCase)
512                                         {
513                                                 type = universe.Mscorlib.FindTypeIgnoreCase(name);
514                                         }
515                                         else
516                                         {
517                                                 type = universe.Mscorlib.FindType(name);
518                                         }
519                                 }
520                                 if (type == null && resolve)
521                                 {
522                                         if (universe.Mscorlib.__IsMissing && !context.__IsMissing)
523                                         {
524                                                 type = universe.Mscorlib.ResolveType(name);
525                                         }
526                                         else
527                                         {
528                                                 type = context.ResolveType(name);
529                                         }
530                                 }
531                         }
532                         return Expand(type, context, throwOnError, originalName, resolve, ignoreCase);
533                 }
534
535                 internal Type Expand(Type type, Assembly context, bool throwOnError, string originalName, bool resolve, bool ignoreCase)
536                 {
537                         Debug.Assert(!resolve || !ignoreCase);
538                         if (type == null)
539                         {
540                                 if (throwOnError)
541                                 {
542                                         throw new TypeLoadException(originalName);
543                                 }
544                                 return null;
545                         }
546                         if (nested != null)
547                         {
548                                 Type outer;
549                                 foreach (string nest in nested)
550                                 {
551                                         outer = type;
552                                         TypeName name = TypeName.Split(TypeNameParser.Unescape(nest));
553                                         type = ignoreCase
554                                                 ? outer.FindNestedTypeIgnoreCase(name.ToLowerInvariant())
555                                                 : outer.FindNestedType(name);
556                                         if (type == null)
557                                         {
558                                                 if (resolve)
559                                                 {
560                                                         type = outer.Module.universe.GetMissingTypeOrThrow(outer.Module, outer, name);
561                                                 }
562                                                 else if (throwOnError)
563                                                 {
564                                                         throw new TypeLoadException(originalName);
565                                                 }
566                                                 else
567                                                 {
568                                                         return null;
569                                                 }
570                                         }
571                                 }
572                         }
573                         if (genericParameters != null)
574                         {
575                                 Type[] typeArgs = new Type[genericParameters.Length];
576                                 for (int i = 0; i < typeArgs.Length; i++)
577                                 {
578                                         typeArgs[i] = genericParameters[i].GetType(type.Assembly.universe, context, throwOnError, originalName, resolve, ignoreCase);
579                                         if (typeArgs[i] == null)
580                                         {
581                                                 return null;
582                                         }
583                                 }
584                                 type = type.MakeGenericType(typeArgs);
585                         }
586                         if (modifiers != null)
587                         {
588                                 foreach (short modifier in modifiers)
589                                 {
590                                         switch (modifier)
591                                         {
592                                                 case SZARRAY:
593                                                         type = type.MakeArrayType();
594                                                         break;
595                                                 case BYREF:
596                                                         type = type.MakeByRefType();
597                                                         break;
598                                                 case POINTER:
599                                                         type = type.MakePointerType();
600                                                         break;
601                                                 default:
602                                                         type = type.MakeArrayType(modifier);
603                                                         break;
604                                         }
605                                 }
606                         }
607                         return type;
608                 }
609         }
610 }