Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Expression.cs
1 //
2 // Expression.cs: Stores references to items or properties.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 using System;
29 using System.IO;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Text;
33 using System.Text.RegularExpressions;
34 using Mono.XBuild.Utilities;
35
36 namespace Microsoft.Build.BuildEngine {
37
38         // Properties and items are processed in two ways
39         // 1. Evaluate, Project calls evaluate on all the item and property groups.
40         //    At this time, the items are fully expanded, all item and property
41         //    references are expanded to get the item's value.
42         //    Properties on the other hand, expand property refs, but _not_
43         //    item references.
44         //
45         // 2. After the 'evaluation' phase, this could be when executing a target/task,
46         //    - Items : no expansion required, as they are already at final value
47         //    - Properties: Item references get expanded now, in the context of the
48         //      batching
49         //
50         // The enum ExpressionOptions is for specifying this expansion of item references.
51         //
52         // GroupingCollection.Evaluate, evaluates all properties and then items
53
54         internal class Expression {
55         
56                 ExpressionCollection expressionCollection;
57
58                 static Regex item_regex;
59                 static Regex property_regex;
60                 static Regex metadata_regex;
61         
62                 public Expression ()
63                 {
64                         this.expressionCollection = new ExpressionCollection ();
65                 }
66
67                 public static T ParseAs<T> (string expression, ParseOptions options, Project project)
68                 {
69                         Expression expr = new Expression ();
70                         expr.Parse (expression, options);
71                         return (T)expr.ConvertTo (project, typeof (T));
72                 }
73
74                 public static T ParseAs<T> (string expression, ParseOptions options, Project project, ExpressionOptions exprOptions)
75                 {
76                         Expression expr = new Expression ();
77                         expr.Parse (expression, options);
78                         return (T)expr.ConvertTo (project, typeof (T), exprOptions);
79                 }
80
81                 // Split: Split on ';'
82                 //         Eg. Property values don't need to be split
83                 //
84                 // AllowItems: if false, item refs should not be treated as item refs!
85                 //              it converts them to strings in the final expressionCollection
86                 //
87                 // AllowMetadata: same as AllowItems, for metadata
88                 public void Parse (string expression, ParseOptions options)
89                 {
90                         bool split = (options & ParseOptions.Split) == ParseOptions.Split;
91                         bool allowItems = (options & ParseOptions.AllowItems) == ParseOptions.AllowItems;
92                         bool allowMd = (options & ParseOptions.AllowMetadata) == ParseOptions.AllowMetadata;
93
94                         expression = expression.Replace ('\\', Path.DirectorySeparatorChar);
95                 
96                         string [] parts;
97                         if (split)
98                                 parts = expression.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
99                         else
100                                 parts = new string [] { expression };
101
102                         List <ArrayList> p1 = new List <ArrayList> (parts.Length);
103                         List <ArrayList> p2 = new List <ArrayList> (parts.Length);
104                         List <ArrayList> p3 = new List <ArrayList> (parts.Length);
105
106                         Prepare (p1, parts.Length);
107                         Prepare (p2, parts.Length);
108                         Prepare (p3, parts.Length);
109
110                         for (int i = 0; i < parts.Length; i++)
111                                 p1 [i] = SplitItems (parts [i], allowItems);
112
113                         for (int i = 0; i < parts.Length; i++) {
114                                 p2 [i] = new ArrayList ();
115                                 foreach (object o in p1 [i]) {
116                                         if (o is string)
117                                                 p2 [i].AddRange (SplitProperties ((string) o));
118                                         else
119                                                 p2 [i].Add (o);
120                                 }
121                         }
122
123                         for (int i = 0; i < parts.Length; i++) {
124                                 p3 [i] = new ArrayList ();
125                                 foreach (object o in p2 [i]) {
126                                         if (o is string)
127                                                 p3 [i].AddRange (SplitMetadata ((string) o));
128                                         else
129                                                 p3 [i].Add (o);
130                                 }
131                         }
132
133                         CopyToExpressionCollection (p3, allowItems, allowMd);
134                 }
135
136                 void Prepare (List <ArrayList> l, int length)
137                 {
138                         for (int i = 0; i < length; i++)
139                                 l.Add (null);
140                 }
141                 
142                 void CopyToExpressionCollection (List <ArrayList> lists, bool allowItems, bool allowMd)
143                 {
144                         for (int i = 0; i < lists.Count; i++) {
145                                 foreach (object o in lists [i]) {
146                                         if (o is string)
147                                                 expressionCollection.Add (MSBuildUtils.Unescape ((string) o));
148                                         else if (!allowItems && o is ItemReference)
149                                                 expressionCollection.Add (((ItemReference) o).OriginalString);
150                                         else if (!allowMd && o is MetadataReference) {
151                                                 expressionCollection.Add (((MetadataReference) o).OriginalString);
152                                         }
153                                         else if (o is IReference)
154                                                 expressionCollection.Add ((IReference) o);
155                                 }
156                                 if (i < lists.Count - 1)
157                                         expressionCollection.Add (";");
158                         }
159                 }
160
161                 ArrayList SplitItems (string text, bool allowItems)
162                 {
163                         ArrayList phase1 = new ArrayList ();
164                         Match m;
165                         m = ItemRegex.Match (text);
166
167                         while (m.Success) {
168                                 string name = null, transform = null, separator = null;
169                                 ItemReference ir;
170                                 
171                                 name = m.Groups [ItemRegex.GroupNumberFromName ("itemname")].Value;
172                                 
173                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_transform")].Success)
174                                         transform = m.Groups [ItemRegex.GroupNumberFromName ("transform")].Value;
175                                 
176                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_separator")].Success)
177                                         separator = m.Groups [ItemRegex.GroupNumberFromName ("separator")].Value;
178
179                                 ir = new ItemReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
180                                                 name, transform, separator, m.Groups [0].Index, m.Groups [0].Length);
181                                 phase1.Add (ir);
182                                 m = m.NextMatch ();
183                         }
184
185                         ArrayList phase2 = new ArrayList ();
186                         int last_end = -1;
187                         int end = text.Length - 1;
188
189                         foreach (ItemReference ir in phase1) {
190                                 int a,b;
191
192                                 a = last_end;
193                                 b = ir.Start;
194
195                                 if (b - a - 1 > 0) {
196                                         phase2.Add (text.Substring (a + 1, b - a - 1));
197                                 }
198
199                                 last_end = ir.End;
200                                 phase2.Add (ir);
201                         }
202
203                         if (last_end < end)
204                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
205
206                         return phase2;
207                 }
208
209                 ArrayList SplitProperties (string text)
210                 {
211                         ArrayList phase1 = new ArrayList ();
212                         Match m;
213                         m = PropertyRegex.Match (text);
214
215                         while (m.Success) {
216                                 string name = null;
217                                 PropertyReference pr;
218                                 
219                                 name = m.Groups [PropertyRegex.GroupNumberFromName ("name")].Value;
220                                 
221                                 pr = new PropertyReference (name, m.Groups [0].Index, m.Groups [0].Length);
222                                 phase1.Add (pr);
223                                 m = m.NextMatch ();
224                         }
225
226                         ArrayList phase2 = new ArrayList ();
227                         int last_end = -1;
228                         int end = text.Length - 1;
229
230                         foreach (PropertyReference pr in phase1) {
231                                 int a,b;
232
233                                 a = last_end;
234                                 b = pr.Start;
235
236                                 if (b - a - 1 > 0) {
237                                         phase2.Add (text.Substring (a + 1, b - a - 1));
238                                 }
239
240                                 last_end = pr.End;
241                                 phase2.Add (pr);
242                         }
243
244                         if (last_end < end)
245                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
246
247                         return phase2;
248                 }
249
250                 ArrayList SplitMetadata (string text)
251                 {
252                         ArrayList phase1 = new ArrayList ();
253                         Match m;
254                         m = MetadataRegex.Match (text);
255
256                         while (m.Success) {
257                                 string name = null, meta = null;
258                                 MetadataReference mr;
259                                 
260                                 if (m.Groups [MetadataRegex.GroupNumberFromName ("name")].Success)
261                                         name = m.Groups [MetadataRegex.GroupNumberFromName ("name")].Value;
262                                 
263                                 meta = m.Groups [MetadataRegex.GroupNumberFromName ("meta")].Value;
264                                 
265                                 mr = new MetadataReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
266                                                                 name, meta, m.Groups [0].Index, m.Groups [0].Length);
267                                 phase1.Add (mr);
268                                 m = m.NextMatch ();
269                         }
270
271                         ArrayList phase2 = new ArrayList ();
272                         int last_end = -1;
273                         int end = text.Length - 1;
274
275                         foreach (MetadataReference mr in phase1) {
276                                 int a,b;
277
278                                 a = last_end;
279                                 b = mr.Start;
280
281                                 if (b - a - 1> 0) {
282                                         phase2.Add (text.Substring (a + 1, b - a - 1));
283                                 }
284
285                                 last_end = mr.End;
286                                 phase2.Add (mr);
287                         }
288
289                         if (last_end < end)
290                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
291
292                         return phase2;
293                 }
294
295                 public object ConvertTo (Project project, Type type)
296                 {
297                         return ConvertTo (project, type, ExpressionOptions.ExpandItemRefs);
298                 }
299
300                 public object ConvertTo (Project project, Type type, ExpressionOptions options)
301                 {
302                         return expressionCollection.ConvertTo (project, type, options);
303                 }
304
305                 public ExpressionCollection Collection {
306                         get { return expressionCollection; }
307                 }
308
309                 static Regex ItemRegex {
310                         get {
311                                 if (item_regex == null)
312                                         item_regex = new Regex (
313                                                 @"@\(\s*"
314                                                 + @"(?<itemname>[_A-Za-z][_\-0-9a-zA-Z]*)"
315                                                 + @"(?<has_transform>\s*->\s*'(?<transform>[^']*)')?"
316                                                 + @"(?<has_separator>\s*,\s*'(?<separator>[^']*)')?"
317                                                 + @"\s*\)");
318                                 return item_regex;
319                         }
320                 }
321
322                 static Regex PropertyRegex {
323                         get {
324                                 if (property_regex == null)
325                                         property_regex = new Regex (
326                                                 @"\$\(\s*"
327                                                 + @"(?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)"
328                                                 + @"\s*\)");
329                                 return property_regex;
330                         }
331                 }
332
333                 static Regex MetadataRegex {
334                         get {
335                                 if (metadata_regex == null)
336                                         metadata_regex = new Regex (
337                                                 @"%\(\s*"
338                                                 + @"((?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)\.)?"
339                                                 + @"(?<meta>[_a-zA-Z][_\-0-9a-zA-Z]*)"
340                                                 + @"\s*\)");
341                                 return metadata_regex;
342                         }
343                 }
344         }
345
346         [Flags]
347         enum ParseOptions {
348                 // absence of one of these flags, means
349                 // false for that option
350                 AllowItems = 0x1,
351                 Split = 0x2,
352                 AllowMetadata = 0x4,
353
354                 None = 0x8, // == no items, no metadata, and no split
355
356                 // commonly used options
357                 AllowItemsMetadataAndSplit = AllowItems | Split | AllowMetadata,
358                 AllowItemsNoMetadataAndSplit = AllowItems | Split
359         }
360
361         enum ExpressionOptions {
362                 ExpandItemRefs,
363                 DoNotExpandItemRefs
364         }
365 }