Update to the latest IKVM which now has support for missing types
[mono.git] / mcs / class / IKVM.Reflection / TypeNameParser.cs
1 /*
2   Copyright (C) 2009 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         struct TypeNameParser
31         {
32                 private const string SpecialChars = "\\+,[]*&";
33                 private const short SZARRAY = -1;
34                 private const short BYREF = -2;
35                 private const short POINTER = -3;
36                 private readonly string name;
37                 private readonly string[] nested;
38                 private readonly string assemblyName;
39                 private readonly short[] modifiers;
40                 private readonly TypeNameParser[] genericParameters;
41
42                 internal static string Escape(string name)
43                 {
44                         if (name == null)
45                         {
46                                 return null;
47                         }
48                         StringBuilder sb = null;
49                         for (int pos = 0; pos < name.Length; pos++)
50                         {
51                                 char c = name[pos];
52                                 if (SpecialChars.IndexOf(c) != -1)
53                                 {
54                                         if (sb == null)
55                                         {
56                                                 sb = new StringBuilder(name, 0, pos, name.Length + 3);
57                                         }
58                                         sb.Append('\\').Append(c);
59                                 }
60                                 else if (sb != null)
61                                 {
62                                         sb.Append(c);
63                                 }
64                         }
65                         return sb != null ? sb.ToString() : name;
66                 }
67
68                 internal static string Unescape(string name)
69                 {
70                         int pos = name.IndexOf('\\');
71                         if (pos == -1)
72                         {
73                                 return name;
74                         }
75                         StringBuilder sb = new StringBuilder(name, 0, pos, name.Length - 1);
76                         for (; pos < name.Length; pos++)
77                         {
78                                 char c = name[pos];
79                                 if (c == '\\')
80                                 {
81                                         c = name[++pos];
82                                 }
83                                 sb.Append(c);
84                         }
85                         return sb.ToString();
86                 }
87
88                 internal static TypeNameParser Parse(string typeName, bool throwOnError)
89                 {
90                         if (throwOnError)
91                         {
92                                 Parser parser = new Parser(typeName);
93                                 return new TypeNameParser(ref parser, true);
94                         }
95                         else
96                         {
97                                 try
98                                 {
99                                         Parser parser = new Parser(typeName);
100                                         return new TypeNameParser(ref parser, true);
101                                 }
102                                 catch (ArgumentException)
103                                 {
104                                         return new TypeNameParser();
105                                 }
106                         }
107                 }
108
109                 private TypeNameParser(ref Parser parser, bool withAssemblyName)
110                 {
111                         bool genericParameter = parser.pos != 0;
112                         name = parser.NextNamePart();
113                         nested = null;
114                         parser.ParseNested(ref nested);
115                         genericParameters = null;
116                         parser.ParseGenericParameters(ref genericParameters);
117                         modifiers = null;
118                         parser.ParseModifiers(ref modifiers);
119                         assemblyName = null;
120                         if (withAssemblyName)
121                         {
122                                 parser.ParseAssemblyName(genericParameter, ref assemblyName);
123                         }
124                 }
125
126                 internal bool Error
127                 {
128                         get { return name == null; }
129                 }
130
131                 internal string FirstNamePart
132                 {
133                         get { return name; }
134                 }
135
136                 internal string AssemblyName
137                 {
138                         get { return assemblyName; }
139                 }
140
141                 private struct Parser
142                 {
143                         private readonly string typeName;
144                         internal int pos;
145
146                         internal Parser(string typeName)
147                         {
148                                 this.typeName = typeName;
149                                 this.pos = 0;
150                         }
151
152                         private void Check(bool condition)
153                         {
154                                 if (!condition)
155                                 {
156                                         throw new ArgumentException("Invalid type name '" + typeName + "'");
157                                 }
158                         }
159
160                         private void Consume(char c)
161                         {
162                                 Check(pos < typeName.Length && typeName[pos++] == c);
163                         }
164
165                         private bool TryConsume(char c)
166                         {
167                                 if (pos < typeName.Length && typeName[pos] == c)
168                                 {
169                                         pos++;
170                                         return true;
171                                 }
172                                 else
173                                 {
174                                         return false;
175                                 }
176                         }
177
178                         internal string NextNamePart()
179                         {
180                                 SkipWhiteSpace();
181                                 int start = pos;
182                                 for (; pos < typeName.Length; pos++)
183                                 {
184                                         char c = typeName[pos];
185                                         if (c == '\\')
186                                         {
187                                                 pos++;
188                                                 Check(pos < typeName.Length && SpecialChars.IndexOf(typeName[pos]) != -1);
189                                         }
190                                         else if (SpecialChars.IndexOf(c) != -1)
191                                         {
192                                                 break;
193                                         }
194                                 }
195                                 Check(pos - start != 0);
196                                 if (start == 0 && pos == typeName.Length)
197                                 {
198                                         return typeName;
199                                 }
200                                 else
201                                 {
202                                         return typeName.Substring(start, pos - start);
203                                 }
204                         }
205
206                         internal void ParseNested(ref string[] nested)
207                         {
208                                 while (TryConsume('+'))
209                                 {
210                                         Add(ref nested, NextNamePart());
211                                 }
212                         }
213
214                         internal void ParseGenericParameters(ref TypeNameParser[] genericParameters)
215                         {
216                                 int saved = pos;
217                                 if (TryConsume('['))
218                                 {
219                                         SkipWhiteSpace();
220                                         if (TryConsume(']') || TryConsume('*') || TryConsume(','))
221                                         {
222                                                 // it's not a generic parameter list, but an array instead
223                                                 pos = saved;
224                                                 return;
225                                         }
226                                         do
227                                         {
228                                                 SkipWhiteSpace();
229                                                 if (TryConsume('['))
230                                                 {
231                                                         Add(ref genericParameters, new TypeNameParser(ref this, true));
232                                                         Consume(']');
233                                                 }
234                                                 else
235                                                 {
236                                                         Add(ref genericParameters, new TypeNameParser(ref this, false));
237                                                 }
238                                         }
239                                         while (TryConsume(','));
240                                         Consume(']');
241                                         SkipWhiteSpace();
242                                 }
243                         }
244
245                         internal void ParseModifiers(ref short[] modifiers)
246                         {
247                                 while (pos < typeName.Length)
248                                 {
249                                         switch (typeName[pos])
250                                         {
251                                                 case '*':
252                                                         pos++;
253                                                         Add(ref modifiers, POINTER);
254                                                         break;
255                                                 case '&':
256                                                         pos++;
257                                                         Add(ref modifiers, BYREF);
258                                                         break;
259                                                 case '[':
260                                                         pos++;
261                                                         Add(ref modifiers, ParseArray());
262                                                         Consume(']');
263                                                         break;
264                                                 default:
265                                                         return;
266                                         }
267                                         SkipWhiteSpace();
268                                 }
269                         }
270
271                         internal void ParseAssemblyName(bool genericParameter, ref string assemblyName)
272                         {
273                                 if (pos < typeName.Length)
274                                 {
275                                         if (typeName[pos] == ']' && genericParameter)
276                                         {
277                                                 // ok
278                                         }
279                                         else
280                                         {
281                                                 Consume(',');
282                                                 SkipWhiteSpace();
283                                                 if (genericParameter)
284                                                 {
285                                                         int start = pos;
286                                                         while (pos < typeName.Length)
287                                                         {
288                                                                 char c = typeName[pos];
289                                                                 if (c == '\\')
290                                                                 {
291                                                                         pos++;
292                                                                         // a backslash itself is not legal in an assembly name, so we don't need to check for an escaped backslash
293                                                                         Check(pos < typeName.Length && typeName[pos++] == ']');
294                                                                 }
295                                                                 else if (c == ']')
296                                                                 {
297                                                                         break;
298                                                                 }
299                                                                 else
300                                                                 {
301                                                                         pos++;
302                                                                 }
303                                                         }
304                                                         Check(pos < typeName.Length && typeName[pos] == ']');
305                                                         assemblyName = typeName.Substring(start, pos - start).Replace("\\]", "]");
306                                                 }
307                                                 else
308                                                 {
309                                                         // only when an assembly name is used in a generic type parameter, will it be escaped
310                                                         assemblyName = typeName.Substring(pos);
311                                                 }
312                                                 Check(assemblyName.Length != 0);
313                                         }
314                                 }
315                                 else
316                                 {
317                                         Check(!genericParameter);
318                                 }
319                         }
320
321                         private short ParseArray()
322                         {
323                                 SkipWhiteSpace();
324                                 Check(pos < typeName.Length);
325                                 char c = typeName[pos];
326                                 if (c == ']')
327                                 {
328                                         return SZARRAY;
329                                 }
330                                 else if (c == '*')
331                                 {
332                                         pos++;
333                                         SkipWhiteSpace();
334                                         return 1;
335                                 }
336                                 else
337                                 {
338                                         short rank = 1;
339                                         while (TryConsume(','))
340                                         {
341                                                 Check(rank < short.MaxValue);
342                                                 rank++;
343                                                 SkipWhiteSpace();
344                                         }
345                                         return rank;
346                                 }
347                         }
348
349                         private void SkipWhiteSpace()
350                         {
351                                 while (pos < typeName.Length && Char.IsWhiteSpace(typeName[pos]))
352                                 {
353                                         pos++;
354                                 }
355                         }
356
357                         private static void Add<T>(ref T[] array, T elem)
358                         {
359                                 if (array == null)
360                                 {
361                                         array = new T[] { elem };
362                                         return;
363                                 }
364                                 Array.Resize(ref array, array.Length + 1);
365                                 array[array.Length - 1] = elem;
366                         }
367                 }
368
369                 internal Type GetType(Universe universe, Assembly context, bool throwOnError, string originalName)
370                 {
371                         Type type;
372                         if (assemblyName != null)
373                         {
374                                 Assembly asm = universe.Load(assemblyName, context, throwOnError);
375                                 if (asm == null)
376                                 {
377                                         return null;
378                                 }
379                                 type = asm.GetTypeImpl(name);
380                         }
381                         else if (context == null)
382                         {
383                                 type = universe.Mscorlib.GetTypeImpl(name);
384                         }
385                         else
386                         {
387                                 type = context.GetTypeImpl(name);
388                                 if (type == null && context != universe.Mscorlib)
389                                 {
390                                         type = universe.Mscorlib.GetTypeImpl(name);
391                                 }
392                         }
393                         return Expand(type, context, throwOnError, originalName);
394                 }
395
396                 internal Type Expand(Type type, Assembly context, bool throwOnError, string originalName)
397                 {
398                         if (type == null)
399                         {
400                                 if (throwOnError)
401                                 {
402                                         throw new TypeLoadException(originalName);
403                                 }
404                                 return null;
405                         }
406                         if (nested != null)
407                         {
408                                 foreach (string nest in nested)
409                                 {
410                                         type = type.GetNestedTypeCorrectly(TypeNameParser.Unescape(nest));
411                                         if (type == null)
412                                         {
413                                                 if (throwOnError)
414                                                 {
415                                                         throw new TypeLoadException(originalName);
416                                                 }
417                                                 return null;
418                                         }
419                                 }
420                         }
421                         if (genericParameters != null)
422                         {
423                                 Type[] typeArgs = new Type[genericParameters.Length];
424                                 for (int i = 0; i < typeArgs.Length; i++)
425                                 {
426                                         typeArgs[i] = genericParameters[i].GetType(type.Assembly.universe, context, throwOnError, originalName);
427                                         if (typeArgs[i] == null)
428                                         {
429                                                 return null;
430                                         }
431                                 }
432                                 type = type.MakeGenericType(typeArgs);
433                         }
434                         if (modifiers != null)
435                         {
436                                 foreach (short modifier in modifiers)
437                                 {
438                                         switch (modifier)
439                                         {
440                                                 case SZARRAY:
441                                                         type = type.MakeArrayType();
442                                                         break;
443                                                 case BYREF:
444                                                         type = type.MakeByRefType();
445                                                         break;
446                                                 case POINTER:
447                                                         type = type.MakePointerType();
448                                                         break;
449                                                 default:
450                                                         type = type.MakeArrayType(modifier);
451                                                         break;
452                                         }
453                                 }
454                         }
455                         return type;
456                 }
457         }
458 }