[xbuild] Expression.ParseAs<T> - new method
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Expression.cs
index 792c2706ab062acaa43567aecb537af7d93b583f..0d7ad2fa5984896fe57d3b622bc8b6ceefddab82 100644 (file)
 using System;
 using System.IO;
 using System.Collections;
+using System.Collections.Generic;
 using System.Text;
-using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
+using System.Text.RegularExpressions;
+using Mono.XBuild.Utilities;
 
 namespace Microsoft.Build.BuildEngine {
+
+       // Properties and items are processed in two ways
+       // 1. Evaluate, Project calls evaluate on all the item and property groups.
+       //    At this time, the items are fully expanded, all item and property
+       //    references are expanded to get the item's value.
+       //    Properties on the other hand, expand property refs, but _not_
+       //    item references.
+       //
+       // 2. After the 'evaluation' phase, this could be when executing a target/task,
+       //    - Items : no expansion required, as they are already at final value
+       //    - Properties: Item references get expanded now, in the context of the
+       //      batching
+       //
+       // The enum ExpressionOptions is for specifying this expansion of item references.
+       //
+       // GroupingCollection.Evaluate, evaluates all properties and then items
+
        internal class Expression {
        
-               IList   objects;
-               Project project;
-               ItemReference parentItemReference;
+               ExpressionCollection expressionCollection;
+
+               static Regex item_regex;
+               static Regex property_regex;
+               static Regex metadata_regex;
        
-               public Expression (Project project)
+               public Expression ()
                {
-                       this.objects = new ArrayList ();
-                       this.project = project;
+                       this.expressionCollection = new ExpressionCollection ();
                }
-               
-               public Expression (Project project, string source)
-                       : this (project)
+
+               public static T ParseAs<T> (string expression, ParseOptions options, Project project)
                {
-                       ParseSource (source);
+                       Expression expr = new Expression ();
+                       expr.Parse (expression, options);
+                       return (T)expr.ConvertTo (project, typeof (T));
                }
-               
-               public void ParseSource (string source)
+
+               public static T ParseAs<T> (string expression, ParseOptions options, Project project, ExpressionOptions exprOptions)
                {
-                       // FIXME: change StringBuilder to substrings 
-                       if (source == null)
-                               throw new ArgumentNullException ("source");                             
-
-                       // FIXME: hack
-                       source = source.Replace ('/', Path.DirectorySeparatorChar);
-                       source = source.Replace ('\\', Path.DirectorySeparatorChar);
-                       StringBuilder temp = new StringBuilder ();
-                       CharEnumerator it = source.GetEnumerator ();
-                       EvaluationState eState = EvaluationState.Out;
-                       ParenState pState = ParenState.Out;
-                       ApostropheState aState = ApostropheState.Out;
-                       int start = 0;
-                       int current = -1;
-                       
-                       while (it.MoveNext ()) {
-                               current++;
-                               switch (eState) {
-                               case EvaluationState.Out:
-                                       switch (it.Current) {
-                                       case '@':
-                                               if (temp.Length > 0) {
-                                                       objects.Add (temp.ToString ());
-                                                       temp = new StringBuilder ();
-                                               }
-                                               eState = EvaluationState.InItem;
-                                               start = current;
-                                               break;
-                                       case '$':
-                                               if (temp.Length > 0) {
-                                                       objects.Add (temp.ToString ());
-                                                       temp = new StringBuilder ();
-                                               }
-                                               eState = EvaluationState.InProperty;
-                                               start = current;
-                                               break;
-                                       case '%':
-                                               if (temp.Length > 0) {
-                                                       objects.Add (temp.ToString ());
-                                                       temp = new StringBuilder ();
-                                               }
-                                               eState = EvaluationState.InMetadata;
-                                               start = current;
-                                               break;
-                                       default:
-                                               temp.Append (it.Current);
-                                               if (current == source.Length - 1)
-                                                       objects.Add (temp.ToString ());
-                                               break;
-                                       }
-                                       break;
-                               case EvaluationState.InItem:
-                                       switch (it.Current) {
-                                       case '(':
-                                               if (pState == ParenState.Out && aState == ApostropheState.Out)
-                                                       pState = ParenState.Left;
-                                               else if (aState == ApostropheState.Out)
-                                                       throw new Exception ("'(' not expected.");
-                                               break;
-                                       case ')':
-                                               if (pState == ParenState.Left && aState == ApostropheState.Out) {
-                                                       objects.Add (new ItemReference (this, source.Substring (start, current - start + 1)));
-                                                       eState = EvaluationState.Out;
-                                                       pState = ParenState.Out;
-                                               }
-                                               break;
-                                       case '\'':
-                                               if (aState == ApostropheState.In)
-                                                       aState = ApostropheState.Out;
-                                               else
-                                                       aState = ApostropheState.In;
-                                               break;
-                                       default:
-                                               break;
-                                       }
-                                       break;
-                               case EvaluationState.InProperty:
-                                       switch (it.Current) {
-                                       case '(':
-                                               if (pState == ParenState.Out)
-                                                       pState = ParenState.Left;
-                                               else
-                                                       throw new Exception ("'(' expected.");
-                                               break;
-                                       case ')':
-                                               if (pState == ParenState.Left) {
-                                                       objects.Add (new PropertyReference (this, source.Substring (start, current - start + 1)));
-                                                       eState = EvaluationState.Out;
-                                                       pState = ParenState.Out;
-                                               }
-                                               break;
-                                       default:
-                                               break;
-                                       }
-                                       break;
-                               case EvaluationState.InMetadata:
-                                       switch (it.Current) {
-                                       case '(':
-                                               if (pState == ParenState.Out)
-                                                       pState = ParenState.Left;
-                                               break;
-                                       case ')':
-                                               if (pState == ParenState.Left) {
-                                                       objects.Add (new MetadataReference (this, source.Substring (start, current - start + 1)));
-                                                       eState = EvaluationState.Out;
-                                                       pState = ParenState.Out;
-                                               }
-                                               break;
-                                       default:
-                                               break;
-                                       }
-                                       break;
-                               default:
-                                       throw new Exception ("Invalid evaluation state.");
-                               }
-                       }
+                       Expression expr = new Expression ();
+                       expr.Parse (expression, options);
+                       return (T)expr.ConvertTo (project, typeof (T), exprOptions);
                }
 
-               public IEnumerator GetEnumerator ()
+               // Split: Split on ';'
+               //         Eg. Property values don't need to be split
+               //
+               // AllowItems: if false, item refs should not be treated as item refs!
+               //              it converts them to strings in the final expressionCollection
+               //
+               // AllowMetadata: same as AllowItems, for metadata
+               public void Parse (string expression, ParseOptions options)
                {
-                       foreach (object o in objects)
-                               yield return o;
-               }
+                       bool split = (options & ParseOptions.Split) == ParseOptions.Split;
+                       bool allowItems = (options & ParseOptions.AllowItems) == ParseOptions.AllowItems;
+                       bool allowMd = (options & ParseOptions.AllowMetadata) == ParseOptions.AllowMetadata;
+
+                       expression = expression.Replace ('\\', Path.DirectorySeparatorChar);
                
-               public object ToNonArray (Type type)
-               {
-                       if (type.IsArray == true)
-                               throw new ArgumentException ("Type specified can not be array type.");
-                       
-                       return ToObject (ToString (), type);
+                       string [] parts;
+                       if (split)
+                               parts = expression.Split (new char [] {';'}, StringSplitOptions.RemoveEmptyEntries);
+                       else
+                               parts = new string [] { expression };
+
+                       List <ArrayList> p1 = new List <ArrayList> (parts.Length);
+                       List <ArrayList> p2 = new List <ArrayList> (parts.Length);
+                       List <ArrayList> p3 = new List <ArrayList> (parts.Length);
+
+                       Prepare (p1, parts.Length);
+                       Prepare (p2, parts.Length);
+                       Prepare (p3, parts.Length);
+
+                       for (int i = 0; i < parts.Length; i++)
+                               p1 [i] = SplitItems (parts [i], allowItems);
+
+                       for (int i = 0; i < parts.Length; i++) {
+                               p2 [i] = new ArrayList ();
+                               foreach (object o in p1 [i]) {
+                                       if (o is string)
+                                               p2 [i].AddRange (SplitProperties ((string) o));
+                                       else
+                                               p2 [i].Add (o);
+                               }
+                       }
+
+                       for (int i = 0; i < parts.Length; i++) {
+                               p3 [i] = new ArrayList ();
+                               foreach (object o in p2 [i]) {
+                                       if (o is string)
+                                               p3 [i].AddRange (SplitMetadata ((string) o));
+                                       else
+                                               p3 [i].Add (o);
+                               }
+                       }
+
+                       CopyToExpressionCollection (p3, allowItems, allowMd);
                }
-               
-               public object ToArray (Type type)
+
+               void Prepare (List <ArrayList> l, int length)
                {
-                       if (type.IsArray == false)
-                               throw new ArgumentException ("Type specified can not be element type.");
-                       
-                       string[] rawTable = ToString ().Split (';');
-                       int i = 0;
-                       
-                       if (type == typeof (bool[])) {
-                               bool[] array = new bool [rawTable.Length];
-                               foreach (string raw in rawTable)
-                                       array [i++] = (bool) ToObject (raw, typeof (bool));
-                               return array;
-                       } else if (type == typeof (string[])) {
-                               string[] array = new string [rawTable.Length];
-                               foreach (string raw in rawTable)
-                                       array [i++] = (string) ToObject (raw, typeof (string));
-                               return array;
-                       } else if (type == typeof (int[])) {
-                               int[] array = new int [rawTable.Length];
-                               foreach (string raw in rawTable)
-                                       array [i++] = (int) ToObject (raw, typeof (int));
-                               return array;
-                       } else if (type == typeof (uint[])) {
-                               uint[] array = new uint [rawTable.Length];
-                               foreach (string raw in rawTable)
-                                       array [i++] = (uint) ToObject (raw, typeof (uint));
-                               return array;
-                       } else if (type == typeof (DateTime[])) {
-                               DateTime[] array = new DateTime [rawTable.Length];
-                               foreach (string raw in rawTable)
-                                       array [i++] = (DateTime) ToObject (raw, typeof (DateTime));
-                               return array;
-                       } else throw new Exception ("Invalid type.");
+                       for (int i = 0; i < length; i++)
+                               l.Add (null);
                }
                
-               private object ToObject (string raw, Type type)
+               void CopyToExpressionCollection (List <ArrayList> lists, bool allowItems, bool allowMd)
                {
-                       if (type == typeof (bool)) {
-                               return Boolean.Parse (raw);
-                       } else if (type == typeof (string)) {
-                               return raw;
-                       } else if (type == typeof (int)) {
-                               return Int32.Parse (raw);
-                       } else if (type == typeof (uint)) {
-                               return UInt32.Parse (raw);
-                       } else if (type == typeof (DateTime)) {
-                               return DateTime.Parse (raw);
-                       } else {
-                               throw new Exception (String.Format ("Unknown type: {0}", type.ToString ()));
+                       for (int i = 0; i < lists.Count; i++) {
+                               foreach (object o in lists [i]) {
+                                       if (o is string)
+                                               expressionCollection.Add (MSBuildUtils.Unescape ((string) o));
+                                       else if (!allowItems && o is ItemReference)
+                                               expressionCollection.Add (((ItemReference) o).OriginalString);
+                                       else if (!allowMd && o is MetadataReference) {
+                                               expressionCollection.Add (((MetadataReference) o).OriginalString);
+                                       }
+                                       else if (o is IReference)
+                                               expressionCollection.Add ((IReference) o);
+                               }
+                               if (i < lists.Count - 1)
+                                       expressionCollection.Add (";");
                        }
                }
-               
-               private new string ToString ()
+
+               ArrayList SplitItems (string text, bool allowItems)
                {
-                       StringBuilder sb = new StringBuilder ();
-                       
-                       foreach (object o in this) {
-                               if (o is string) {
-                                       sb.Append ((string) o);
-                               } else if (o is ItemReference) {
-                                       sb.Append (((ItemReference)o).ToString ());
-                               } else if (o is PropertyReference) {
-                                       sb.Append (((PropertyReference)o).ToString ());
-                               } else if (o is MetadataReference) {
-                                       // FIXME: we don't handle them yet
-                               } else {
-                                       throw new Exception ("Invalid type in objects collection.");
+                       ArrayList phase1 = new ArrayList ();
+                       Match m;
+                       m = ItemRegex.Match (text);
+
+                       while (m.Success) {
+                               string name = null, transform = null, separator = null;
+                               ItemReference ir;
+                               
+                               name = m.Groups [ItemRegex.GroupNumberFromName ("itemname")].Value;
+                               
+                               if (m.Groups [ItemRegex.GroupNumberFromName ("has_transform")].Success)
+                                       transform = m.Groups [ItemRegex.GroupNumberFromName ("transform")].Value;
+                               
+                               if (m.Groups [ItemRegex.GroupNumberFromName ("has_separator")].Success)
+                                       separator = m.Groups [ItemRegex.GroupNumberFromName ("separator")].Value;
+
+                               ir = new ItemReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
+                                               name, transform, separator, m.Groups [0].Index, m.Groups [0].Length);
+                               phase1.Add (ir);
+                               m = m.NextMatch ();
+                       }
+
+                       ArrayList phase2 = new ArrayList ();
+                       int last_end = -1;
+                       int end = text.Length - 1;
+
+                       foreach (ItemReference ir in phase1) {
+                               int a,b;
+
+                               a = last_end;
+                               b = ir.Start;
+
+                               if (b - a - 1 > 0) {
+                                       phase2.Add (text.Substring (a + 1, b - a - 1));
                                }
+
+                               last_end = ir.End;
+                               phase2.Add (ir);
                        }
-                       return sb.ToString ();
+
+                       if (last_end < end)
+                               phase2.Add (text.Substring (last_end + 1, end - last_end));
+
+                       return phase2;
                }
 
-               public ITaskItem ToITaskItem ()
+               ArrayList SplitProperties (string text)
                {
-                       ITaskItem item;
-                       
-                       if (objects == null)
-                               throw new Exception ("Cannot cast empty expression to ITaskItem.");
-                       
-                       if (objects [0] is ItemReference) {
-                               ItemReference ir = (ItemReference) objects [0];
-                               ITaskItem[] array = ir.ToITaskItemArray ();
-                               if (array.Length == 1) {
-                                       return array [0];
-                               } else {
-                                       throw new Exception ("TaskItem array too long");
+                       ArrayList phase1 = new ArrayList ();
+                       Match m;
+                       m = PropertyRegex.Match (text);
+
+                       while (m.Success) {
+                               string name = null;
+                               PropertyReference pr;
+                               
+                               name = m.Groups [PropertyRegex.GroupNumberFromName ("name")].Value;
+                               
+                               pr = new PropertyReference (name, m.Groups [0].Index, m.Groups [0].Length);
+                               phase1.Add (pr);
+                               m = m.NextMatch ();
+                       }
+
+                       ArrayList phase2 = new ArrayList ();
+                       int last_end = -1;
+                       int end = text.Length - 1;
+
+                       foreach (PropertyReference pr in phase1) {
+                               int a,b;
+
+                               a = last_end;
+                               b = pr.Start;
+
+                               if (b - a - 1 > 0) {
+                                       phase2.Add (text.Substring (a + 1, b - a - 1));
                                }
-                       } else {
-                               item = new TaskItem (ToString ());
-                               return item;
+
+                               last_end = pr.End;
+                               phase2.Add (pr);
                        }
+
+                       if (last_end < end)
+                               phase2.Add (text.Substring (last_end + 1, end - last_end));
+
+                       return phase2;
                }
-               
-               public ITaskItem[] ToITaskItemArray ()
+
+               ArrayList SplitMetadata (string text)
                {
-                       ArrayList finalItems = new ArrayList ();
-                       ArrayList tempItems = new ArrayList ();
-                       ITaskItem[] array;
-                       ITaskItem[] finalArray;
-                       
-                       foreach (object o in objects) {
-                               if (o is ItemReference) {
-                                       tempItems.Add (o);
-                               } else if (o is PropertyReference) {
-                                       PropertyReference pr = (PropertyReference) o;
-                                       tempItems.Add (pr.ToString ());
-                               } else if (o is MetadataReference) {
-                                       // FIXME: not handled yet
-                               } else if (o is string) {
-                                       tempItems.Add (o);
-                               } else {
-                                       throw new Exception ("Invalid type in objects collection.");
-                               }
+                       ArrayList phase1 = new ArrayList ();
+                       Match m;
+                       m = MetadataRegex.Match (text);
+
+                       while (m.Success) {
+                               string name = null, meta = null;
+                               MetadataReference mr;
+                               
+                               if (m.Groups [MetadataRegex.GroupNumberFromName ("name")].Success)
+                                       name = m.Groups [MetadataRegex.GroupNumberFromName ("name")].Value;
+                               
+                               meta = m.Groups [MetadataRegex.GroupNumberFromName ("meta")].Value;
+                               
+                               mr = new MetadataReference (text.Substring (m.Groups [0].Index, m.Groups [0].Length),
+                                                               name, meta, m.Groups [0].Index, m.Groups [0].Length);
+                               phase1.Add (mr);
+                               m = m.NextMatch ();
                        }
-                       foreach (object o in tempItems) {
-                               if (o is ItemReference) {
-                                       ItemReference ir = (ItemReference) o;
-                                       array = ir.ToITaskItemArray ();
-                                       if (array != null)
-                                               foreach (ITaskItem item in array)
-                                                       finalItems.Add (item);
-                               } else if (o is string) {
-                                       string s = (string) o;
-                                       array = ITaskItemArrayFromString (s);
-                                       foreach (ITaskItem item in array)
-                                               finalItems.Add (item);
-                               } else {
-                                       throw new Exception ("Invalid type in tempItems collection.");
+
+                       ArrayList phase2 = new ArrayList ();
+                       int last_end = -1;
+                       int end = text.Length - 1;
+
+                       foreach (MetadataReference mr in phase1) {
+                               int a,b;
+
+                               a = last_end;
+                               b = mr.Start;
+
+                               if (b - a - 1> 0) {
+                                       phase2.Add (text.Substring (a + 1, b - a - 1));
                                }
+
+                               last_end = mr.End;
+                               phase2.Add (mr);
                        }
-                       
-                       finalArray = new ITaskItem [finalItems.Count];
-                       int i = 0;
-                       foreach (ITaskItem item in finalItems)
-                               finalArray [i++] = item;
-                       return finalArray;
+
+                       if (last_end < end)
+                               phase2.Add (text.Substring (last_end + 1, end - last_end));
+
+                       return phase2;
                }
-               
-               // FIXME: quite stupid name
-               private ITaskItem[] ITaskItemArrayFromString (string source)
+
+               public object ConvertTo (Project project, Type type)
                {
-                       ArrayList tempItems = new ArrayList ();
-                       ITaskItem[] finalArray;
-                       string[] splittedSource = source.Split (';');
-                       foreach (string s in splittedSource) {
-                               if (s != String.Empty) {
-                                       tempItems.Add (new TaskItem (s));
-                               }
+                       return ConvertTo (project, type, ExpressionOptions.ExpandItemRefs);
+               }
+
+               public object ConvertTo (Project project, Type type, ExpressionOptions options)
+               {
+                       return expressionCollection.ConvertTo (project, type, options);
+               }
+
+               public ExpressionCollection Collection {
+                       get { return expressionCollection; }
+               }
+
+               static Regex ItemRegex {
+                       get {
+                               if (item_regex == null)
+                                       item_regex = new Regex (
+                                               @"@\(\s*"
+                                               + @"(?<itemname>[_A-Za-z][_\-0-9a-zA-Z]*)"
+                                               + @"(?<has_transform>\s*->\s*'(?<transform>[^']*)')?"
+                                               + @"(?<has_separator>\s*,\s*'(?<separator>[^']*)')?"
+                                               + @"\s*\)");
+                               return item_regex;
                        }
-                       finalArray = new ITaskItem [tempItems.Count];
-                       int i = 0;
-                       foreach (ITaskItem item in tempItems)
-                               finalArray [i++] = item;
-                       return finalArray;
                }
-               
-               public Project Project {
-                       get { return project; }
+
+               static Regex PropertyRegex {
+                       get {
+                               if (property_regex == null)
+                                       property_regex = new Regex (
+                                               @"\$\(\s*"
+                                               + @"(?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)"
+                                               + @"\s*\)");
+                               return property_regex;
+                       }
                }
-               
-               public ItemReference ParentItemReference {
-                       get { return parentItemReference; }
+
+               static Regex MetadataRegex {
+                       get {
+                               if (metadata_regex == null)
+                                       metadata_regex = new Regex (
+                                               @"%\(\s*"
+                                               + @"((?<name>[_a-zA-Z][_\-0-9a-zA-Z]*)\.)?"
+                                               + @"(?<meta>[_a-zA-Z][_\-0-9a-zA-Z]*)"
+                                               + @"\s*\)");
+                               return metadata_regex;
+                       }
                }
        }
 
-       internal enum EvaluationState {
-               Out,
-               InItem,
-               InMetadata,
-               InProperty
-       }
-       
-       internal enum ParenState {
-               Out,
-               Left
-       }
-       
-       internal enum ApostropheState {
-               In,
-               Out
+       [Flags]
+       enum ParseOptions {
+               // absence of one of these flags, means
+               // false for that option
+               AllowItems = 0x1,
+               Split = 0x2,
+               AllowMetadata = 0x4,
+
+               None = 0x8, // == no items, no metadata, and no split
+
+               // commonly used options
+               AllowItemsMetadataAndSplit = AllowItems | Split | AllowMetadata,
+               AllowItemsNoMetadataAndSplit = AllowItems | Split
        }
-       
-       internal enum ItemParsingState {
-               Name,
-               Transform1,
-               Transform2,
-               Separator
+
+       enum ExpressionOptions {
+               ExpandItemRefs,
+               DoNotExpandItemRefs
        }
 }