[xbuild] Expression.ParseAs<T> - new method
[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 #if NET_2_0
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                 ExpressionCollection expressionCollection;
59
60                 static Regex item_regex;
61                 static Regex property_regex;
62                 static Regex metadata_regex;
63         
64                 public Expression ()
65                 {
66                         this.expressionCollection = new ExpressionCollection ();
67                 }
68
69                 public static T ParseAs<T> (string expression, ParseOptions options, Project project)
70                 {
71                         Expression expr = new Expression ();
72                         expr.Parse (expression, options);
73                         return (T)expr.ConvertTo (project, typeof (T));
74                 }
75
76                 public static T ParseAs<T> (string expression, ParseOptions options, Project project, ExpressionOptions exprOptions)
77                 {
78                         Expression expr = new Expression ();
79                         expr.Parse (expression, options);
80                         return (T)expr.ConvertTo (project, typeof (T), exprOptions);
81                 }
82
83                 // Split: Split on ';'
84                 //         Eg. Property values don't need to be split
85                 //
86                 // AllowItems: if false, item refs should not be treated as item refs!
87                 //              it converts them to strings in the final expressionCollection
88                 //
89                 // AllowMetadata: same as AllowItems, for metadata
90                 public void Parse (string expression, ParseOptions options)
91                 {
92                         bool split = (options & ParseOptions.Split) == ParseOptions.Split;
93                         bool allowItems = (options & ParseOptions.AllowItems) == ParseOptions.AllowItems;
94                         bool allowMd = (options & ParseOptions.AllowMetadata) == ParseOptions.AllowMetadata;
95
96                         expression = expression.Replace ('\\', Path.DirectorySeparatorChar);
97                 
98                         string [] parts;
99                         if (split)
100                                 parts = expression.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
101                         else
102                                 parts = new string [] { expression };
103
104                         List <ArrayList> p1 = new List <ArrayList> (parts.Length);
105                         List <ArrayList> p2 = new List <ArrayList> (parts.Length);
106                         List <ArrayList> p3 = new List <ArrayList> (parts.Length);
107
108                         Prepare (p1, parts.Length);
109                         Prepare (p2, parts.Length);
110                         Prepare (p3, parts.Length);
111
112                         for (int i = 0; i < parts.Length; i++)
113                                 p1 [i] = SplitItems (parts [i], allowItems);
114
115                         for (int i = 0; i < parts.Length; i++) {
116                                 p2 [i] = new ArrayList ();
117                                 foreach (object o in p1 [i]) {
118                                         if (o is string)
119                                                 p2 [i].AddRange (SplitProperties ((string) o));
120                                         else
121                                                 p2 [i].Add (o);
122                                 }
123                         }
124
125                         for (int i = 0; i < parts.Length; i++) {
126                                 p3 [i] = new ArrayList ();
127                                 foreach (object o in p2 [i]) {
128                                         if (o is string)
129                                                 p3 [i].AddRange (SplitMetadata ((string) o));
130                                         else
131                                                 p3 [i].Add (o);
132                                 }
133                         }
134
135                         CopyToExpressionCollection (p3, allowItems, allowMd);
136                 }
137
138                 void Prepare (List <ArrayList> l, int length)
139                 {
140                         for (int i = 0; i < length; i++)
141                                 l.Add (null);
142                 }
143                 
144                 void CopyToExpressionCollection (List <ArrayList> lists, bool allowItems, bool allowMd)
145                 {
146                         for (int i = 0; i < lists.Count; i++) {
147                                 foreach (object o in lists [i]) {
148                                         if (o is string)
149                                                 expressionCollection.Add (MSBuildUtils.Unescape ((string) o));
150                                         else if (!allowItems && o is ItemReference)
151                                                 expressionCollection.Add (((ItemReference) o).OriginalString);
152                                         else if (!allowMd && o is MetadataReference) {
153                                                 expressionCollection.Add (((MetadataReference) o).OriginalString);
154                                         }
155                                         else if (o is IReference)
156                                                 expressionCollection.Add ((IReference) o);
157                                 }
158                                 if (i < lists.Count - 1)
159                                         expressionCollection.Add (";");
160                         }
161                 }
162
163                 ArrayList SplitItems (string text, bool allowItems)
164                 {
165                         ArrayList phase1 = new ArrayList ();
166                         Match m;
167                         m = ItemRegex.Match (text);
168
169                         while (m.Success) {
170                                 string name = null, transform = null, separator = null;
171                                 ItemReference ir;
172                                 
173                                 name = m.Groups [ItemRegex.GroupNumberFromName ("itemname")].Value;
174                                 
175                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_transform")].Success)
176                                         transform = m.Groups [ItemRegex.GroupNumberFromName ("transform")].Value;
177                                 
178                                 if (m.Groups [ItemRegex.GroupNumberFromName ("has_separator")].Success)
179                                         separator = m.Groups [ItemRegex.GroupNumberFromName ("separator")].Value;
180
181                                 ir = new ItemReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
182                                                 name, transform, separator, m.Groups [0].Index, m.Groups [0].Length);
183                                 phase1.Add (ir);
184                                 m = m.NextMatch ();
185                         }
186
187                         ArrayList phase2 = new ArrayList ();
188                         int last_end = -1;
189                         int end = text.Length - 1;
190
191                         foreach (ItemReference ir in phase1) {
192                                 int a,b;
193
194                                 a = last_end;
195                                 b = ir.Start;
196
197                                 if (b - a - 1 > 0) {
198                                         phase2.Add (text.Substring (a + 1, b - a - 1));
199                                 }
200
201                                 last_end = ir.End;
202                                 phase2.Add (ir);
203                         }
204
205                         if (last_end < end)
206                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
207
208                         return phase2;
209                 }
210
211                 ArrayList SplitProperties (string text)
212                 {
213                         ArrayList phase1 = new ArrayList ();
214                         Match m;
215                         m = PropertyRegex.Match (text);
216
217                         while (m.Success) {
218                                 string name = null;
219                                 PropertyReference pr;
220                                 
221                                 name = m.Groups [PropertyRegex.GroupNumberFromName ("name")].Value;
222                                 
223                                 pr = new PropertyReference (name, m.Groups [0].Index, m.Groups [0].Length);
224                                 phase1.Add (pr);
225                                 m = m.NextMatch ();
226                         }
227
228                         ArrayList phase2 = new ArrayList ();
229                         int last_end = -1;
230                         int end = text.Length - 1;
231
232                         foreach (PropertyReference pr in phase1) {
233                                 int a,b;
234
235                                 a = last_end;
236                                 b = pr.Start;
237
238                                 if (b - a - 1 > 0) {
239                                         phase2.Add (text.Substring (a + 1, b - a - 1));
240                                 }
241
242                                 last_end = pr.End;
243                                 phase2.Add (pr);
244                         }
245
246                         if (last_end < end)
247                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
248
249                         return phase2;
250                 }
251
252                 ArrayList SplitMetadata (string text)
253                 {
254                         ArrayList phase1 = new ArrayList ();
255                         Match m;
256                         m = MetadataRegex.Match (text);
257
258                         while (m.Success) {
259                                 string name = null, meta = null;
260                                 MetadataReference mr;
261                                 
262                                 if (m.Groups [MetadataRegex.GroupNumberFromName ("name")].Success)
263                                         name = m.Groups [MetadataRegex.GroupNumberFromName ("name")].Value;
264                                 
265                                 meta = m.Groups [MetadataRegex.GroupNumberFromName ("meta")].Value;
266                                 
267                                 mr = new MetadataReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
268                                                                 name, meta, m.Groups [0].Index, m.Groups [0].Length);
269                                 phase1.Add (mr);
270                                 m = m.NextMatch ();
271                         }
272
273                         ArrayList phase2 = new ArrayList ();
274                         int last_end = -1;
275                         int end = text.Length - 1;
276
277                         foreach (MetadataReference mr in phase1) {
278                                 int a,b;
279
280                                 a = last_end;
281                                 b = mr.Start;
282
283                                 if (b - a - 1> 0) {
284                                         phase2.Add (text.Substring (a + 1, b - a - 1));
285                                 }
286
287                                 last_end = mr.End;
288                                 phase2.Add (mr);
289                         }
290
291                         if (last_end < end)
292                                 phase2.Add (text.Substring (last_end + 1, end - last_end));
293
294                         return phase2;
295                 }
296
297                 public object ConvertTo (Project project, Type type)
298                 {
299                         return ConvertTo (project, type, ExpressionOptions.ExpandItemRefs);
300                 }
301
302                 public object ConvertTo (Project project, Type type, ExpressionOptions options)
303                 {
304                         return expressionCollection.ConvertTo (project, type, options);
305                 }
306
307                 public ExpressionCollection Collection {
308                         get { return expressionCollection; }
309                 }
310
311                 static Regex ItemRegex {
312                         get {
313                                 if (item_regex == null)
314                                         item_regex = new Regex (
315                                                 @"@\(\s*"
316                                                 + @"(?<itemname>[_A-Za-z][_\-0-9a-zA-Z]*)"
317                                                 + @"(?<has_transform>\s*->\s*'(?<transform>[^']*)')?"
318                                                 + @"(?<has_separator>\s*,\s*'(?<separator>[^']*)')?"
319                                                 + @"\s*\)");
320                                 return item_regex;
321                         }
322                 }
323
324                 static Regex PropertyRegex {
325                         get {
326                                 if (property_regex == null)
327                                         property_regex = new Regex (
328                                                 @"\$\(\s*"
329                                                 + @"(?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)"
330                                                 + @"\s*\)");
331                                 return property_regex;
332                         }
333                 }
334
335                 static Regex MetadataRegex {
336                         get {
337                                 if (metadata_regex == null)
338                                         metadata_regex = new Regex (
339                                                 @"%\(\s*"
340                                                 + @"((?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)\.)?"
341                                                 + @"(?<meta>[_a-zA-Z][_\-0-9a-zA-Z]*)"
342                                                 + @"\s*\)");
343                                 return metadata_regex;
344                         }
345                 }
346         }
347
348         [Flags]
349         enum ParseOptions {
350                 // absence of one of these flags, means
351                 // false for that option
352                 AllowItems = 0x1,
353                 Split = 0x2,
354                 AllowMetadata = 0x4,
355
356                 None = 0x8, // == no items, no metadata, and no split
357
358                 // commonly used options
359                 AllowItemsMetadataAndSplit = AllowItems | Split | AllowMetadata,
360                 AllowItemsNoMetadataAndSplit = AllowItems | Split
361         }
362
363         enum ExpressionOptions {
364                 ExpandItemRefs,
365                 DoNotExpandItemRefs
366         }
367 }
368
369 #endif