Merge pull request #2003 from esdrubal/seq_test_fix2
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Expression.cs
1 //
2 // Expression.cs: Stores references to items or properties.
3 //
4 // Authors:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Marek Safar (marek.safar@gmail.com)
7 // 
8 // (C) 2005 Marek Sieradzki
9 // Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30 using System;
31 using System.IO;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Text;
35 using System.Text.RegularExpressions;
36 using Mono.XBuild.Utilities;
37
38 namespace Microsoft.Build.BuildEngine {
39
40         // Properties and items are processed in two ways
41         // 1. Evaluate, Project calls evaluate on all the item and property groups.
42         //    At this time, the items are fully expanded, all item and property
43         //    references are expanded to get the item's value.
44         //    Properties on the other hand, expand property refs, but _not_
45         //    item references.
46         //
47         // 2. After the 'evaluation' phase, this could be when executing a target/task,
48         //    - Items : no expansion required, as they are already at final value
49         //    - Properties: Item references get expanded now, in the context of the
50         //      batching
51         //
52         // The enum ExpressionOptions is for specifying this expansion of item references.
53         //
54         // GroupingCollection.Evaluate, evaluates all properties and then items
55
56         internal class Expression {
57         
58                 enum TokenKind
59                 {
60                         OpenParens,
61                         CloseParens,
62                         Dot,
63                         End
64                 }
65
66                 ExpressionCollection expressionCollection;
67
68                 static Regex item_regex;
69                 static Regex metadata_regex;
70         
71                 public Expression ()
72                 {
73                         this.expressionCollection = new ExpressionCollection ();
74                 }
75
76                 public static T ParseAs<T> (string expression, ParseOptions options, Project project)
77                 {
78                         Expression expr = new Expression ();
79                         expr.Parse (expression, options);
80                         return (T)expr.ConvertTo (project, typeof (T));
81                 }
82
83                 public static T ParseAs<T> (string expression, ParseOptions options, Project project, ExpressionOptions exprOptions)
84                 {
85                         Expression expr = new Expression ();
86                         expr.Parse (expression, options);
87                         return (T)expr.ConvertTo (project, typeof (T), exprOptions);
88                 }
89
90                 // Split: Split on ';'
91                 //         Eg. Property values don't need to be split
92                 //
93                 // AllowItems: if false, item refs should not be treated as item refs!
94                 //              it converts them to strings in the final expressionCollection
95                 //
96                 // AllowMetadata: same as AllowItems, for metadata
97                 public void Parse (string expression, ParseOptions options)
98                 {
99                         bool split = (options & ParseOptions.Split) == ParseOptions.Split;
100                         bool allowItems = (options & ParseOptions.AllowItems) == ParseOptions.AllowItems;
101                         bool allowMd = (options & ParseOptions.AllowMetadata) == ParseOptions.AllowMetadata;
102
103                         expression = expression.Replace ('\\', Path.DirectorySeparatorChar);
104                 
105                         string [] parts;
106                         if (split)
107                                 parts = expression.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
108                         else
109                                 parts = new string [] { expression };
110
111                         // TODO: Too complicated, each part parses only its known part
112                         // we should simply do it in one go and avoid all this parts code madness
113
114                         List <ArrayList> p1 = new List <ArrayList> (parts.Length);
115                         List <ArrayList> p2 = new List <ArrayList> (parts.Length);
116                         List <ArrayList> p3 = new List <ArrayList> (parts.Length);
117
118                         Prepare (p1, parts.Length);
119                         Prepare (p2, parts.Length);
120                         Prepare (p3, parts.Length);
121
122                         for (int i = 0; i < parts.Length; i++)
123                                 p1 [i] = SplitItems (parts [i], allowItems);
124
125                         for (int i = 0; i < parts.Length; i++) {
126                                 p2 [i] = new ArrayList ();
127                                 foreach (object o in p1 [i]) {
128                                         if (o is string)
129                                                 p2 [i].AddRange (ExtractProperties ((string) o));
130                                         else
131                                                 p2 [i].Add (o);
132                                 }
133                         }
134
135                         for (int i = 0; i < parts.Length; i++) {
136                                 p3 [i] = new ArrayList ();
137                                 foreach (object o in p2 [i]) {
138                                         if (o is string)
139                                                 p3 [i].AddRange (SplitMetadata ((string) o));
140                                         else
141                                                 p3 [i].Add (o);
142                                 }
143                         }
144
145                         CopyToExpressionCollection (p3, allowItems, allowMd);
146                 }
147
148                 void Prepare (List <ArrayList> l, int length)
149                 {
150                         for (int i = 0; i < length; i++)
151                                 l.Add (null);
152                 }
153                 
154                 void CopyToExpressionCollection (List <ArrayList> lists, bool allowItems, bool allowMd)
155                 {
156                         for (int i = 0; i < lists.Count; i++) {
157                                 foreach (object o in lists [i]) {
158                                         if (o is string)
159                                                 expressionCollection.Add (MSBuildUtils.Unescape ((string) o));
160                                         else if (!allowItems && o is ItemReference)
161                                                 expressionCollection.Add (((ItemReference) o).OriginalString);
162                                         else if (!allowMd && o is MetadataReference) {
163                                                 expressionCollection.Add (((MetadataReference) o).OriginalString);
164                                         }
165                                         else if (o is IReference)
166                                                 expressionCollection.Add ((IReference) o);
167                                 }
168                                 if (i < lists.Count - 1)
169                                         expressionCollection.Add (";");
170                         }
171                 }
172
173                 ArrayList SplitItems (string text, bool allowItems)
174                 {
175                         ArrayList phase1 = new ArrayList ();
176                         Match m;
177                         m = ItemRegex.Match (text);
178
179                         while (m.Success) {
180                                 string name = null, transform = null, separator = null;
181                                 ItemReference ir;
182                                 
183                                 name = m.Groups [ItemRegex.GroupNumberFromName ("itemname")].Value;
184                                 
185                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_transform")].Success)
186                                         transform = m.Groups [ItemRegex.GroupNumberFromName ("transform")].Value;
187                                 
188                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_separator")].Success)
189                                         separator = m.Groups [ItemRegex.GroupNumberFromName ("separator")].Value;
190
191                                 ir = new ItemReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
192                                                 name, transform, separator, m.Groups [0].Index, m.Groups [0].Length);
193                                 phase1.Add (ir);
194                                 m = m.NextMatch ();
195                         }
196
197                         ArrayList phase2 = new ArrayList ();
198                         int last_end = -1;
199                         int end = text.Length - 1;
200
201                         foreach (ItemReference ir in phase1) {
202                                 int a,b;
203
204                                 a = last_end;
205                                 b = ir.Start;
206
207                                 if (b - a - 1 > 0) {
208                                         phase2.Add (text.Substring (a + 1, b - a - 1));
209                                 }
210
211                                 last_end = ir.End;
212                                 phase2.Add (ir);
213                         }
214
215                         if (last_end < end)
216                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
217
218                         return phase2;
219                 }
220
221                 //
222                 // Parses property syntax
223                 //
224                 static List<object> ExtractProperties (string text)
225                 {
226                         var phase = new List<object> ();
227
228                         var     pos = text.IndexOf ("$(", StringComparison.Ordinal);
229                         if (pos < 0) {
230                                 phase.Add (text);
231                                 return phase;
232                         }
233
234                         if (pos != 0) {
235                                 // Extract any whitespaces before property reference
236                                 phase.Add (text.Substring (0, pos));
237                         }
238
239                         while (pos < text.Length) {
240                                 pos += 2;
241                                 int start = pos;
242                                 int end = 0;
243                                 bool requires_closing_parens = true;
244                                         
245                                 var ch = text [pos];
246                                 if ((ch == 'r' || ch == 'R') && text.Substring (pos + 1).StartsWith ("egistry:", StringComparison.OrdinalIgnoreCase)) {
247                                         pos += 9;
248                                         ParseRegistryFunction (text, pos);
249                                 } else {
250                                         while (char.IsWhiteSpace (ch))
251                                                 ch = text [pos++];
252
253                                         if (ch == '[') {
254                                                 phase.Add (ParsePropertyFunction (text, ref pos));
255                                         } else {
256                                                 // TODO: There is something like char index syntax as well: $(aa [10])
257                                                 // text.IndexOf ('[');
258
259                                                 end = text.IndexOf (')', pos) + 1;
260                                                 if (end > 0) {
261                                                         //
262                                                         // Instance string method, $(foo.Substring (0, 3))
263                                                         //
264                                                         var dot = text.IndexOf ('.', pos, end - pos);
265                                                         if (dot > 0) {
266                                                                 var name = text.Substring (start, dot - start);
267                                                                 ++dot;
268                                                                 var res = ParseInvocation (text, ref dot, null, new PropertyReference (name));
269                                                                 if (res != null) {
270                                                                         phase.Add (res);
271                                                                         end = dot;
272                                                                 }
273                                                         } else {
274                                                                 var name = text.Substring (start, end - start - 1);
275
276                                                                 //
277                                                                 // Check for wrong syntax e.g $(foo()
278                                                                 //
279                                                                 var open_parens = name.IndexOf ('(');
280                                                                 if (open_parens < 0) {
281                                                                         //
282                                                                         // Simple property reference $(Foo)
283                                                                         //
284                                                                         phase.Add (new PropertyReference (name));
285                                                                         requires_closing_parens = false;
286                                                                 } else {
287                                                                         end = 0;
288                                                                 }
289                                                         }
290                                                 }
291
292                                                 if (end == 0) {
293                                                         end = text.Length;
294                                                         start -= 2;
295                                                         phase.Add (text.Substring (start, end - start));
296                                                 }
297
298                                                 pos = end;
299                                         }
300
301                                         if (requires_closing_parens) {
302                                                 end = text.IndexOf (')', pos);
303                                                 if (end < 0)
304                                                         end = 0;
305                                                 else
306                                                         pos = end + 1;
307                                         }
308                                 }
309
310                                 end = text.IndexOf ("$(", pos, StringComparison.Ordinal);
311                                 if (end < 0)
312                                         end = text.Length;
313
314                                 if (end - pos > 0)
315                                         phase.Add (text.Substring (pos, end - pos));
316
317                                 pos = end;
318                         }
319
320                         return phase;
321                 }
322
323                 //
324                 // Property function with syntax $([Class]::Method(Parameters))
325                 //
326                 static MemberInvocationReference ParsePropertyFunction (string text, ref int pos)
327                 {
328                         int p = text.IndexOf ("]::", pos, StringComparison.Ordinal);
329                         if (p < 0)
330                                 throw new InvalidProjectFileException (string.Format ("Invalid static method invocation syntax '{0}'", text.Substring (pos)));
331
332                         var type_name = text.Substring (pos + 1, p - pos - 1);
333                         var type = GetTypeForStaticMethod (type_name);
334                         if (type == null) {
335                                 if (type_name.Contains ("."))
336                                         throw new InvalidProjectFileException (string.Format ("Invalid type '{0}' used in static method invocation", type_name));
337
338                                 throw new InvalidProjectFileException (string.Format ("'{0}': Static method invocation requires full type name to be used", type_name));
339                         }
340
341                         pos = p + 3;
342                         return ParseInvocation (text, ref pos, type, null);
343                 }
344
345                 //
346                 // Property function with syntax $(Registry:Call)
347                 //
348                 static void ParseRegistryFunction (string text, int pos)
349                 {
350                         throw new NotImplementedException ("Registry function");
351                 }
352
353                 static Type GetTypeForStaticMethod (string typeName)
354                 {
355                         //
356                         // In static property functions, you can use any static method or property of these system classes:
357                         //
358                         switch (typeName.ToLowerInvariant ()) {
359                         case "system.byte":
360                                 return typeof (byte);
361                         case "system.char":
362                                 return typeof (char);
363                         case "system.convert":
364                                 return typeof (Convert);
365                         case "system.datetime":
366                                 return typeof (DateTime);
367                         case "system.decimal":
368                                 return typeof (decimal);
369                         case "system.double":
370                                 return typeof (double);
371                         case "system.enum":
372                                 return typeof (Enum);
373                         case "system.guid":
374                                 return typeof (Guid);
375                         case "system.int16":
376                                 return typeof (Int16);
377                         case "system.int32":
378                                 return typeof (Int32);
379                         case "system.int64":
380                                 return typeof (Int64);
381                         case "system.io.path":
382                                 return typeof (System.IO.Path);
383                         case "system.math":
384                                 return typeof (Math);
385                         case "system.uint16":
386                                 return typeof (UInt16);
387                         case "system.uint32":
388                                 return typeof (UInt32);
389                         case "system.uint64":
390                                 return typeof (UInt64);
391                         case "system.sbyte":
392                                 return typeof (sbyte);
393                         case "system.single":
394                                 return typeof (float);
395                         case "system.string":
396                                 return typeof (string);
397                         case "system.stringcomparer":
398                                 return typeof (StringComparer);
399                         case "system.timespan":
400                                 return typeof (TimeSpan);
401                         case "system.text.regularexpressions.regex":
402                                 return typeof (System.Text.RegularExpressions.Regex);
403                         case "system.version":
404                                 return typeof (Version);
405                         case "microsoft.build.utilities.toollocationhelper":
406                                 throw new NotImplementedException (typeName);
407                         case "msbuild":
408                                 return typeof (PredefinedPropertyFunctions);
409                         case "system.environment":
410                                 return typeof (System.Environment);
411                         case "system.io.directory":
412                                 return typeof (System.IO.Directory);
413                         case "system.io.file":
414                                 return typeof (System.IO.File);
415                         }
416
417                         return null;
418                 }
419
420                 static bool IsMethodAllowed (Type type, string name)
421                 {
422                         if (type == typeof (System.Environment)) {
423                                 switch (name.ToLowerInvariant ()) {
424                                 case "commandline":
425                                 case "expandenvironmentvariables":
426                                 case "getenvironmentvariable":
427                                 case "getenvironmentvariables":
428                                 case "getfolderpath":
429                                 case "getlogicaldrives":
430                                         return true;
431                                 }
432
433                                 return false;
434                         }
435
436                         if (type == typeof (System.IO.Directory)) {
437                                 switch (name.ToLowerInvariant ()) {
438                                 case "getdirectories":
439                                 case "getfiles":
440                                 case "getlastaccesstime":
441                                 case "getlastwritetime":
442                                         return true;
443                                 }
444
445                                 return false;
446                         }
447
448                         if (type == typeof (System.IO.File)) {
449                                 switch (name.ToLowerInvariant ()) {
450                                 case "getcreationtime":
451                                 case "getattributes":
452                                 case "getlastaccesstime":
453                                 case "getlastwritetime":
454                                 case "readalltext":
455                                         return true;
456                                 }
457                         }
458
459                         return true;
460                 }
461
462                 static List<string> ParseArguments (string text, ref int pos)
463                 {
464                         List<string> args = new List<string> ();
465                         int parens = 0;
466                         bool backticks = false;
467                         int start = pos;
468                         for (; pos < text.Length; ++pos) {
469                                 var ch = text [pos];
470
471                                 if (ch == '`') {
472                                         backticks = !backticks;
473                                         continue;
474                                 }
475
476                                 if (backticks)
477                                         continue;
478
479                                 if (ch == '(') {
480                                         ++parens;
481                                         continue;
482                                 }
483
484                                 if (ch == ')') {
485                                         if (parens == 0) {
486                                                 var arg = text.Substring (start, pos - start).Trim ();
487                                                 if (arg.Length > 0)
488                                                         args.Add (arg);
489
490                                                 ++pos;
491                                                 return args;
492                                         }
493
494                                         --parens;
495                                         continue;
496                                 }
497
498                                 if (parens != 0)
499                                         continue;
500
501                                 if (ch == ',') {
502                                         args.Add (text.Substring (start, pos - start));
503                                         start = pos + 1;
504                                         continue;
505                                 }
506                         }
507
508                         // Invalid syntax
509                         return null;
510                 }
511
512                 static MemberInvocationReference ParseInvocation (string text, ref int p, Type type, IReference instance)
513                 {
514                         TokenKind token;
515                         MemberInvocationReference mir = null;
516
517                         while (true) {
518                                 int prev = p;
519                                 token = ScanName (text, ref p);
520                                 var name = text.Substring (prev, p - prev).TrimEnd ();
521
522                                 switch (token) {
523                                 case TokenKind.Dot:
524                                 case TokenKind.OpenParens:
525                                         break;
526                                 case TokenKind.CloseParens:
527                                         return new MemberInvocationReference (type, name) {
528                                                 Instance = instance
529                                         };
530
531                                 case TokenKind.End:
532                                         if (mir == null || name.Length != 0)
533                                                 throw new InvalidProjectFileException (string.Format ("Invalid static method invocation syntax '{0}'", text.Substring (p)));
534
535                                         return mir;
536                                 default:
537                                         throw new NotImplementedException ();
538                                 }
539
540                                 instance = mir = new MemberInvocationReference (type, name) {
541                                         Instance = instance
542                                 };
543
544                                 if (type != null) {
545                                         if (!IsMethodAllowed (type, name))
546                                                 throw new InvalidProjectFileException (string.Format ("The function '{0}' on type '{1}' has not been enabled for execution", name, type.FullName));
547
548                                         type = null;
549                                 }
550
551                                 if (token == TokenKind.OpenParens) {
552                                         ++p;
553                                         mir.Arguments = ParseArguments (text, ref p);
554                                 }
555
556                                 if (p < text.Length && text [p] == '.') {
557                                         ++p;
558                                         continue;
559                                 }
560
561                                 return mir;
562                         }
563                 }
564
565                 static TokenKind ScanName (string text, ref int p)
566                 {
567                         for (; p < text.Length; ++p) {
568                                 switch (text [p]) {
569                                 case '(':
570                                         return TokenKind.OpenParens;
571                                 case '.':
572                                         return TokenKind.Dot;
573                                 case ')':
574                                         return TokenKind.CloseParens;
575                                 }
576                         }
577
578                         return TokenKind.End;
579                 }
580
581                 ArrayList SplitMetadata (string text)
582                 {
583                         ArrayList phase1 = new ArrayList ();
584                         Match m;
585                         m = MetadataRegex.Match (text);
586
587                         while (m.Success) {
588                                 string name = null, meta = null;
589                                 MetadataReference mr;
590                                 
591                                 if (m.Groups [MetadataRegex.GroupNumberFromName ("name")].Success)
592                                         name = m.Groups [MetadataRegex.GroupNumberFromName ("name")].Value;
593                                 
594                                 meta = m.Groups [MetadataRegex.GroupNumberFromName ("meta")].Value;
595                                 
596                                 mr = new MetadataReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
597                                                                 name, meta, m.Groups [0].Index, m.Groups [0].Length);
598                                 phase1.Add (mr);
599                                 m = m.NextMatch ();
600                         }
601
602                         ArrayList phase2 = new ArrayList ();
603                         int last_end = -1;
604                         int end = text.Length - 1;
605
606                         foreach (MetadataReference mr in phase1) {
607                                 int a,b;
608
609                                 a = last_end;
610                                 b = mr.Start;
611
612                                 if (b - a - 1> 0) {
613                                         phase2.Add (text.Substring (a + 1, b - a - 1));
614                                 }
615
616                                 last_end = mr.End;
617                                 phase2.Add (mr);
618                         }
619
620                         if (last_end < end)
621                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
622
623                         return phase2;
624                 }
625
626                 public object ConvertTo (Project project, Type type)
627                 {
628                         return ConvertTo (project, type, ExpressionOptions.ExpandItemRefs);
629                 }
630
631                 public object ConvertTo (Project project, Type type, ExpressionOptions options)
632                 {
633                         return expressionCollection.ConvertTo (project, type, options);
634                 }
635
636                 public ExpressionCollection Collection {
637                         get { return expressionCollection; }
638                 }
639
640                 static Regex ItemRegex {
641                         get {
642                                 if (item_regex == null)
643                                         item_regex = new Regex (
644                                                 @"@\(\s*"
645                                                 + @"(?<itemname>[_A-Za-z][_\-0-9a-zA-Z]*)"
646                                                 + @"(?<has_transform>\s*->\s*'(?<transform>[^']*)')?"
647                                                 + @"(?<has_separator>\s*,\s*'(?<separator>[^']*)')?"
648                                                 + @"\s*\)");
649                                 return item_regex;
650                         }
651                 }
652                         
653                 static Regex MetadataRegex {
654                         get {
655                                 if (metadata_regex == null)
656                                         metadata_regex = new Regex (
657                                                 @"%\(\s*"
658                                                 + @"((?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)\.)?"
659                                                 + @"(?<meta>[_a-zA-Z][_\-0-9a-zA-Z]*)"
660                                                 + @"\s*\)");
661                                 return metadata_regex;
662                         }
663                 }
664         }
665
666         [Flags]
667         enum ParseOptions {
668                 // absence of one of these flags, means
669                 // false for that option
670                 AllowItems = 0x1,
671                 Split = 0x2,
672                 AllowMetadata = 0x4,
673
674                 None = 0x8, // == no items, no metadata, and no split
675
676                 // commonly used options
677                 AllowItemsMetadataAndSplit = AllowItems | Split | AllowMetadata,
678                 AllowItemsNoMetadataAndSplit = AllowItems | Split
679         }
680
681         enum ExpressionOptions {
682                 ExpandItemRefs,
683                 DoNotExpandItemRefs
684         }
685 }