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
}
}