Merge pull request #217 from QuickJack/master
[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                                 switch (c)
126                                 {
127                                         case '\\':
128                                         case '+':
129                                         case ',':
130                                         case '[':
131                                         case ']':
132                                         case '*':
133                                         case '&':
134                                                 if (sb == null)
135                                                 {
136                                                         sb = new StringBuilder(name, 0, pos, name.Length + 3);
137                                                 }
138                                                 sb.Append("\\").Append(c);
139                                                 break;
140                                         default:
141                                                 if (sb != null)
142                                                 {
143                                                         sb.Append(c);
144                                                 }
145                                                 break;
146                                 }
147                         }
148                         return sb != null ? sb.ToString() : name;
149                 }
150
151                 internal static string Unescape(string name)
152                 {
153                         int pos = name.IndexOf('\\');
154                         if (pos == -1)
155                         {
156                                 return name;
157                         }
158                         StringBuilder sb = new StringBuilder(name, 0, pos, name.Length - 1);
159                         for (; pos < name.Length; pos++)
160                         {
161                                 char c = name[pos];
162                                 if (c == '\\')
163                                 {
164                                         c = name[++pos];
165                                 }
166                                 sb.Append(c);
167                         }
168                         return sb.ToString();
169                 }
170
171                 internal static TypeNameParser Parse(string typeName, bool throwOnError)
172                 {
173                         if (throwOnError)
174                         {
175                                 Parser parser = new Parser(typeName);
176                                 return new TypeNameParser(ref parser, true);
177                         }
178                         else
179                         {
180                                 try
181                                 {
182                                         Parser parser = new Parser(typeName);
183                                         return new TypeNameParser(ref parser, true);
184                                 }
185                                 catch (ArgumentException)
186                                 {
187                                         return new TypeNameParser();
188                                 }
189                         }
190                 }
191
192                 private TypeNameParser(ref Parser parser, bool withAssemblyName)
193                 {
194                         bool genericParameter = parser.pos != 0;
195                         name = parser.NextNamePart();
196                         nested = null;
197                         parser.ParseNested(ref nested);
198                         genericParameters = null;
199                         parser.ParseGenericParameters(ref genericParameters);
200                         modifiers = null;
201                         parser.ParseModifiers(ref modifiers);
202                         assemblyName = null;
203                         if (withAssemblyName)
204                         {
205                                 parser.ParseAssemblyName(genericParameter, ref assemblyName);
206                         }
207                 }
208
209                 internal bool Error
210                 {
211                         get { return name == null; }
212                 }
213
214                 internal string FirstNamePart
215                 {
216                         get { return name; }
217                 }
218
219                 internal string AssemblyName
220                 {
221                         get { return assemblyName; }
222                 }
223
224                 private struct Parser
225                 {
226                         private readonly string typeName;
227                         internal int pos;
228
229                         internal Parser(string typeName)
230                         {
231                                 this.typeName = typeName;
232                                 this.pos = 0;
233                         }
234
235                         private void Check(bool condition)
236                         {
237                                 if (!condition)
238                                 {
239                                         throw new ArgumentException("Invalid type name '" + typeName + "'");
240                                 }
241                         }
242
243                         private void Consume(char c)
244                         {
245                                 Check(pos < typeName.Length && typeName[pos++] == c);
246                         }
247
248                         private bool TryConsume(char c)
249                         {
250                                 if (pos < typeName.Length && typeName[pos] == c)
251                                 {
252                                         pos++;
253                                         return true;
254                                 }
255                                 else
256                                 {
257                                         return false;
258                                 }
259                         }
260
261                         internal string NextNamePart()
262                         {
263                                 SkipWhiteSpace();
264                                 int start = pos;
265                                 for (; pos < typeName.Length; pos++)
266                                 {
267                                         char c = typeName[pos];
268                                         if (c == '\\')
269                                         {
270                                                 pos++;
271                                                 Check(pos < typeName.Length && SpecialChars.IndexOf(typeName[pos]) != -1);
272                                         }
273                                         else if (SpecialChars.IndexOf(c) != -1)
274                                         {
275                                                 break;
276                                         }
277                                 }
278                                 Check(pos - start != 0);
279                                 if (start == 0 && pos == typeName.Length)
280                                 {
281                                         return typeName;
282                                 }
283                                 else
284                                 {
285                                         return typeName.Substring(start, pos - start);
286                                 }
287                         }
288
289                         internal void ParseNested(ref string[] nested)
290                         {
291                                 while (TryConsume('+'))
292                                 {
293                                         Add(ref nested, NextNamePart());
294                                 }
295                         }
296
297                         internal void ParseGenericParameters(ref TypeNameParser[] genericParameters)
298                         {
299                                 int saved = pos;
300                                 if (TryConsume('['))
301                                 {
302                                         SkipWhiteSpace();
303                                         if (TryConsume(']') || TryConsume('*') || TryConsume(','))
304                                         {
305                                                 // it's not a generic parameter list, but an array instead
306                                                 pos = saved;
307                                                 return;
308                                         }
309                                         do
310                                         {
311                                                 SkipWhiteSpace();
312                                                 if (TryConsume('['))
313                                                 {
314                                                         Add(ref genericParameters, new TypeNameParser(ref this, true));
315                                                         Consume(']');
316                                                 }
317                                                 else
318                                                 {
319                                                         Add(ref genericParameters, new TypeNameParser(ref this, false));
320                                                 }
321                                         }
322                                         while (TryConsume(','));
323                                         Consume(']');
324                                         SkipWhiteSpace();
325                                 }
326                         }
327
328                         internal void ParseModifiers(ref short[] modifiers)
329                         {
330                                 while (pos < typeName.Length)
331                                 {
332                                         switch (typeName[pos])
333                                         {
334                                                 case '*':
335                                                         pos++;
336                                                         Add(ref modifiers, POINTER);
337                                                         break;
338                                                 case '&':
339                                                         pos++;
340                                                         Add(ref modifiers, BYREF);
341                                                         break;
342                                                 case '[':
343                                                         pos++;
344                                                         Add(ref modifiers, ParseArray());
345                                                         Consume(']');
346                                                         break;
347                                                 default:
348                                                         return;
349                                         }
350                                         SkipWhiteSpace();
351                                 }
352                         }
353
354                         internal void ParseAssemblyName(bool genericParameter, ref string assemblyName)
355                         {
356                                 if (pos < typeName.Length)
357                                 {
358                                         if (typeName[pos] == ']' && genericParameter)
359                                         {
360                                                 // ok
361                                         }
362                                         else
363                                         {
364                                                 Consume(',');
365                                                 SkipWhiteSpace();
366                                                 if (genericParameter)
367                                                 {
368                                                         int start = pos;
369                                                         while (pos < typeName.Length)
370                                                         {
371                                                                 char c = typeName[pos];
372                                                                 if (c == '\\')
373                                                                 {
374                                                                         pos++;
375                                                                         // a backslash itself is not legal in an assembly name, so we don't need to check for an escaped backslash
376                                                                         Check(pos < typeName.Length && typeName[pos++] == ']');
377                                                                 }
378                                                                 else if (c == ']')
379                                                                 {
380                                                                         break;
381                                                                 }
382                                                                 else
383                                                                 {
384                                                                         pos++;
385                                                                 }
386                                                         }
387                                                         Check(pos < typeName.Length && typeName[pos] == ']');
388                                                         assemblyName = typeName.Substring(start, pos - start).Replace("\\]", "]");
389                                                 }
390                                                 else
391                                                 {
392                                                         // only when an assembly name is used in a generic type parameter, will it be escaped
393                                                         assemblyName = typeName.Substring(pos);
394                                                 }
395                                                 Check(assemblyName.Length != 0);
396                                         }
397                                 }
398                                 else
399                                 {
400                                         Check(!genericParameter);
401                                 }
402                         }
403
404                         private short ParseArray()
405                         {
406                                 SkipWhiteSpace();
407                                 Check(pos < typeName.Length);
408                                 char c = typeName[pos];
409                                 if (c == ']')
410                                 {
411                                         return SZARRAY;
412                                 }
413                                 else if (c == '*')
414                                 {
415                                         pos++;
416                                         SkipWhiteSpace();
417                                         return 1;
418                                 }
419                                 else
420                                 {
421                                         short rank = 1;
422                                         while (TryConsume(','))
423                                         {
424                                                 Check(rank < short.MaxValue);
425                                                 rank++;
426                                                 SkipWhiteSpace();
427                                         }
428                                         return rank;
429                                 }
430                         }
431
432                         private void SkipWhiteSpace()
433                         {
434                                 while (pos < typeName.Length && Char.IsWhiteSpace(typeName[pos]))
435                                 {
436                                         pos++;
437                                 }
438                         }
439
440                         private static void Add<T>(ref T[] array, T elem)
441                         {
442                                 if (array == null)
443                                 {
444                                         array = new T[] { elem };
445                                         return;
446                                 }
447                                 Array.Resize(ref array, array.Length + 1);
448                                 array[array.Length - 1] = elem;
449                         }
450                 }
451
452                 internal Type GetType(Universe universe, Assembly context, bool throwOnError, string originalName, bool resolve)
453                 {
454                         TypeName name = TypeName.Split(this.name);
455                         Type type;
456                         if (assemblyName != null)
457                         {
458                                 Assembly asm = universe.Load(assemblyName, context, throwOnError);
459                                 if (asm == null)
460                                 {
461                                         return null;
462                                 }
463                                 if (resolve)
464                                 {
465                                         type = asm.ResolveType(name);
466                                 }
467                                 else
468                                 {
469                                         type = asm.FindType(name);
470                                 }
471                         }
472                         else if (context == null)
473                         {
474                                 if (resolve)
475                                 {
476                                         type = universe.Mscorlib.ResolveType(name);
477                                 }
478                                 else
479                                 {
480                                         type = universe.Mscorlib.FindType(name);
481                                 }
482                         }
483                         else
484                         {
485                                 type = context.FindType(name);
486                                 if (type == null && context != universe.Mscorlib)
487                                 {
488                                         type = universe.Mscorlib.FindType(name);
489                                 }
490                                 if (type == null && resolve)
491                                 {
492                                         if (universe.Mscorlib.__IsMissing && !context.__IsMissing)
493                                         {
494                                                 type = universe.Mscorlib.ResolveType(name);
495                                         }
496                                         else
497                                         {
498                                                 type = context.ResolveType(name);
499                                         }
500                                 }
501                         }
502                         return Expand(type, context, throwOnError, originalName, resolve);
503                 }
504
505                 internal Type Expand(Type type, Assembly context, bool throwOnError, string originalName, bool resolve)
506                 {
507                         if (type == null)
508                         {
509                                 if (throwOnError)
510                                 {
511                                         throw new TypeLoadException(originalName);
512                                 }
513                                 return null;
514                         }
515                         if (nested != null)
516                         {
517                                 Type outer;
518                                 foreach (string nest in nested)
519                                 {
520                                         outer = type;
521                                         TypeName name = TypeName.Split(TypeNameParser.Unescape(nest));
522                                         type = outer.FindNestedType(name);
523                                         if (type == null)
524                                         {
525                                                 if (resolve)
526                                                 {
527                                                         type = outer.Module.universe.GetMissingTypeOrThrow(outer.Module, outer, name);
528                                                 }
529                                                 else if (throwOnError)
530                                                 {
531                                                         throw new TypeLoadException(originalName);
532                                                 }
533                                                 else
534                                                 {
535                                                         return null;
536                                                 }
537                                         }
538                                 }
539                         }
540                         if (genericParameters != null)
541                         {
542                                 Type[] typeArgs = new Type[genericParameters.Length];
543                                 for (int i = 0; i < typeArgs.Length; i++)
544                                 {
545                                         typeArgs[i] = genericParameters[i].GetType(type.Assembly.universe, context, throwOnError, originalName, resolve);
546                                         if (typeArgs[i] == null)
547                                         {
548                                                 return null;
549                                         }
550                                 }
551                                 type = type.MakeGenericType(typeArgs);
552                         }
553                         if (modifiers != null)
554                         {
555                                 foreach (short modifier in modifiers)
556                                 {
557                                         switch (modifier)
558                                         {
559                                                 case SZARRAY:
560                                                         type = type.MakeArrayType();
561                                                         break;
562                                                 case BYREF:
563                                                         type = type.MakeByRefType();
564                                                         break;
565                                                 case POINTER:
566                                                         type = type.MakePointerType();
567                                                         break;
568                                                 default:
569                                                         type = type.MakeArrayType(modifier);
570                                                         break;
571                                         }
572                                 }
573                         }
574                         return type;
575                 }
576         }
577 }